Linux模块机制浅析
Linux模塊機制淺析
?
Linux允許用戶通過插入模塊,實現干預內核的目的。一直以來,對linux的模塊機制都不夠清晰,因此本文對內核模塊的加載機制進行簡單地分析。
模塊的Hello World!
我們通過創建一個簡單的模塊進行測試。首先是源文件main.c和Makefile。
florian@florian-pc:~/module$ cat main.c
#include<linux/module.h>
#include<linux/init.h>
?
static int __init init(void)
{
??? printk("Hi module!\n");
??? return 0;
}
?
static void __exit exit(void)
{
??? printk("Bye module!\n");
}
?
module_init(init);
module_exit(exit);
其中init為模塊入口函數,在模塊加載時被調用執行,exit為模塊出口函數,在模塊卸載被調用執行。
florian@florian-pc:~/module$ cat Makefile
obj-m += main.o
#generate the path
CURRENT_PATH:=$(shell pwd)
#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)
#the absolute path
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
#complie object
all:
??? make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
#clean
clean:
??? make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
其中,obj-m指定了目標文件的名稱,文件名需要和源文件名相同(擴展名除外),以便于make自動推導。
然后使用make命令編譯模塊,得到模塊文件main.ko。
florian@florian-pc:~/module$ make
make -C /usr/src/linux-headers-2.6.35-22-generic?M=/home/florian/module modules
make[1]: 正在進入目錄 `/usr/src/linux-headers-2.6.35-22-generic'
? Building modules, stage 2.
? MODPOST 1 modules
make[1]:正在離開目錄 `/usr/src/linux-headers-2.6.35-22-generic'
使用insmod和rmmod命令對模塊進行加載和卸載操作,并使用dmesg打印內核日志。
florian@florian-pc:~/module$ sudo insmod main.ko;dmesg | tail -1
[31077.810049] Hi module!
?
florian@florian-pc:~/module$ sudo rmmod main.ko;dmesg | tail -1
[31078.960442] Bye module!
通過內核日志信息,可以看出模塊的入口函數和出口函數都被正確調用執行。
模塊文件
使用readelf命令查看一下模塊文件main.ko的信息。
florian@florian-pc:~/module$ readelf -h main.ko
ELF Header:
? Magic:?? 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
? Class:???????????????????????????? ELF32
? Data:????????????????????????????? 2's complement, little endian
? Version:?????????????????????????? 1 (current)
? OS/ABI:??????????????????????????? UNIX - System V
? ABI Version:?????????????????????? 0
? Type:????????????????????????????? REL (Relocatable file)
? Machine:?????????????????????????? Intel 80386
? Version:?????????????????????????? 0x1
? Entry point address:?????????????? 0x0
? Start of program headers:????????? 0 (bytes into file)
? Start of section headers:????????? 1120 (bytes into file)
? Flags:???????????????????????????? 0x0
? Size of this header:?????????????? 52 (bytes)
? Size of program headers:?????????? 0 (bytes)
? Number of program headers:???????? 0
? Size of section headers:?????????? 40 (bytes)
? Number of section headers:???????? 19
? Section header string table index: 16
我們發現main.ko的文件類型為可重定位目標文件,這和一般的目標文件格式沒有任何區別。我們知道,目標文件是不能直接執行的,它需要經過鏈接器的地址空間分配、符號解析和重定位的過程,轉化為可執行文件才能執行。
那么,內核將main.ko加載后,是否對其進行了鏈接呢?
模塊數據結構
首先,我們了解一下模塊的內核數據結構。
linux3.5.2/kernel/module.h:220
struct module
{
??? ……
??? /* Startup function. */
??? int (*init)(void);
??? ……
??? /* Destruction function. */
??? void (*exit)(void);
??? ……
};
模塊數據結構的init和exit函數指針記錄了我們定義的模塊入口函數和出口函數。
模塊加載
模塊加載由內核的系統調用init_module完成。
linux3.5.2/kernel/module.c:3009
/* This is where the real work happens */
SYSCALL_DEFINE3(init_module, void __user *, umod,
?????? unsigned long, len, const char __user *, uargs)
{
??? struct module *mod;
??? int ret = 0;
??? ……
??? /* Do all the hard work */
??? mod = load_module(umod, len, uargs);//模塊加載
??? ……
??? /* Start the module */
??? if (mod->init != NULL)
?????? ret = do_one_initcall(mod->init);//模塊init函數調用
??? ……
??? return 0;
}
系統調用init_module由SYSCALL_DEFINE3(init_module...)實現,其中有兩個關鍵的函數調用。load_module用于模塊加載,do_one_initcall用于回調模塊的init函數。
函數load_module的實現為。
linux3.5.2/kernel/module.c:2864
/* Allocate and load the module: note that size of section 0 is always
?? zero, and we rely on this for optional sections. */
static struct module *load_module(void __user *umod,
????????????? ? unsigned long len,
????????????? ? const char __user *uargs)
{
??? struct load_info info = { NULL, };
??? struct module *mod;
??? long err;
??? ……
??? /* Copy in the blobs from userspace, check they are vaguely sane. */
??? err = copy_and_check(&info, umod, len, uargs);//拷貝到內核
??? if (err)
?????? return ERR_PTR(err);
??? /* Figure out module layout, and allocate all the memory. */
??? mod = layout_and_allocate(&info);//地址空間分配
??? if (IS_ERR(mod)) {
?????? err = PTR_ERR(mod);
?????? goto free_copy;
??? }
??? ……
??? /* Fix up syms, so that st_value is a pointer to location. */
??? err = simplify_symbols(mod, &info);//符號解析
??? if (err < 0)
?????? goto free_modinfo;
??? err = apply_relocations(mod, &info);//重定位
??? if (err < 0)
?????? goto free_modinfo;
??? ……
}
函數load_module內有四個關鍵的函數調用。copy_and_check將模塊從用戶空間拷貝到內核空間,layout_and_allocate為模塊進行地址空間分配,simplify_symbols為模塊進行符號解析,apply_relocations為模塊進行重定位。
由此可見,模塊加載時,內核為模塊文件main.ko進行了鏈接的過程!
至于函數do_one_initcall的實現就比較簡單了。
linux3.5.2/kernel/init.c:673
int __init_or_module do_one_initcall(initcall_t fn)
{
??? int count = preempt_count();
??? int ret;
??? if (initcall_debug)
?????? ret = do_one_initcall_debug(fn);
??? else
?????? ret = fn();//調用init module
??? ……
??? return ret;
}
即調用了模塊的入口函數init。
模塊卸載
模塊卸載由內核的系統調用delete_module完成。
linux3.5.2/kernel/module.c:768
SYSCALL_DEFINE2(delete_module, const char __user *, name_user,
??? ??? unsigned int, flags)
{
??? struct module *mod;
??? char name[MODULE_NAME_LEN];
??? int ret, forced = 0;
??? ……
??? /* Final destruction now no one is using it. */
??? if (mod->exit != NULL)
?????? mod->exit();//調用exit module
??? ……
??? free_module(mod);//卸載模塊
??? ……
}
通過回調exit完成模塊的出口函數功能,最后調用free_module將模塊卸載。
結論
如此看來,內核模塊其實并不神秘。傳統的用戶程序需要編譯為可執行程序才能執行,而模塊程序只需要編譯為目標文件的形式便可以加載到內核,有內核實現模塊的鏈接,將之轉化為可執行代碼。同時,在內核加載和卸載的過程中,會通過函數回調用戶定義的模塊入口函數和模塊出口函數,實現相應的功能。
參考資料
http://hi.baidu.com/20065562/item/15dcc4ce92c3d510b67a24af
http://blog.chinaunix.net/uid-26009923-id-3840337.html?
總結
以上是生活随笔為你收集整理的Linux模块机制浅析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: maven jetty/tomcat/w
- 下一篇: Android自动化测试之Robotiu