Linux核心驅動之初始化和關停

2021-07-03 21:31:34 字數 2957 閱讀 3233

模組初始化函式註冊模組提供的任何功能. 這些功能, 我們指的是新功能, 可以由應用程式訪問的或者一整個驅動或者乙個新軟體抽象. 實際的初始化函式定義常常如:

static int __init initialization_function(void)module_init(initialization_function);

初始化函式應當宣告成靜態的, 因為它們不會在特定檔案之外可見; 沒有硬性規定這個, 然而, 因為沒有函式能輸出給核心其他部分, 除非明確請求. 宣告中的 __init 標誌可能看起來有點怪; 它是乙個給核心的暗示, 給定的函式只是在初始化使用. 模組載入者在模組載入後會丟掉這個初始化函式, 使它的記憶體可做其他用途

使用 moudle_init 是強制的. 這個巨集定義增加了特別的段到模組目標**中, 表明在**找到模組的初始化函式. 沒有這個定義, 你的初始化函式不會被呼叫.

清理函式

每個非試驗性的模組也要求有乙個清理函式, 它登出介面, 在模組被去除之前返回所有資源給系統. 這個函式定義為:

static void __exit cleanup_function(void) module_exit(cleanup_function);

清理函式沒有返回值, 因此它被宣告為 void. __exit 修飾符標識這個**是只用於模組解除安裝( 通過使編譯器把它放在特殊的 elf 段). 如果你的模組直接建立在核心裡, 或者如果你的核心配置成不允許模組解除安裝, 標識為 __exit 的函式被簡單地丟棄. 因為這個原因, 乙個標識 __exit 的函式只在模組解除安裝或者系統停止時呼叫; 任何別的使用是錯的. 再一次, moudle_exit 宣告對於使得核心能夠找到你的清理函式是必要的.

如果你的模組沒有定義乙個清理函式, 核心不會允許它被解除安裝.

初始化中的錯誤處理

你必須記住一件事, 在註冊核心設施時, 註冊可能失敗. 即便最簡單的動作常常需要記憶體分配, 分配的記憶體可能不可用. 因此模組**必須一直檢查返回值, 並且確認要求的操作實際上已經成功.

如果在你註冊工具時發生任何錯誤, 首先第一的事情是決定模組是否能夠無論如何繼續初始化它自己. 常常, 在乙個註冊失敗後模組可以繼續操作, 如果需要可以功能降級. 在任何可能的時候, 你的模組應當盡力向前, 並提供事情失敗後具備的能力.

如果證實你的模組在乙個特別型別的失敗後完全不能載入, 你必須取消任何在失敗前註冊的動作. 核心不保留已經註冊的設施的每模組註冊, 因此如果初始化在某個點失敗, 模組必須能自己退回所有東西. 如果你無法登出你獲取的東西, 核心就被置於乙個不穩定狀態; 它包含了不存在的**的內部指標. 這種情況下, 經常地, 唯一的方法就是重啟系統. 在初始化錯誤發生時, 你確實要小心地將事情做正確.

錯誤恢復有時用 goto 語句處理是最好的. 我們通常不願使用 goto, 但是在我們的觀念裡, 這是乙個它有用的地方. 在錯誤情形下小心使用 goto 可以去掉大量的複雜, 過度對齊的, "結構形" 的邏輯. 因此, 在核心裡, goto 是處理錯誤經常用到, 如這裡顯示的.

下面例子**( 使用設施註冊和登出函式)在初始化在任何點失敗時做得正確:

int __init my_init_function(void)

這段**試圖註冊 3 個(虛構的)設施. goto 語句在失敗情況下使用, 在事情變壞之前只對之前已經成功註冊的設施進行登出.

另乙個選項, 不需要繁多的 goto 語句, 是跟蹤已經成功註冊的, 並且在任何出錯情況下呼叫你的模組的清理函式. 清理函式只回卷那些已經成功完成的步驟. 然而這種選擇, 需要更多**和更多 cpu 時間, 因此在快速途徑下, 你仍然依賴於 goto 作為最好的錯誤恢復工具.

