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_init
和 module_exit
宏,用於定義這些函數屬於哪種函數。LKM 還包含一組必要的宏和一組可選的宏,用於定義模塊的許可證、模塊的作者、模塊的描述等等。圖 1 提供了一個非常簡單的
LKM 的視圖。
圖 1. 簡單 LKM 的源代碼視圖
2.6 版本的 Linux 內核提供了一個新的更簡單的方法,用於構建 LKM。構建 LKM 時,可以使用典型的用戶工具管理模塊(儘管內部已經改變):標準
insmod
(安裝 LKM),rmmod
(刪除
LKM),modprobe
(insmod
和 rmmod
的包裝器),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 的示例
瞭解 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 內存(core
和 init
)。
|
在許多應用程序中,動態加載模塊非常重要,但加載之後,就沒有必要卸載模塊。這允許內核在啓動時是動態的(根據找到的設備加載模塊),但並不是在整個操作過程中都是動態的。如果不需要在加載之後卸載模塊,那麼可以進行一些優化,減少模塊管理所需的代碼。您可以
“取消” 內核配置選項 CONFIG_MODULE_UNLOAD
,刪除大量與卸載模塊相關的內核功能。
|
這一直是內核裏面模塊管理過程的高級視圖。要獲得模塊管理的細節,源代碼本身就是最佳的文檔。關於在模塊管理中調用的主要函數,請查看
./linux/kernel/module.c(以及 ./linux/include/linux/module.h 中的頭文件)。您還可以在
./linux/arch/<arch>/kernel/module.c 中找到幾個與架構相關的函數。最後,可以在
./linux/kernel/kmod.c 中找到內核自動加載函數(可以根據需要從內核自動加載模塊)。這個功能可以通過
CONFIG_KMOD
配置選項啓用。
學習
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文。
- 查看 Rusty Russell 的博客 “Bleeding Edge”,瞭解他當前的 Linux 內核開發。Rusty 是新的 Linux
模塊架構的主要開發人員。
- Linux Kernel Module Programming Guide 雖然有點過時,但提供了大量關於
LKM 及其開發的詳細信息。
- 查看 “使用 /proc 文件系統來訪問 Linux 內核的內容”(developerWorks,2006 年 3
月),詳細瞭解在 /proc 文件系統上進行 LKM 編程。
- 通過 “使用 Linux 系統調用的內核命令”(developerWorks,2007 年 3
月)學習有關係統調用的細節。
- 要更多地瞭解 Linux 內核,請閱讀 Tim 的 “Linux 內核剖析”(developerWorks,2007 年 6
月),它是本系列的第一篇文章,從較高層次介紹了 Linux 內核以及一些有趣的內容。
- “Standards and specs: An unsung hero: the hardworking
ELF”(developerWorks,2005 年 12 月)是有關 ELF 的出色介紹。ELF 是 Linux 的標準對象格式。ELF
是一種靈活的文件格式,它涵蓋了可執行映像、對象、共享庫,甚至內核轉儲等。您還可以在 格式參考資料(PDF 文檔)和 有關 ELF 格式的書籍 中找到更加詳細的信息。
- Captain's Universe 通過示例生成文件提供了關於 LKM 構建的出色介紹。在 2.6
版本內核中,構建 LKM 的過程有了變化(變得更好)。
- 這裏有一些用於插入、刪除和管理模塊的模塊實用程序。可以通過
insmod
命令將模塊插入內核,通過rmmod
命令刪除模塊。要查詢內核中當前的模塊,使用lsmod
命令。因爲模塊可以依賴於其他模塊,所以可以用depmod
命令構建一個依賴項文件。要在感興趣的模塊之前自動加載依賴模塊,可以使用modprobe
命令(insmod
的包裝器)。最後,您可以使用modinfo
命令讀取 LKM 的模塊信息 。 - Linux Journal 上的文章 “Linkers and
Loaders”(2002 年 11 月)大量介紹了使用 ELF 文件(包括符號解析和重定位)的鏈接器和加載器的目的。
- developerWorks Linux 專區 提供了更多針對 Linux 開發人員的資源,同時請瀏覽我們的
最受歡迎的文章和教程。
- 查看 developerWorks 上所有 Linux 技巧 和 Linux 教程。
- 隨時關注 developerWorks 技術活動和網絡廣播。
獲得產品和技術
- 訂購 SEK
for Linux,共包含兩張 DVD,其中有用於 Linux 的最新 IBM 試用軟件,包括
DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。
- 使用可直接從 developerWorks 下載的 IBM
試用軟件 構建您的下一個 Linux 開發項目。
討論
- 通過博客、論壇、podcast 和空間參與 developerWorks 社區。
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. 的一名顧問工程師。 |