Linux 核心啟動時,所有驅動程式的初始化工作主要集中在 init/main.c 的 do_basic_setup() 函數中。這個函數依序呼叫 init_workqueues()、usermodehelper_init()、driver_init() 等核心初始化函數,最後透過 do_initcalls() 執行所有註冊的初始化呼叫。
do_initcalls() 會遍歷 __initcall_start 到 __initcall_end 之間的所有函數指標並依序執行。這裡有幾個關鍵檢查機制:
在 arch/xxx/kernel/vmlinux.lds.S 連結腳本中定義了 .initcall.init 區段,包含七個子區段:
.initcall.init {
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
這七個優先級對應 include/linux/init.h 中定義的巨集:
| 優先級 | 巨集名稱 | 適用場景 |
|---|---|---|
| 1 | core_initcall(fn) | 核心子系統初始化 |
| 2 | postcore_initcall(fn) | 核心後續初始化 |
| 3 | arch_initcall(fn) | 架構相關初始化 |
| 4 | subsys_initcall(fn) | 子系統初始化 |
| 5 | fs_initcall(fn) | 檔案系統初始化 |
| 6 | device_initcall(fn) | 設備驅動初始化(預設) |
| 7 | late_initcall(fn) | 延遲初始化 |
關鍵發現:我們常用的 module_init() 實際上等同於 device_initcall(),屬於優先級 6(.initcall6.init)。
開發觸控螢幕驅動時常遇到一個經典問題:驅動需要使用 I2C 總線提供的 API,但在核心初始化階段 I2C 子系統尚未就緒。這是因為 I2C 核心初始化屬於 subsys_initcall(優先級 4),而普通設備驅動預設是 device_initcall(優先級 6),導致執行順序錯誤。
subsys_initcall 或 late_initcalli2c_driver 和 input_dev 結構,控制載入順序module_init 中註冊 i2c_driverattach_adapter 回呼中再註冊 input_dev在 2.4 核心中,初始化函數指標順序與連結順序相關,是不確定的。2.6 核心引入七層子區段機制,區段間順序固定,但同一子區段內仍受連結順序影響。這意味著同優先級驅動之間的相對順序仍不可預測,必須透過優先級調整來解決強依賴關係。
late_initcall 處理非關鍵路徑初始化initcall_debug 核心參數除錯初始化順序module_init 配合 MODULE_ALIAS 實現按需載入