my_init_function 的返回值, err, 是乙個錯誤碼. 在 linux 核心裡, 錯誤碼是負數, 屬於定義於 的集合. 如果你需要產生你自己的錯誤碼代替你從其他函式得到的返回值, 你應當包含 以便使用符號式的返回值, 例如 -enodev, -enomem, 等等. 返回適當的錯誤碼總是乙個好做法, 因為使用者程式能夠把它們轉變為有意義的字串, 使用 perror 或者類似的方法.

顯然, 模組清理函式必須撤銷任何由初始化函式進行的註冊, 並且慣例(但常常不是要求的)是按照註冊時相反的順序登出設施.

void __exit my_cleanup_function(void)

如果你的初始化和清理比處理幾項複雜, goto 方法可能變得難於管理, 因為所有的清理**必須在初始化函式裡重複, 包括幾個混合的標號. 有時, 因此, 一種不同的**排布證明更成功.

使**重複最小和所有東西流線化, 你應當做的是無論何時發生錯誤都從初始化裡呼叫清理函式. 清理函式接著必須在撤銷它的註冊前檢查每一項的狀態. 以最簡單的形式, **看起來象這樣:

struct something *item1;

struct somethingelse *item2;

int stuff_ok;

void my_cleanup(void)

int __init my_init(void)

如這段**所示, 你也許需要, 也許不要外部的標誌來標識初始化步驟的成功, 要依賴你呼叫的註冊/分配函式的語義. 不管要不要標誌, 這種初始化會變得包含大量的項, 常常比之前展示的技術要好. 注意, 但是, 清理函式當由非退出**呼叫時不能標誌為 __exit, 如同前面的例子.

模組載入競爭

到目前, 我們的討論已來到乙個模組載入的重要方面: 競爭情況. 如果你在如何編寫你的初始化函式上不小心

首先時你應該一直記住, 核心的某些別的部分會在註冊完成之後馬上使用任何你註冊的設施. 這是完全可能的, 換句話說, 核心將呼叫進你的模組, 在你的初始化函式仍然在執行時. 所以你的**必須準備好被呼叫, 一旦它完成了它的第乙個註冊. 不要註冊任何設施, 直到所有的需要支援那個設施的你的內部初始化已經完成.

你也必須考慮到如果你的初始化函式決定失敗會發生什麼, 但是核心的一部分已經在使用你的模組已註冊的設施. 如果這種情況對你的模組是可能的, 你應當認真考慮根本不要使初始化失敗. 畢竟, 模組已清楚地成功輸出一些有用的東西. 如果初始化必須失敗, 必須小心地處理任何可能的在核心別處發生的操作, 直到這些操作已完成.

linux核心 策略路由之初始化

4.2 路由策略初始化 路由策略的初始化,主要分為通用策略規則初始化和協議相關的策略規則初始化。4.2.1 通用策略規則初始化 功能 註冊通知鏈,包括裝置註冊和登出時的通知。static int init fib rules init void 4.2.2 協議相關的策略規則初始化 這裡主要講ipv...

linux核心 路由fib表之初始化

1 路由表操作 主要實現下列功能 a 路由表初始化 b 路由建立 c 路由刪除 1.1 路由表的初始化 路由表的初始化由ip fib init函式實現的,功能 a 註冊路由操作相關函式 b 將路由模組新增到網路命令空間 c 註冊網路裝置狀態和位址變化到通知鏈 d 為fib node和fib alia...

Linux裝置驅動第 2 章之 初始化和關閉

2.7.初始化和關閉 模組的初始化函式負責註冊模組所提供的任何設施。這裡的設施指的是乙個可以被應用程式訪問的新功能,它可能是乙個完整的驅動程式或者僅僅是乙個新的軟體抽象。初始化函式定義通常如下所示 static int init initialization function void module...