模組的生命週期(module lifecycle)
可載入模組有清楚的生與死,不像一般程式從 main() 跑到 return。它有兩個關鍵時刻:被載入的瞬間(init),此時必須把一切建立起來;被移除的瞬間(exit),此時必須把一切拆除。想像市場裡一個臨時攤位:你到的時候搭起帳棚、擺出貨品;你離開時必須把每一樣東西打包帶走,否則就留下沒人會清理的垃圾。
具體而言,你用 module_init() 標記一個函式、用 module_exit() 標記另一個。當 insmod 或 modprobe 載入 .ko 時,核心恰好呼叫 init 函式一次;它成功回傳 0,無法繼續時則回傳負的錯誤碼(如 -ENODEV),此時載入失敗、模組被丟棄。init 函式通常會註冊裝置、配置緩衝區、請求 IRQ 並映射暫存器。由 rmmod 呼叫的 exit 函式必須以相反順序復原這一切:釋放緩衝區、釋放 IRQ、解除註冊裝置。沒有垃圾回收,也沒有自動清理——init 抓取的任何東西,exit 都得釋放。
這之所以重要,是因為核心一跑就是好幾個月,所以每個洩漏都會累積:一個在 init 配置記憶體卻忘了在 exit 釋放的模組,每一次載入/卸載循環都會洩漏核心記憶體;而漏掉解除註冊可能留下一個過時指標,日後當某處呼叫進你已卸載的程式碼時讓核心當掉。一個常見且危險的錯誤是部分失敗時的清理:若 init 在第 1、2 步成功但在第 3 步失敗,它必須在回傳錯誤前復原第 2、1 步——錯誤路徑必須精確地回退已成功的部分。
static int __init my_init(void) { int err = register_chrdev(...); if (err) return err; // 載入乾淨地失敗 return 0; // 成功 } static void __exit my_exit(void) { unregister_chrdev(...); // 精確復原 init 做過的事 } module_init(my_init); module_exit(my_exit);
最小生命週期:init 回傳 0 或負錯誤碼;exit 把 init 反過來。__init/__exit 提示讓核心在用過後釋放那段程式碼。
init 裡的錯誤路徑必須以相反順序回退已成功的步驟;忘了這點會洩漏核心資源,或留下懸置的註冊,在卸載後讓核心當掉。