Linux 可加載內核模塊剖析

Linux 可加載內核模塊剖析

Linux® 可加載內核模塊(從內核的 1.2 版本開始引入)是 Linux 內核的最重要創新之一。它們提供了可伸縮的、動態的內核。探索隱藏在可加載模塊後面的原理,並學習這些獨立的對象如何動態地轉換成 Linux 內核的一部分。

Linux 就是通常所說的單內核(monolithic kernel),即操作系統的大部分功能都被稱爲內核,並在特權模式下運行。它與微型內核 不同,後者只把基本的功能(進程間通信 [IPC]、調度、基本的輸入/輸出 [I/O] 和內存管理)當作內核運行,而把其他功能(驅動程序、網絡堆棧和文件系統)排除在特權空間之外。因此,您可能認爲 Linux 是一個完全靜態的內核,但事實恰恰相反。通過 Linux 內核模塊(LKM)可以在運行時動態地更改 Linux。


可動態更改 是指可以將新的功能加載到內核、從內核去除某個功能,甚至添加使用其他 LKM 的新 LKM。LKM 的優點是可以最小化內核的內存佔用,只加載需要的元素(這是嵌入式系統的重要特性)。

Linux 不是可以進行動態更改的惟一(也不是第一個)單內核。Berkeley Software Distribution(BSD)的變體、Sun Solaris、更老的內核(比如 OpenVMS),以及其他流行的操作系統(比如 Microsoft® Windows® 和 Apple Mac OS X)都支持可加載模塊。

剖析內核模塊

LKM 與直接編譯到內核或典型程序的元素有根本區別。典型的程序有一個 main 函數,其中 LKM 包含 entry 和 exit 函數(在 2.6 版本,您可以任意命名這些函數)。當向內核插入模塊時,調用 entry 函數,從內核刪除模塊時則調用 exit 函數。因爲 entry 和 exit 函數是用戶定義的,所以存在 module_initmodule_exit 宏,用於定義這些函數屬於哪種函數。LKM 還包含一組必要的宏和一組可選的宏,用於定義模塊的許可證、模塊的作者、模塊的描述等等。圖 1 提供了一個非常簡單的 LKM 的視圖。


圖 1. 簡單 LKM 的源代碼視圖
簡單 LKM 的源代碼視圖

2.6 版本的 Linux 內核提供了一個新的更簡單的方法,用於構建 LKM。構建 LKM 時,可以使用典型的用戶工具管理模塊(儘管內部已經改變):標準 insmod(安裝 LKM),rmmod (刪除 LKM),modprobeinsmodrmmod 的包裝器),depmod(用於創建模塊依賴項),以及 modinfo(用於爲模塊宏查找值)。更多關於爲 2.6 版本內核構建 LKM 的信息,請查看 參考資料





回頁首


剖析內核模塊對象

LKM 只不過是一個特殊的可執行可鏈接格式(Executable and Linkable Format,ELF)對象文件。通常,必須鏈接對象文件才能在可執行文件中解析它們的符號和結果。由於必須將 LKM 加載到內核後 LKM 才能解析符號,所以 LKM 仍然是一個 ELF 對象。您可以在 LKM 上使用標準對象工具(在 2.6 版本中,內核對象帶有後綴 .ko,)。例如,如果在 LKM 上使用 objdump 實用工具,您將發現一些熟悉的區段(section),比如 .text(說明)、.data(已初始化數據)和 .bss(塊開始符號或未初始化數據)。

您還可以在模塊中找到其他支持動態特性的區段。.init.text 區段包含 module_init 代碼,.exit.text 區段包含 module_exit 代碼(參見圖 2)。.modinfo 區段包含各種表示模塊許可證、作者和描述等的宏文本。


圖 2. 具有各種 ELF 區段的 LKM 的示例
具有各種 ELF 區段的 LKM 的示例

瞭解 LKM 的基礎知識之後,現在我們進一步探索模塊是如何進入內核的,以及在內核內部是如何管理模塊的。





回頁首


LKM 的生命週期

