module_init宏解析

在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)

解釋同上 12)。

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)();
        ......
        }
    ......
}
相關文章
相關標籤/搜索