Linux Kernel程式碼分段解析




1. gcc__attribute__編繹屬性




要了解Linux Kernel程式碼的分段資訊,需要了解一下gcc__attribute__的編繹屬性,__attribute__主要用于改變所聲明或定義的函數或資料的特徴,它有很多子項,用于改變作用物件的特徴。比如對函數,noline將禁止進行內聯延伸、noreturn表示沒有傳回值、pure表明函數除傳回值外,不會通過其他(如全域變數、指標)對函數外部產生任何影響。但這裡我們比對感興趣的是對程式碼段起作用子項section





__attribute__section子項的使用格式為:


__attribute__((section("section_name")))


其作用是將作用的函數或資料擺入指定名為"section_name"匯入段。




這裡還要注意一下兩個概念:匯入段和匯出段




匯入段和匯出段是相對于要生成最終的elfbinary時的Link過程說的,Link過程的匯入大都是由原始碼編繹生成的目標檔案.o,那么這些.o檔案中包含的段相對link過程來說就是匯入段,而Link的匯出一般是可執行檔案elf或程式庫等,這些匯出檔案中也包含有段,這些匯出檔案中的段就叫做匯出段。匯入段和匯出段本來沒有什么必然的聯繫,是互相獨立,只是在Link過程中,Link程式會根據一定的規則(這些規則其實來源于Link Script),將不同的匯入段重新組合到不同的匯出段中,即使是段的名字,匯入段和匯出段可以完全不同。




其用法舉例如下:




int var __attribute__((section(".xdata"))) = 0;




這樣定義的變數var將被擺入名為.xdata的匯入段,(注意:__attribute__這種用法中的括弧好像很嚴格,這裡的几個括弧好象一個也無法少。)




static int __attribute__((section(".xinit"))) functionA(void)


{


.....


}




這個例子將使函數functionA被擺入名叫.xinit的匯入段。




需要着重注意的是,__attribute__section屬性只指定物件的匯入段,它并無法影響所指定物件最終會放在可執行檔案的什么段。




2. Linux Kernel原始碼中與段有關的重要巨集定義




. 關于__init__initdata__exit__exitdata及類似的巨集




開啟Linux Kernel原始碼樹中的檔案:include/init.h,可以看到有下面的巨集定議:




#define __init __attribute__ ((__section__ (".init.text"))) __cold


#define __initdata __attribute__ (( __section__ (".init.data")))


#define __exitdata __attribute__ (( __section__ (".exit.data")))


#define __exit_call __attribute_used__ __attribute__ (( __section__ (".exitcall.exit")))


#define __init_refok oninline __attribute__ ((__section__ (".text.init.refok")))


#define __initdata_refok __attribute__ ((__section__ (".data.init.refok")))


#define __exit_refok noinline __attribute__ ((__section__ (".exit.text.refok")))


.........


#ifdef MODULE


#define __exit __attribute__ (( __section__ (".exit.text"))) __cold


#else


#define __exit __attribute_used__ __attribute__ ((__section__ (".exit.text"))) __cold


#endif




對于經常寫驅動模組或翻閱Kernel原始碼的人,看到熟悉的巨集了吧:__init, __initdata, __exit, __exitdata




__init 巨集最常用的地方是驅動模組起始化函數的定義處,其目的是將驅動模組的起始化函數擺入名叫.init.text的匯入段。對于__initdata來說,用于資料定義,目的是將資料擺入名叫.init.data的匯入段。其他几個巨集也類似。另外需要注意的是,在以上定意中,用__section__代替了section。還有其他一些類似的巨集定義,這裡不一一列出,其作用都是類似的。




. 關于initcall的一些巨集定義




在該檔案中,下面這條巨集定議更為重要,它是一條可延伸的巨集:




#define __define_initcall(level,fn,id) \


static initcall_t __initcall_##fn##id __attribute_used__ \


__attribute__ ((__section__(".initcall" level ".init"))) = fn




這條巨集帶有3個參數:level,fn, id,解析該巨集可以看出:




 .其用來定義類別為initcall_tstatic函數指標,函數指標的名稱由參數fnid決定:__initcall_##fn##id,這就是函數指標的名稱,它其實是一個變數名稱。從該名稱的定義方法我們其學到了巨集定義的一種進階用法,即利用巨集的參數產生名稱,這要借助于"##"這一象徵式組合的作用。




 . 這一函數指標變數擺入什么匯入段呢,請看__attribute__ ((__section__ (".initcall" levle ".init"))),匯入段的名稱由level決定,如果level="1",則匯入段是.initcall1.init,如果level="3s",則匯入段是.initcall3s.init。這一函數指標變數就是放在用這種方法決定的匯入段中的。




 . 這一定義的函數指標變數的初始值是什么叫,其實就是巨集參數fn,實際使用中,fn其實就是真實定義好的函數。




