解密module_init

轉載自:http://blog.163.com/xinbuqianjin@126/blog/static/167563447201010221231507/

在Linux底下寫過driver模塊的對這個宏一定不會陌生。module_init宏在MODULE宏有沒有定義的情況下展開的內容是不同的,如果這個宏沒有定義,基本上表明閣下的模塊是要編譯進內核的(obj-y)。
1.在MODULE沒有定義這種情況下,module_init定義如下:
#define module_init(x) __initcall(x);
因爲
#define __initcall(fn)                            device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
所以,module_init(x)最終展開爲:
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
更直白點,假設閣下driver所對應的模塊的初始化函數爲int gpio_init(void),那麼module_init(gpio_init)實際上等於:
static initcall_t  __initcall_gpio_init_6 __used __attribute__((__section__(".initcall6.init"))) = gpio_init;
就是聲明一類型爲initcall_t(typedef int (*initcall_t)(void))函數指針類型的變量__initcall_gpio_init_6並將gpio_init賦值與它。
這裏的函數指針變量聲明比較特殊的地方在於,將這個變量放在了一名爲".initcall6.init"節中。接下來結合vmlinux.lds中的
.initcall.init : AT(ADDR(.initcall.init) - (0xc0000000 -0x00000000)) {
   __initcall_start = .;
   *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
   __initcall_end = .;
   }
以及do_initcalls:
static void __init do_initcalls(void)
{
initcall_t *call;
for (call = __initcall_start; call < __initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
那麼就不難理解閣下模塊中的module_init中的初始化函數何時被調用了:在系統啓動過程中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。

2.在MODULE被定義的情況下(大部分可動態加載的driver模塊都屬於此, obj-m),module_init定義如下:
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
這段宏定義關鍵點是後面一句,通過alias將initfn變名爲init_module。前面那個__inittest的定義其實是種技巧,用來對initfn進行某種靜態的類型檢查,如果閣下將模塊初始化函數定義成,比如,void gpio_init(void)或者是int gpio_init(int),那麼在編譯時都會有類似下面的warning:
GPIO/fsl-gpio.c: In function '__inittest':
GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type
通過module_init將模塊初始化函數統一別名爲init_module,這樣以後insmod時候,在系統內部會調用sys_init_module()去找到init_module函數的入口地址。
如果objdump -t gpio.ko,就會發現init_module和gpio_init位於相同的地址偏移處。簡言之,這種情況下模塊的初始化函數在insmod時候被調用。

發佈了38 篇原創文章 · 獲贊 35 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章