在init.h中咱們看到 #define module_init(x) __initcall(x); 還看到 #define __initcall(fn) device_initcall(fn) 還有 #define __define_initcall(level,fn) \ static initcall_t __initcall_##fn __attribute_used__ \ __attribute__((__section__(".initcall" level ".init"))) = fn __define_initcall 做用 宏定義__define_initcall(level,fn) 對於內核的初始化很重要,它指示編譯器在編譯的時候,將一系列初始化函數的起始地址值按照必定的順序放在一個section中。在內核初始化階 段,do_initcalls() 將按順序從該section中以函數指針的形式取出這些函數的起始地址,來依次完成相應的初始化。因爲內核某些部分的初始化須要依賴於其餘某些部分的初始 化的完成,所以這個順序排列經常很是重要。 下面將從__define_initcall(level,fn) 宏定義的代碼分析入手,依次分析名稱爲initcall.init的section的結構,最後分析內核初始化函數 do_initcalls()是如何利用宏定義__define_initcall(level,fn)及其相關的衍生的7個宏宏定義,來實現內核某些部分的順序初始化的。 1、分析 __define_initcall(level,fn) 宏定義 1) 這個宏的定義位於inlclude\linux\init.h中: #define __define_initcall(level,fn) \ static initcall_t __initcall_##fn \ __attribute__((__section__(".initcall" level ".init"))) \ = fn 其中 initcall_t 是一個函數指針類型: typedef int (*initcall_t)(void); 而屬性 __attribute__((__section__())) 則表示把對象放在一個這個由括號中的名稱所指代的section中。 因此這個宏定義的的含義是: (1) 聲明一個名稱爲__initcall_##fn的函數指針(其中##表示替換鏈接); (2) 將這個函數指針初始化爲fn; (3) 編譯的時候須要把這個函數指針變量放置到名稱爲 ".initcall" level ".init"的section中(好比level="1",表明這個section的名稱是 ".initcall1.init")。 2) 舉例:__define_initcall(6, pci_init)上述宏調用的含義是: 聲明一個函數指針__initcall_pic_init = pci_init;且 這個指針變量__initcall_pic_init 須要放置到名稱爲 .initcall6.init的section中( 其實質就是將 這個函數pic_init的首地址放置到了這個section中)。 3) 這個宏通常並不直接使用,而是被定義成下述其餘更簡單的7個衍生宏這些衍生宏宏的定義也位於 inlclude\linux\Init.h 中: #define core_initcall(fn) __define_initcall("1",fn) #define postcore_initcall(fn) __define_initcall("2",fn) #define arch_initcall(fn) __define_initcall("3",fn) #define subsys_initcall(fn) __define_initcall("4",fn) #define fs_initcall(fn) __define_initcall("5",fn) #define device_initcall(fn) __define_initcall("6",fn) #define late_initcall(fn) __define_initcall("7",fn) 所以經過宏 core_initcall() 來聲明的函數指針,將放置到名稱爲 .initcall1.init的section中,而經過宏 postcore_initcall() 來聲明的函數指針,將放置到名稱爲.initcall2.init的section中,依次類推。 4) 舉例:device_initcall(pci_init) 解釋同上 1-2)。 2、與初始化調用有關section--initcall.init被分紅了7個子section 1) 它們依次是.initcall1.init、.initcall2.init、...、.initcall7.init 2) 按照前後順序依次排列 3) 它們的定義在文件vmlinux.lds.S中 例如 對於i386+,在i386\kernel\vmlinux.lds.S中有: __initcall_start = .; .initcall.init : { *(.initcall1.init) *(.initcall2.init) *(.initcall3.init) *(.initcall4.init) *(.initcall5.init) *(.initcall6.init) *(.initcall7.init) } __initcall_end = .; 而在makefile 中有 LDFLAGS_vmlinux += -T arch/$(ARCH)/kernel/vmlinux.lds.s 4) 在這7個section總的開始位置被標識爲__initcall_start,而在結尾被標識爲__initcall_end。 3、 內核初始化函數do_basic_setup(): do_initcalls() 將從.initcall.init 中,也就是這7個section中依次取出全部的函數指針,並調用這些函數指針所指向的函數,來完成內核的一些相關的初始化。 這個函數的定義位於init\main.c中: //很簡單 static void __init do_initcalls(void) { ...... for (call = __initcall_start; call < __initcall_end; call++) { ...... result = (*call)(); ...... } ...... }