該巨集定義并不直接使用,請看接下來的這些巨集定義:




#define pure_initcall(fn) __define_initcall("0",fn,0)


#define core_initcall(fn) __define_initcall("1",fn,1)


#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)


#define postcore_initcall(fn) __define_initcall("2",fn,2)


#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)


#define arch_initcall(fn) __define_initcall("3",fn,3)


#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)


#define subsys_initcall(fn) __define_initcall("4",fn,4)


#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)


#define fs_initcall(fn) __define_initcall("5",fn,5)


#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)


#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)


#define device_initcall(fn) __define_initcall("6",fn,6)


#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)


#define late_initcall(fn) __define_initcall("7",fn,7)


#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)




這些巨集定義出來是為了方便的使用__define_initcall巨集定義的,上面每條巨集第一次使用時都會產生一個新的匯入段。




接下來還有一條




#define __initcall(fn) device_initcall(fn)


這一條其實只是定義了另一個代號,即平常使用的__initcall其實就是這兒的device_initcall,用它定義的函數指定位于段.initcall6.init中。




. __setup巨集的來源及使用




__setup這條巨集在Linux Kernel中使用最多的地方就是定義處理Kernel啟動參數的函數及資料架構,請看下面的巨集定義:




#define __setup_param(str, unique_id, fn, early) \


static char __setup_str_##unique_id[] __initdata __aligned(1) = str; \


static struct obs_kernel_param __setup_##unique_id \


__used __section(.init.setup) \


__attribute__((aligned((sizeof(long))))) \


= { __setup_str_##unique_id, fn, early }




#define __setup(str, fn) \


__setup_param(str, fn, fn, 0)




使用Kernel中的例子解析一下這兩條定義:




__setup("root=",root_dev_setup);




這條敘述出現在init/do_mounts.c中,其作用是處理Kernel啟動時的像root=/dev/mtdblock3之類的參數的。




分解一下這條敘述,首先變為:




__setup_param("root=",root_dev_setup,root_dev_setup,0);




繼續分解,將得到下面這段代嗎:




static char __setup_str_root_dev_setup_id[] __initdata __aligned(1) = "root=";


static struct obs_kernel_param __setup_root_dev_setup_id


__used __section(.init.setup)


__attribute__((aligned((sizeof(long)))))


= { __setup_str_root_dev_setup_id, root_dev_setup, 0 };






這段程式碼定義了兩個變數:字元陣列變數__setup_str_root_dev_setup_id,其起始化內容為"root=",由于該變數用__initdata修飾,它將被擺入.init.data匯入段;另一變數是架構變數__setup_root_dev_setup_id,其類別為struct obs_kernel_param, 該變理被擺入匯入段.init.setup中。架構struct struct obs_kernel_param也在該檔案中定義如下:




struct obs_kernel_param {


const char *str;


int (*setup_func)(char *);


int early;


};




變數__setup_root_dev_setup_id的三個成員分別被起始化為:


__setup_str_root_dev_setup_id --> 前面定義的字元陣列變數,初始內容為"root="


root_dev_setup --> 通過巨集傳過來的處理函數。


0 -->常數0,該成員的作用以后解析。




現在不難想像核心啟動時怎么處理啟動參數的了:通過__setup巨集定義obs_kernel_param架構變數都被擺入.init.setup段中,這樣一來實際是使.init.setup段變成一張表,Kernel在處理每一個啟動參數時,都會來尋找這張表,與每一個資料項中的成員str進行比對,如果完全相同,就會呼叫該資料項的函數指標成員setup_func所指向的函數(該函數是在使用__setup巨集定義該變數時傳入的函數參數),并將啟動參數如root=后面的內容傳給該處理函數。


 


static int __init ads7846_setup(char *options)
{
 char *this_opt = NULL;


 if (!options || !*options)
  return 1;


 while ((this_opt = strsep(&options, ",")) != NULL) {
  if (!*this_opt)
   continue;
  else if (!strncmp(this_opt, "x_min:", 6))
   x_min = simple_strtoul(this_opt + 6, NULL, 10);
  else if (!strncmp(this_opt, "x_max:", 6))
   x_max = simple_strtoul(this_opt + 6, NULL, 10);
  else if (!strncmp(this_opt, "y_min:", 6))
   y_min = simple_strtoul(this_opt + 6, NULL, 10);
  else if (!strncmp(this_opt, "y_max:", 6))
   y_max = simple_strtoul(this_opt + 6, NULL, 10);
 }


 return 1;
}
/* Usage: kernel commandline add " calibration=x_min:12,x_max:1111,y_min:12,y_max:1234" */
__setup("calibration=", ads7846_setup);


 


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 立你斯 的頭像
    立你斯

    立你斯學習記錄

    立你斯 發表在 痞客邦 留言(0) 人氣()