http://www.cnblogs.com/qiaoge/archive/2012/03/31/2427024.html
要了解Linux Kernel代碼的分段信息,需要了解一下gcc的__attribute__的編繹屬性,__attribute__主要用於改變所聲明或定義的函數或 數據的特性,它有很多子項,用於改變作用對象的特性。比如對函數,noline將禁止進行內聯擴展、noreturn表示沒有返回值、pure表明函數除 返回值外,不會通過其它(如全局變量、指針)對函數外部產生任何影響。但這裏我們比較感興趣的是對代碼段起作用子項section。
__attribute__的section子項的使用格式爲:
__attribute__((section("section_name")))
其作用是將作用的函數或數據放入指定名爲"section_name"輸入段。
這裏還要注意一下兩個概念:輸入段和輸出段
輸入段和輸出段是相對於要生成最終的elf或binary時的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源代碼中與段有關的重要宏定義
A. 關於__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。還有其它一些類似的宏定義,這裏不一一列出,其作用都是類似的。
B. 關於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,分析該宏可以看出:
1.其用來定義類型爲initcall_t的static函數指針,函數指針的名稱由參數fn和id決 定:__initcall_##fn##id,這就是函數指針的名稱,它其實是一個變量名稱。從該名稱的定義方法我們其學到了宏定義的一種高級用法,即利 用宏的參數產生名稱,這要藉助於"##"這一符號組合的作用。
2. 這一函數指針變量放入什麼輸入段呢,請看__attribute__ ((__section__ (".initcall" levle ".init"))),輸入段的名稱由level決定,如果level="1",則輸入段是.initcall1.init,如果level="3s", 則輸入段是.initcall3s.init。這一函數指針變量就是放在用這種方法決定的輸入段中的。
3. 這一定義的函數指針變量的初始值是什麼叫,其實就是宏參數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中。
C. __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=後面的內容傳給該處理函數。