在用戶空間中,insmod(插入模塊)啓動模塊加載過程。insmod 命令定義需要加載的模塊,並調用 init_module 用戶空間系統調用,開始加載過程。2.6 版本內核的 insmod 命令經過修改後變得非常簡單(70 行代碼),可以在內核中執行更多工作。insmod 並不進行所有必要的符號解析(處理 kerneld),它只是通過 init_module 函數將模塊二進制文件複製到內核,然後由內核完成剩餘的任務。

init_module 函數通過系統調用層,進入內核到達內核函數 sys_init_module(參見圖 3)。這是加載模塊的主要函數,它利用許多其他函數完成困難的工作。類似地,rmmod 命令會使 delete_module 執行 system call 調用,而 delete_module 最終會進入內核,並調用 sys_delete_module 將模塊從內核刪除。


圖 3. 加載和卸載模塊時用到的主要命令和函數
加載和卸載模塊時用到的主要命令和函數

在模塊的加載和卸載期間,模塊子系統維護了一組簡單的狀態變量,用於表示模塊的操作。加載模塊時,狀態爲 MODULE_STATE_COMING。如果模塊已經加載並且可用,狀態爲 MODULE_STATE_LIVE。此外,卸載模塊時,狀態爲 MODULE_STATE_GOING





回頁首


模塊加載細節

現在,我們看看加載模塊時的內部函數(參見圖 4)。當調用內核函數 sys_init_module 時,會開始一個許可檢查,查明調用者是否有權執行這個操作(通過 capable 函數完成)。然後,調用 load_module 函數,這個函數負責將模塊加載到內核並執行必要的調試(後面還會討論這點)。load_module 函數返回一個指向最新加載模塊的模塊引用。這個模塊加載到系統內具有雙重鏈接的所有模塊的列表上,並且通過 notifier 列表通知正在等待模塊狀態改變的線程。最後,調用模塊的 init() 函數,更新模塊狀態,表明模塊已經加載並且可用。


圖 4. 內部(簡化的)模塊加載過程
內部(簡化的)模塊加載過程

加載模塊的內部細節是 ELF 模塊解析和操作。load_module 函數(位於 ./linux/kernel/module.c)首先分配一塊用於容納整個 ELF 模塊的臨時內存。然後,通過 copy_from_user 函數將 ELF 模塊從用戶空間讀入到臨時內存。作爲一個 ELF 對象,這個文件的結構非常獨特,易於解析和驗證。

下一步是對加載的 ELF 映像執行一組健康檢查(它是有效的 ELF 文件嗎?它適合當前的架構嗎?等等)。完成健康檢查後,就會解析 ELF 映像,然後會爲每個區段頭創建一組方便變量,簡化隨後的訪問。因爲 ELF 對象的偏移量是基於 0 的(除非重新分配),所以這些方便變量將相對偏移量包含到臨時內存塊中。在創建方便變量的過程中還會驗證 ELF 區段頭,確保加載的是有效模塊。

任何可選的模塊參數都從用戶空間加載到另一個已分配的內核內存塊(第 4 步),並且更新模塊狀態,表明模塊已加載(MODULE_STATE_COMING)。如果需要 per-CPU 數據(這在檢查區段頭時確定),那麼就分配 per-CPU 塊。

在前面的步驟,模塊區段被加載到內核(臨時)內存,並且知道哪個區段應該保持,哪個可以刪除。步驟 7 爲內存中的模塊分配最終的位置,並移動必要的區段(ELF 頭中的 SHF_ALLOC,或在執行期間佔用內存的區段)。然後執行另一個分配,大小是模塊必要區段所需的大小。迭代臨時 ELF 塊中的每個區段,並將需要執行的區段複製到新的塊中。接下來要進行一些額外的維護。同時還進行符號解析,可以解析位於內核中的符號(被編譯成內核映象),或臨時的符號(從其他模塊導出)。

然後爲每個剩餘的區段迭代新的模塊並執行重新定位。這個步驟與架構有關,因此依賴於爲架構(./linux/arch/<arch>/kernel/module.c)定義的 helper 函數。最後,刷新指令緩存(因爲使用了臨時 .text 區段),執行一些額外的維護(釋放臨時模塊內存,設置系統文件),並將模塊最終返回到 load_module





