Linux内核模块编译与加载
2015-12-14 22:22
696 查看
Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用所需要的组件?
方法一:
把所有的组件都编译进内核文件,即Zlmage或bzlmage,但会导致两个问题:一是生成的内核文件过大;二是如果要添加或删除某个组件,需要重新编译整个内核。
有没有一种机制能让内核文件(zlmage或bzlmage)本身不包含某个组件,而是在该组件需要被使用的时候,动态地添加到正在运行的内核中呢?
有,Linux“内核模块”机制。
方法二:
linux内核模块机制。
内核模块特点:
模块本身并不被编译进内核文件(zlmage或bzlmage),仅以.o或其它形式存在。
可以根据需求,在内核运行期间动态地安装或卸载。
下面写一个内核模块的例子:
与应用程序相比,内核模块没有main函数,输出使用printk,而不是printf。
程序结构:
1)模块加载函数(必需)
安装模块时被系统自动调用的函数,通过module_init宏来指定。
2)模块卸载函数(必需)
卸载模块时被系统自动调用,通过module_exit宏来指定。
1)如果内核模块仅由一个源文件构成,该如何编写Makefile?
说明:KDIR模块依赖的内核源代码路径
KDIR:=/lib/modules/2.6.18-53.e15/build
-C进入后面的路径中,使用该路径中的Makefile编译。
生成的内核模块为.ko文件。
2)由多个源文件组成,main.c add.c
main.c调用了add.c中的函数,main.c和add.c构成内核模块hello.
卸载:rmmod hello
查看:lsmod
加载:modprobe hello
modprobe如同insmod,也是加载一个模块到内核,不同之处在于它会根据文件/lib/modules/
宏MODULE_LICENSE告诉内核,该模块带有一个许可证,否则加载内核时内核会报错。有效的许可证有”GPL”,”GPL v2”,”GPL and additional rights”,”Dual BSD/GPL” ,”Dual MPL/GPL” “proprietary”
hello.c:
2)作者申明 MODULE_AUTHOR(“…..”);
3)模块描述 MODULE_DESCRIPTION(“….”);
4)模块版本 MODULE_VERSION(“V1.0”);
5)模块别名 MODULE_ALIAS(“a simple module”);
6)模块参数 通过宏module_param指定模块参数,用于在加载模块时传递参数给模块。
module_param(name, type, perm)
name:参数名称
type: bool、int、charp(字符串)
perm:访问权限,常见值:
S_IRUGO:任何用户都对/sys/module中出现的该参数具有读写权限。
S_IWUSR:允许root用户修改/sys/module中出现的参数。
例子:声明成模块参数。
param.c:
编译出param.ko模块,
不传值加载: insmod param.ko
输出 Name:David Age: 12
传值加载: insmod param.ko age=12
输出 Age:12
为什么要导出内核符号?
一个内核模块实现了一些函数,为了让其他模块使用,必须将符号导出(函数、变量)
使用EXPORT_SYMBOL导出:
比如在.c文件中定义了两个函数:
常见问题:版本不匹配
内核模块的版本由其依赖的内核代码版本所决定,在加载内核模块事,insmod会将内核模块版本与当前正在运行的内核版本比较,不一致会报错。
解决方法:
1. 使用modprob –force -modversion强行插入
2. 确保编译内核模块时,所依赖内核代码版本与正在运行的内核版本相同。(可通过uname -r查看当前内核版本)
内核模块先在内核中注册自己以便服务于将来的某个请求,然后它的初始化函数结束,此时模块仍然存在于内核中,直到卸载函数被调用。
printk函数有优先级
方法一:
把所有的组件都编译进内核文件,即Zlmage或bzlmage,但会导致两个问题:一是生成的内核文件过大;二是如果要添加或删除某个组件,需要重新编译整个内核。
有没有一种机制能让内核文件(zlmage或bzlmage)本身不包含某个组件,而是在该组件需要被使用的时候,动态地添加到正在运行的内核中呢?
有,Linux“内核模块”机制。
方法二:
linux内核模块机制。
内核模块特点:
模块本身并不被编译进内核文件(zlmage或bzlmage),仅以.o或其它形式存在。
可以根据需求,在内核运行期间动态地安装或卸载。
下面写一个内核模块的例子:
1. 编写hello.c文件
#include <linux/init.h> #include <linux/module.h> static int hello_init(void){ printk(KERN_WARNING"hello world\n"); return 0; } static void hello_exit(void){ printk(KERN_INFO"Goodbye,wold\n"); } module_init(hello_init); module_exit(hello_exit);
与应用程序相比,内核模块没有main函数,输出使用printk,而不是printf。
程序结构:
1)模块加载函数(必需)
安装模块时被系统自动调用的函数,通过module_init宏来指定。
2)模块卸载函数(必需)
卸载模块时被系统自动调用,通过module_exit宏来指定。
2. 编写Makefile文件
模块的编译,通常使用makefile。1)如果内核模块仅由一个源文件构成,该如何编写Makefile?
ifneq ($(KERNELRELEASE),) obj-m:=hello.o else KDIR:=/lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M=$(PWD) modules clean: rm -f *ko *.o *.mod.o *.mod.c *.symvers endif
说明:KDIR模块依赖的内核源代码路径
KDIR:=/lib/modules/2.6.18-53.e15/build
-C进入后面的路径中,使用该路径中的Makefile编译。
生成的内核模块为.ko文件。
2)由多个源文件组成,main.c add.c
main.c调用了add.c中的函数,main.c和add.c构成内核模块hello.
ifneq ($(KERNELRELEASE),) ojb-m:=hello.o hello-objs:=main.o add.o else 后面不变
3. 安装与卸载模块
加载:insmod hello.ko(加载后使用lsmod | grep hello查看)卸载:rmmod hello
查看:lsmod
加载:modprobe hello
modprobe如同insmod,也是加载一个模块到内核,不同之处在于它会根据文件/lib/modules/
模块可选信息:
1)许可证申明宏MODULE_LICENSE告诉内核,该模块带有一个许可证,否则加载内核时内核会报错。有效的许可证有”GPL”,”GPL v2”,”GPL and additional rights”,”Dual BSD/GPL” ,”Dual MPL/GPL” “proprietary”
hello.c:
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("David");
2)作者申明 MODULE_AUTHOR(“…..”);
3)模块描述 MODULE_DESCRIPTION(“….”);
4)模块版本 MODULE_VERSION(“V1.0”);
5)模块别名 MODULE_ALIAS(“a simple module”);
6)模块参数 通过宏module_param指定模块参数,用于在加载模块时传递参数给模块。
module_param(name, type, perm)
name:参数名称
type: bool、int、charp(字符串)
perm:访问权限,常见值:
S_IRUGO:任何用户都对/sys/module中出现的该参数具有读写权限。
S_IWUSR:允许root用户修改/sys/module中出现的参数。
例子:声明成模块参数。
int a=3; char * st; module_param(a, int, S_IRUGO); module_param(st, charp, S_IRUGO);
param.c:
#include <linux/init.h> #include <linux/module.h> static char * name="David"; static int age=30; module_param(age, int, S_IRUGO); module_param(name, charp, S_IRUGO); static int hello_init(void){ printk(KERN_EMERG"Name:%s\n", name); printk(KERN_EMERGE"Age:%d\n", age); return 0; } static void hello_exit(void){ printk(KERN_INFO"Goodbye,wold\n"); } module_init(hello_init); module_exit(hello_exit);
编译出param.ko模块,
不传值加载: insmod param.ko
输出 Name:David Age: 12
传值加载: insmod param.ko age=12
输出 Age:12
内核符号导出:
/proc/kallsyms记录了内核中所有导出的符号的名字与地址。为什么要导出内核符号?
一个内核模块实现了一些函数,为了让其他模块使用,必须将符号导出(函数、变量)
使用EXPORT_SYMBOL导出:
比如在.c文件中定义了两个函数:
EXPORT_SYMBOL(add_integar); EXPORT_SYMBOL(sub_integer);
常见问题:版本不匹配
内核模块的版本由其依赖的内核代码版本所决定,在加载内核模块事,insmod会将内核模块版本与当前正在运行的内核版本比较,不一致会报错。
解决方法:
1. 使用modprob –force -modversion强行插入
2. 确保编译内核模块时,所依赖内核代码版本与正在运行的内核版本相同。(可通过uname -r查看当前内核版本)
内核程序 VS 应用程序
应用程序是从头(main)到尾执行,执行结束后从内存中消失。内核模块先在内核中注册自己以便服务于将来的某个请求,然后它的初始化函数结束,此时模块仍然存在于内核中,直到卸载函数被调用。
printk函数有优先级
相关文章推荐
- Linux之内存泄露的检测工具
- Linux环境PHP7.0安装
- 在centOS上安装nodejs记录
- Linux之ARM/Linux与单片机开发差异
- Linux之PPP配置
- linux用service命令管理自定义服务脚本
- URL之Linux相关资源整理
- linux处理邮件编码
- centos 6.5源码编译安装subversion 1.8.10
- linux-结构化成行成列-小文件循环合并成大文件--方便上传到hadoop
- linux应用编程笔记(16)多线程编程函数全解析
- linux处理邮件编码
- linux字符设备驱动解析
- linux监控程序-程序自动重启方法
- Linux VI常用命令
- Linux下高并发socket最大连接数所受的限制问题
- centos6.5 FTP服务器的搭建
- Linux centOS下安装配置openfire
- linux 备份策略
- linux 系统登录日志