本教程操作环境:linux7.3系统、dell g3电脑。
linux中引入模块机制有什么好处?
首先,模块是预先注册自己以便服务于将来的某个请求,然后他的初始化函数就立即结束。换句话说,模块初始化函数的任务就是为以后调用函数预先作准备。
好处:
1) 应用程序在退出时,可以不管资源的释放或者其他的清除工作,但是模块的退出函数却必须仔细此撤销初始化函数所作的一切。
2) 该机制有助于缩短模块的开发周期。即:注册和卸载都很灵活方便。
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 pathcurrent_path:=$(shell pwd)#the current kernel version numberlinux_kernel:=$(shell uname -r)#the absolute pathlinux_kernel_path:=/usr/src/linux-headers-$(linux_kernel)#complie objectall: make -c $(linux_kernel_path) m=$(current_path) modules#cleanclean: 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 modulesmake[1]: 正在进入目录 `/usr/src/linux-headers-2.6.35-22-generic' building modules, stage 2. modpost 1 modulesmake[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将模块卸载。
结论
如此看来,内核模块其实并不神秘。传统的用户程序需要编译为可执行程序才能执行,而模块程序只需要编译为目标文件的形式便可以加载到内核,有内核实现模块的链接,将之转化为可执行代码。同时,在内核加载和卸载的过程中,会通过函数回调用户定义的模块入口函数和模块出口函数,实现相应的功能。
相关推荐:《linux视频教程》
以上就是linux中引入模块机制有什么好处的详细内容。
