一、內核執行流程
內核初始化設備驅動的過程:
第一個C函數從main.c (kernel\init)開始,暫且不論匯編文件
[html] view plaincopyprint?
start_kernel()->rest_init()->do_basic_setup()->do_initcalls()
函數do_initcalls如下:
[html]
static void __init do_initcalls(void)
{
initcall_t *fn;
/*循環調用__initcall_start與__initcall_end之間函數*/
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
二、而關於module_init(x)是如何被加載的
[html]
1.#define module_init(x) __initcall(x);
2.#define __initcall(fn) device_initcall(fn)
3.#define device_initcall(fn) __define_initcall("6",fn)
4.#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
從而可以得出:
module_init(x) = static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = x
首先分析:
1.其中 initcall_t 是個函數指針類型:typedef int (*initcall_t)(void);
2.屬性 __attribute__((__section__())) 則表示把對象放在一個這個由括號中的名稱所指代的section中。
所以這個宏定義的的含義是:
1) 聲明一個名稱為__initcall_##fn的函數指針(其中##表示替換連接,);
2) 將這個函數指針初始化為fn;
3) 編譯的時候需要把這個函數指針變量放置到名稱為 ".initcall" level ".init"
的section中(比如level="1",代表這個section的名稱是 ".initcall 1 .init")。
4)舉例說明:假如我們有這個宏定義module_init(kpd_mod_init);那麼經過預編譯替換之後就發生如下變化
將函數 kpd_mod_init 的首地址放到section的名稱是 ".initcall 6 .init" 的段中去。
注意:為什麼是6?因為我們這裡使用的是device,#define device_initcall(fn) __define_initcall("6",fn)
會被替換成6。具體對應規則如下:
[html]
#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)
在連接腳本段.initcall中將依次存放著
[html]
".initcall 1 .init"
".initcall 2 .init"
".initcall 3 .init"
".initcall 4 .init"
".initcall 5 .init"
".initcall 6 .init"
".initcall 7 .init"
三、現在回到之前提到的
[html]
do_one_initcall(*fn);
注意這也是個回調函數,關於回調函數的進一步說明可以參考有關資料
不難得出,在這裡將會依次執行,上述提到的那7個段中的函數,進行有關的初始化。
假如您希望某個初始化函數在內核初始化階段就被調用,那麼您就應該使用宏__define_initcall(level,fn)
或其7個衍生宏來把這個初始化函數fn的起始地址按照初始化的順序放置到相關的section 中。 內核初始化
時的do_initcalls()將從這個section中按順序找到這些函數來執行。
幾個注意點:
1.module_init這個是只有在該模塊以module方式存在的時候才有意義,當你加載該模塊的時候才會去調用它
,加載哪個模塊,就調用哪個模塊的module_init。如果那個模塊本身直接編譯進內核了,那它的代碼就直接
放在內核中相應的區域了,系統啟動的時候自動就調用這些函數了。在我使用的MTK平台都是將其編譯進內核
了的。
2.*(.initcall6.init)在這裡.initcall6.init是一個數據段的名稱,這句話的意思是所有聲明要放在這個段
的數據都將按順序存放在這個段裡。每個module_init都將定義一個函數指針,這些函數指針被指定要放在
.initcall6.init這個段裡,它們將按順序排列。這樣在內核啟動的時候,它會根據這些函數指針按順序調用
所有的函數。
四、接下來可以開始分析我們的kpd驅動
1.我們知道加入我們把驅動編譯進內核的話,那麼內核啟動初始化的階段會執行module_init(kpd_mod_init);
也就是kpd_mod_init這個函數Kpd.c (mediatek\platform\mt6575\kernel\drivers\keypad)
2.在這個初始化函數中,會有如下流程
kpd_mod_init() -> platform_driver_register(&kpd_pdrv) -> kpd_pdrv_probe(struct platform_device *pdev)
注意了,上述流程將是內核啟動後自動進行的初始化。當然我們假設driver和device是可以在bus上綁定的。
3.所以接下來我們可以看看這個kpd_pdrv_probe到底是做了些什麼事情。