來源: http://www.itqun.net/content-detail/194643_2.html
do_initcalls 的原理
前言
macro 定義__define_initcall(level,fn)對於內核的初始化很重要,它指示編譯器在編譯的時候,將一系列初始化函數的起始地址值按照一定的順序
放在一個section中。在內核初始化階段,do_initcalls() 將按順序從該section中以函數指針的形式取出這些函數的起始地址,來依次完成相應
的初始化。由於內核某些部分的初始化需要依賴於其他某些部分的初始化的完成,因此這個順序排列常常非常重要。
下面將從__define_initcall(level,fn) macro 定義的代碼分析入手,依次分析名稱為initcall.init的section的結構,最後分析內核初始化函數
do_initcalls()是如何利用macro 定義__define_initcall(level,fn)及其相關的衍生的7個macro macro 定義,來實現內核某些部分的順序初始化的。
1、分析 __define_initcall(level,fn) macro 定義
1) 這個macro 的定義位於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中。
所以這個macro
定義的的含義是:1) 聲明一個名稱為__initcall_##fn的函數
指針(其中##表示替換連接,);2) 將這個函數指針初始化為fn;3) 編譯
的時候需要把這個函數指針變量放置到名稱為 ".initcall" level ".init"
的section中(比如level="1",代表這個section的名稱是
".initcall1.init")。
2) 舉例:__define_initcall(6,
pci_init)
上述macro
呼叫的含義是:1) 聲明一個函數指針__initcall_pic_init = pci_init;
且 2)
這個指針變量__initcall_pic_init 需要放置到名稱為 .initcall6.init
的section中( 其實質就是將 這個函數pic_init的首地址放置到了這個
section中)。
3) 這個macro 一般並不直接使用,而是被定義成下述其他更簡單的7個衍生macro
這些衍生macro
macro 的定義也位於 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)
因此通過macro
core_initcall() 來聲明的函數指針,將放置到名稱為
.initcall1.init的section中,而通過macro 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中:
extern initcall_t
__initcall_start, __initcall_end;
static void __init
do_initcalls(void)
{
initcall_t *call;
....
for
(call = &__initcall_start; call < &__initcall_end; call++)
{
....
(*call)();
....
}
....
}
這些函數指針指向的函數就是通過macro __define_initcall(level,fn)
賦值的函數fn,他們呼叫的順序就是放置在這些section中的順序,
這個順序很重要, 這就是這個macro __define_initcall(level,fn)的作用。
注意到,這裡__initcall_start
和 __initcall_end 就是section
initcall.init的頭和尾。
4、 歸納之
1) __define_initcall(level,fn)的作用就是指示編譯器把一些初始化函數
的指針(即:函數起始地址)按照順序放置一個名為 .initcall.init 的
section中,這個section又被分成了7個子section,它們按順序排列。
在內核初始化
階段,這些放置到這個section中的函數指針將供
do_initcalls() 按順序依次呼叫,來完成相應初始化。
2) 函數指針放置到的子section由macro 定義的level確定,對應level較小的
子section位於較前面。而位於同一個子section內的函數指針順序不定,
將由編譯器按照編譯的順序隨機指定。
3) 因此,如果你希望某個初始化函數在內核初始化階段就被呼叫,那麼你
就應該使用macro __define_initcall(level,fn)
或 其7個衍生macro 把這個
函數fn的對應的指針放置到按照初始化的順序放置到相關的 section 中。
同事,如果某個初始化函數fn_B需要依賴於另外一個初始化函數fn_A的
完成,那麼你應該把fn_B放在比fn_A對應的level值較大的子section中,
這樣,do_initcalls()將在fn_A之後呼叫fn_B。
****************************************************************
如果你希望某個初始化函數在內核初始化階段就被呼叫,那麼你
就應該使用macro __define_initcall(level,fn) 或 其7個衍生macro 來
把這個初始化函數fn的起始地址按照初始化的順序放置到相關的
section 中。 內核初始化時的do_initcalls()將從這個section
中按順序找到這些函數來執行。 如果你希望某個初始化函數在內核初始化階段就被呼叫,那麼你
就應該使用macro __define_initcall(level,fn) 或 其7個衍生macro 來
把這個初始化函數fn的起始地址按照初始化的順序放置到相關的
section 中。 內核初始化時的do_initcalls()將從這個section
中按順序找到這些函數來執行。
*****************************************************************