轉自: http://blog.csdn.net/linweig/article/details/5085487
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,它是以一個什麼樣的東西呢?我們知道,他編譯之後會是一個ko尾碼名的東西,但是裡面是一個什麼樣的。我們看,一般的模組的模板。
/*
* hello.c Hello, World! As a Kernel Module
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
/*
* hello_init the init function, called when the module is loaded.
* Returns zero if successfully loaded, nonzero otherwise.
*/
static int hello_init(void)
{
printk(KERN_ALERT "I bear a charmed life./n");
return 0;
}
/*
* hello_exit the exit function, called when the module is removed.
*/
static void hello_exit(void)
{
printk(KERN_ALERT "Out, out, brief candle!/n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shakespeare");
模組並不是驅動,只是Linux的一種核心擴充機制,驅動就是藉助這種機制的,理論上應該這樣的理解。
這就是一個模組的基本模板,通過編譯之後,會產生一個ko,注意,編譯的時候與其他的應用程式有很大的不同,原因是它產生的東西是一個完全獨立的ELF。
LKM 與直接編譯到核心或典型程式的元素有根本區別。典型的程式有一個 main 函數,其中 LKM 包含 entry 和 exit 函數(在 2.6 版本,您可以任意命名這些函數)。當向核心插入模組時,調用 entry 函數,從核心刪除模組時則調用 exit 函數。因為 entry 和 exit 函數是使用者定義的,所以存在 module_init
和 module_exit
宏,用於定義這些函數屬於哪種函數。LKM 還包含一組必要的宏和一組可選的宏,用於定義模組的許可證、模組的作者、模組的描述等等。
2.6 版本的 Linux 核心提供了一個新的更簡單的方法,用於構建 LKM。構建 LKM 時,可以使用典型的使用者工具管理模組(儘管內部已經改變):標準 insmod
(安裝 LKM),rmmod
(刪除 LKM),modprobe
(insmod
和 rmmod
的封裝器),depmod
(用於建立模組依賴項),以及 modinfo
(用於為模組宏尋找值)。
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 區段包含各種表示模組許可證、作者和描述等的宏文本。
在使用者空間中,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
將模組從核心刪除。
在模組的載入和卸載期間,模組子系統維護了一組簡單的狀態變數,用於表示模組的操作。載入模組時,狀態為 MODULE_STATE_COMING
。如果模組已經載入並且可用,狀態為 MODULE_STATE_LIVE
。此外,卸載模組時,狀態為 MODULE_STATE_GOING
。
下面開始剖析一下,這樣的一個elf檔案是如何加入進核心的:
現在,我們看看載入模組時的內建函式。當調用核心功能 sys_init_module
時,會開始一個許可檢查,查明調用者是否有權執行這個操作(通過 capable
函數完成)。然後,調用 load_module
函數,這個函數負責將模組載入到核心並執行必要的調試(後面還會討論這點)。load_module
函數返回一個指向最新載入模組的模組引用。這個模組載入到系統內具有雙重連結的所有模組的列表上,並且通過 notifier 列表通知正在等待模組狀態改變的線程。最後,調用模組的 init()
函數,更新模組狀態,表明模組已經載入並且可用。
/* This is where the real work happens */
asmlinkage long
sys_init_module(void __user *umod,
unsigned long len,
const char __user *uargs)
{
struct module *mod;
int ret = 0;
/* Must have permission */
if (!capable(CAP_SYS_MODULE))
return -EPERM;
/* Only one module load at a time, please */
if (mutex_lock_interruptible(&module_mutex) != 0)
return -EINTR;
/* Do all the hard work */
mod = load_module(umod, len, uargs);
if (IS_ERR(mod)) {
mutex_unlock(&module_mutex);
return PTR_ERR(mod);
}
/* Drop lock so they can recurse */
mutex_unlock(&module_mutex);
blocking_notifier_call_chain(&module_notify_list,
MODULE_STATE_COMING, mod);
/* Start the module */
if (mod->init != NULL)
ret = do_one_initcall(mod->init);
if (ret < 0) {
/* Init routine failed: abort. Try to protect us from
buggy refcounters. */
mod->state = MODULE_STATE_GOING;
synchronize_sched();
module_put(mod);
blocking_notifier_call_chain(&module_notify_list,
MODULE_STATE_GOING, mod);
mutex_lock(&module_mutex);
free_module(mod);
mutex_unlock(&module_mutex);
wake_up(&module_wq);
return ret;
}
if (ret > 0) {
printk(KERN_WARNING "%s: '%s'->init suspiciously returned %d, "
"it should follow 0/-E convention/n"
KERN_WARNING "%s: loading module anyway.../n",
__func__, mod->name, ret,
__func__);
dump_stack();
}
/* Now it's a first class citizen! Wake up anyone waiting for it. */
mod->state = MODULE_STATE_LIVE;
wake_up(&module_wq);
mutex_lock(&module_mutex);
/* Drop initial reference. */
module_put(mod);
unwind_remove_table(mod->unwind_info, 1);
module_free(mod, mod->module_init);
mod->module_init = NULL;
mod->init_size = 0;
mod->init_text_size = 0;
mutex_unlock(&module_mutex);
return 0;
}
載入模組的內部細節是 ELF 模組解析和操作。load_module
函數(位於 ./linux/kernel/module.c)首先分配一塊用於容納整個 ELF 模組的臨時記憶體。然後,通過 copy_from_user
函數將 ELF 模組從使用者空間讀入到臨時記憶體。作為一個 ELF 對象,這個檔案的結構非常獨特,易於解析和驗證。
這個函數的代碼比較長,實現的就是一個模組的解析,找出各個Section,這個有興趣的人可以看看Linux核心的源碼。
下一步是對載入的 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
。