回頁首


模塊卸載細節

卸載模塊的過程和加載模塊基本一樣,除了必須進行幾個健康檢查外(確保安全刪除模塊)。卸載模塊過程首先在用戶空間調用 rmmod(刪除模塊)命令。在 rmmod 命令內部,對 delete_module 執行系統調用,它最終會導致在內核內部調用 sys_delete_module(查看 圖 3)。圖 5 演示了刪除模塊的基本操作過程。


圖 5. 內部(簡化的)模塊卸載過程
內部(簡化的)模塊卸載過程

當調用內核函數 sys_delete_module(將要刪除的模塊的名稱作爲參數傳入)之後,第一步便是確保調用方具有權限。接下來會檢查一個列表,查看是否存在依賴於這個模塊的其他模塊。這裏有一個名爲 modules_which_use_me 的列表,它包含每個依賴模塊的一個元素。如果這個列表爲空,就不存在任何模塊依賴項,因此這個模塊就是要刪除的模塊(否則會返回一個錯誤)。接下來還要測試模塊是否加載。用戶可以在當前安裝的模塊上調用 rmmod,因此這個檢查確保模塊已經加載。在幾個維護檢查之後,倒數第二個步驟是調用模塊的 exit 函數(模塊內部自帶)。最後,調用 free_module 函數。

調用 free_module 函數之後,您將發現模塊將被安全刪除。該模塊不存在依賴項,因此可以開始模塊的內核清理過程。首先,從安裝期間添加的各種列表中(系統文件、模塊列表等)刪除模塊。其次,調用一個與架構相關的清理例程(可以在 ./linux/arch/<arch>/kernel/module.c 中找到)。然後迭代具有依賴性的模塊,並將這個模塊從這些列表中刪除。最後,從內核的角度而言,清理已經完成,爲模塊分配的各種內存已被釋放,包括參數內存、per-CPU 內存和模塊的 ELF 內存(coreinit)。





回頁首


爲模塊管理優化內核

在許多應用程序中,動態加載模塊非常重要,但加載之後,就沒有必要卸載模塊。這允許內核在啓動時是動態的(根據找到的設備加載模塊),但並不是在整個操作過程中都是動態的。如果不需要在加載之後卸載模塊,那麼可以進行一些優化,減少模塊管理所需的代碼。您可以 “取消” 內核配置選項 CONFIG_MODULE_UNLOAD,刪除大量與卸載模塊相關的內核功能。





回頁首


結束語

這一直是內核裏面模塊管理過程的高級視圖。要獲得模塊管理的細節,源代碼本身就是最佳的文檔。關於在模塊管理中調用的主要函數,請查看 ./linux/kernel/module.c(以及 ./linux/include/linux/module.h 中的頭文件)。您還可以在 ./linux/arch/<arch>/kernel/module.c 中找到幾個與架構相關的函數。最後,可以在 ./linux/kernel/kmod.c 中找到內核自動加載函數(可以根據需要從內核自動加載模塊)。這個功能可以通過 CONFIG_KMOD 配置選項啓用。



參考資料

學習

獲得產品和技術
  • 訂購 SEK for Linux,共包含兩張 DVD,其中有用於 Linux 的最新 IBM 試用軟件,包括 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。

  • 使用可直接從 developerWorks 下載的 IBM 試用軟件 構建您的下一個 Linux 開發項目。


討論


關於作者

M. Tim Jones

M. Tim Jones 是一名嵌入式固件架構師,同時也是 Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming(第二版)、AI Application Programming(第二版)和 BSD Sockets Programming from a Multilanguage Perspective 等書的作者。他的工程背景非常廣泛,從同步宇宙飛船的內核開發到嵌入式架構設計,再到網絡協議的開發。Tim 是位於科羅拉多州 Longmont 的 Emulex Corp. 的一名顧問工程師。


發佈了10 篇原創文章 · 獲贊 12 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章