Linux驅動late_initcall和module_init相關分析
文章來源:http://blog.chinaunix.net/uid-29570002-id-4387097.html
Linux系統啟動過程很複雜,因為它既需要支援模組靜態載入機制也要支援動態載入機制。模組動態載入機制給系統提供了極大的靈活性,驅動程式既可支援靜態編譯進核心,也可以支援動態載入機制。Linux系統中對裝置和子系統的初始化在最後進行,主要過程可以用下圖表示。
圖1
進入子系統初始化時,在核心init程序中進行裝置初始化,最為複雜、詭異的機制莫過於do_initcalls()函式呼叫,該函式完成了所有需要靜態載入模組的初始化,需要進行靜態載入的核心模組,需要使用一些特定的巨集進行處理,下面詳細來說明一些
先來看看do_initcalls()函式原型:
圖2
核心部分是639~671之間,該部分是一個函式指標呼叫,遍歷_initcall_start~_initcall_end範圍,逐個呼叫函式指標。
那_initcall_start~_initcall_end之間存放的是什麼呢,可以以下面一幅示意圖來說明。
圖3
圖左邊是地址指標,右邊是相關巨集,使用相關巨集處理函式指標,可以將被處理的函式指標放在特定的指標範圍內。例如,網路裝置層初始化函式是net_dev_init(),定義在net/core/dev.c中,在該函式下方有條巨集處理subsys_initcall(net_dev_init),該巨集完成將
這種機制真是比較巧妙,也比較難以理解,設計初衷就是為了實現一個通用的啟動流程,使移植或擴充套件時,只需要對需要啟動載入的模組進行巨集處理即可。
下面來詳細瞭解這種機制的實現方法。
先說一說gcc對手動定位程式碼段位置的支援,_attribute_是gcc的關鍵字,指示編譯器給符號設定特定屬性。編譯完成後輸入到連結器的是各個帶有符號表的檔案,連結器對各個檔案中符號進行重定位,
當然,具體這些段是如何生成的,也是有檔案進行配置,即在連結配置檔案arch/xxx/vmlinux.ds.S.中,如下
圖4
在2.6.16核心中INITCALLS已直接被替換為
*(.initcall1.init)*(.initcall2.init)*(.initcall3.init)*(.initcall4.init)*(.initcall5.init)*(.initcall6.init)*(.initcall7.init) |
這和圖3中的結構是對應的。接下來看看核心提供了哪些巨集定義用來處理特定函式指標和資料。在include/linux/init.h檔案中,包括各種常見的包裝。
#define __define_initcall(level,fn) \static initcall_t __initcall_##fn __attribute_used__ \ __attribute__((__section__(".initcall" level ".init"))) = fn#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) |
可以看出,核心為滿足不同初始化等級,設計了1~7共7個等級,不同等級初始化程式碼用對應的巨集進行處理,讀者可以對照上表進行理解一下。還有其它一些巨集,用於各種任務需求,如模組載入巨集module_init(),module_exit(),其處理又略有不同,讀者可以自己理解一下。
總的來說,initcalls機制提供給核心開發者或驅動開發者一個介面,方便將自己的函式新增到核心初始化列表中,在核心初始化最後階段進行處理。
#define module_init(x) __initcall(x); #define __initcall(fn) device_initcall(fn)modul_init是屬於device_initcall,也就是說 late_initcall 還要在 module_init的後面。