一、模块的概念
模块是一种向linux内核添加设备驱动程序、文件系统及其他组件的有效办法。无需重新编译新内核和重启系统就可以向内核加入功能。增加了linux的可扩展性。
优点:
将功能模块后,内核image就不会发生膨胀。
方便测试新的内核特性。
二、HelloWorld模块
文件hello.c:
#include <linux/module.h> /*定义了与module相关的一些宏和函数,如MODULE_LICENSE等*/
#include <linux/init.h> /*定义了module_init和module_exit宏*/
#include <linux/kernel.h> /*定义了printk函数*/
/*
* 初始化函数,当模块装载时被调用,如果成功装载则返回0,
* 否则返回非0
*/
static int hello_init(void)
{
printk(KERN_ALERT "Hello world\n");
return 0;
}
/*
* 退出函数,当模块被卸载时被调用,释放在初始化函数中申请的资源
*/
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye world\n");
}
module_init(hello_init);/*将hello_init函数注册为模块init函数*/
module_exit(hello_exit);/*将hello_eixt函数注册为模块exit函数*/
MODULE_LICENSE("GPL");/*模块所使用的许可协议*/
MODULE_AUTHOR("ice");/*模块的作者*/
三、编译模块
生成最终模块文件需要编写Makefile,如下:
文件Makefile:
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNELDIR ?= /lib/modules/`uname -r`/build
PWD := `pwd`
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
这个makefile被调用两次,在命令行执行make时,KERNELRELEASE未被设置,所以会执行else中语句。
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules会进入到内核代码树的目录并读取内核顶层的Makefile。
该Makefile会设置KERNELRELEASE,再次读取模块目录下的Makefile时,设置obj-m从而开始进行模块的编译工作。
-C : 在执行make之前先进入$(KERNELDIR)目录,即内核树的顶层目录,然后再执行make,此时make读取的makefile就是内核树顶层目录中的Makefile文件。
M=$(PWD) : 将M的参数值传递给内核树的顶层Makefile,即M的值为当前的模块源文件所在的目录。
modules : make的执行目标
obj-m := hello.o 表示最终生成模块hello.ko文件
如果源文件包含多个.c文件(file1.c, file2.c),obj-m应编写如下:
obj-m := hello.o
hello-objs := file1.o file2.o
四、装载和卸载模块
使用insmod和modprobe命令来装载模块。
命令如:insmod ./hello.ko
insmod装载模块流程:
1.insmod会使用系统调用函数sys_init_module
2.sys_init_module给模块分配分存
3.sys_init_module将模块复制到内存区域
4.调用模块的初始化函数
modprobe与insmod的区别在于modprobe会处理模块的依赖关系,将依赖的模块装载到内核中。
使用lsmod命令可以查看当前系统已经安装的模块信息列表,该命令读取/proc/modules文件。
使用rmmod和modprobe -r命令来卸载已安装模块。
命令如:rmmod hello
五、模块参数
通过模块参数,可以在系统启动或模块装载时指定或修改参数的值,类似于命令参数,不必将参数的值写死在代码中,增强了模块的灵活性,同时用户程序可以通过模块参数读取模块的数据。
外部参数:装载模块时,在命令中使用名称,如insmod ./hello.ko who=jack
内部变量: 模块代码里面使用的变量名称
module_param(name, type, perm);
name: 外部参数和内部变量同为name。
type: 参数或变量的数据类型,值为:
byte
short
ushort
int
uint
long
ulong
charp: 字符指针,在传递数据时,由内核为其分配内存,并将字符指针指向内存的首地址
bool
invbool
perm: 模块参数在sysfs文件系统下对应的文件访问权限,一般在/sys/module/生成,如果为0,不会生成对应文件。
其值可以为S_IRUGO | S_IWUSR或0644等。
module_param_named(name, variable, type, perm);
将外部参数name与内部变量variable关联起来,这样外部参数名称就不必要与内部变量同名,可以隐藏内部变量的名称。
module_param_string(name, string, len, perm);
字符串形式的模块参数,等价于module_param(name, charp, perm)。
module_param_array(name, type, nump, perm);
数组形式的模块参数,命令行中参数值以逗号分隔。
type: 数组元素的数据类型。
nump: 指针类型,用于存放从外部参数传递过来的数组元素个数,当传递元素的个数超过了数组的大小时会报错。
module_param_array_named(name, array, type, nump, perm);
以上module_开头都是宏,这些模块参数宏定义在<linux/moduleparam.h>文件中。
MODULE_PARM_DESC(name, description)
描述参数,可以通过modinfo命令查看参数描述信息
六、模块符号表
模块可以导出自己的函数或变量供其它模块使用。这样提高了代码的复用性。
模块导出的函数信息存放在模块导出符号表中,该符号表可看作为内核的API,导出的函数必须非static。
EXPORT_SYMBOL(name)
EXPORT_SYMBOL_GPL(name)
EXPORT_SYMBOL_GPL导出的name只能被使用GPL许可协议的模块使用。
七、内核模块与应用程序
1.应用程序为完成某个任务,而模块一般服务将来某个请求。
2.模块退出时必须清理初始化时申请的资源,而应用程序不必要。
3.模块是以类似事件驱动的方式工作,而不是所有的应用程序都是事件驱动。
4.应用程序通过链接库使用外部函数,而模块只能通过导出符号表。
5.应用程序的错误是无害的,因为模块是运行在内核空间,所以模块的错误可能会影响整个系统。
八、综合示例
1.目录结构
|--Makefile
|--hello/
|--hello.c
|--hello.h
|--Makefile
|--other/
|--other.c
|--Makefile
2.源代码
2.1.顶层Makefile:
obj-y = hello/ other/
KERNELDIR ?= /lib/modules/`uname -r`/build
PWD := `pwd`
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
2.2.文件hello/hello.h
void hello_prt(void);
2.3.文件hello/hello.c:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include "hello.h"
/*charp*/
static char *chp;
module_param(chp, charp, 0600);
MODULE_PARM_DESC(chp, "[char pointer]");
/*string*/
static char str[100];
module_param_string(str, str, 100, S_IRUSR | S_IWUSR);
MODULE_PARM_DESC(str, "[string]");
/*array*/
static int len;
static int intarr[5];
module_param_array(intarr, int, &len, 0600);
MODULE_PARM_DESC(intarr, "[array of int]");
void hello_prt(void)
{
printk(KERN_ALERT "hello\n");
}
EXPORT_SYMBOL(hello_prt);
static int __init hello_init(void)
{
int i;
printk(KERN_ALERT "Hello world\n");
printk(KERN_ALERT "chp:%s\n", chp);
printk(KERN_ALERT "str:%s\n", str);
for (i = 0; i < len; i++) {
printk(KERN_ALERT "intarr[%d]=%d\n", i, intarr[i]);
}
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye world\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ice");
2.4.文件hello/Makefile:
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNELDIR=/lib/modules/`uname -r`/build
PWD := `pwd`
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *.mod.c *.ko
rm -rf Module.* .*cmd .tmp_versions
2.5.文件other/other.c:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include "../hello/hello.h"
static int other_init(void)
{
printk(KERN_ALERT "init other\n");
hello_prt();/*使用hello模块的导出函数*/
return 0;
}
static void other_exit(void)
{
printk(KERN_ALERT "exit other\n");
}
module_init(other_init);
module_exit(other_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ice");
2.6.文件other/Makefile:
ifneq ($(KERNELRELEASE),)
obj-m := other.o
else
KERNELDIR=/lib/modules/`uname -r`/build
PWD := `pwd`
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *.mod.c *.ko
rm -rf Module.* .*cmd .tmp_versions
3.测试
进入到顶层Makefile所在的目录,执行make
[root@ice-centos module]# make
make -C /lib/modules/2.6.18-371.1.2.el5.centos.plus/build M=/root/test/module modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-371.1.2.el5.centos.plus-i686'
CC [M] /root/test/module/hello/hello.o
CC [M] /root/test/module/other/other.o
Building modules, stage 2.
MODPOST
CC /root/test/module/hello/hello.mod.o
LD [M] /root/test/module/hello/hello.ko
CC /root/test/module/other/other.mod.o
LD [M] /root/test/module/other/other.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-371.1.2.el5.centos.plus-i686'
[root@ice-centos module]# ls
hello/ Makefile Module.markers Module.symvers other/
[root@ice-centos module]# modinfo hello/hello.ko other/other.ko ==> 查看模块文件信息
filename: hello/hello.ko
author: ice
license: GPL
srcversion: 5EBD27C704FD9027F9DB618
depends:
vermagic: 2.6.18-371.1.2.el5.centos.plus SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1
parm: chp:[char pointer] (charp)
parm: str:[string] (string)
parm: intarr:[array of int] (array of int)
filename: other/other.ko
author: ice
license: GPL
srcversion: 6417B31CEB48894C5F7EE55
depends: hello ==> 因为使用了hello模块的函数,所以依赖hello模块
vermagic: 2.6.18-371.1.2.el5.centos.plus SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1
[root@ice-centos module]# insmod hello/hello.ko chp=aa str=abcdefg intarr=11,22,33,44
[root@ice-centos module]# insmod other/other.ko
[root@ice-centos module]# tail -9 /var/log/messages
Oct 30 16:02:01 ice-centos kernel: Hello world
Oct 30 16:02:01 ice-centos kernel: chp:aa
Oct 30 16:02:01 ice-centos kernel: str:abcdefg
Oct 30 16:02:01 ice-centos kernel: intarr[0]=11
Oct 30 16:02:01 ice-centos kernel: intarr[1]=22
Oct 30 16:02:01 ice-centos kernel: intarr[2]=33
Oct 30 16:02:01 ice-centos kernel: intarr[3]=44
Oct 30 16:02:08 ice-centos kernel: init other
Oct 30 16:02:08 ice-centos kernel: hello ==> 调用hello_prt函数打印的信息
[root@ice-centos module]# ls /sys/module/hello/ ==> hello模块装载成功后,会在/sys/module目录下生成hello模块目录
parameters/ refcnt sections/ srcversion
[root@ice-centos module]# ll /sys/module/hello/parameters/ ==> hello模块的模块参数
total 0
-rw------- 1 root root 4096 Oct 30 16:05 chp
-rw------- 1 root root 4096 Oct 30 16:05 intarr
-rw------- 1 root root 4096 Oct 30 16:05 str
[root@ice-centos module]# rmmod hello ==> 由于hello模块被other模块引用,所以等other模块卸载后才能卸载hello模块
ERROR: Module hello is in use by other
[root@ice-centos module]# rmmod other
[root@ice-centos module]# rmmod hello
[root@ice-centos module]# tail -2 /var/log/messages
Oct 30 16:03:29 ice-centos kernel: exit other
Oct 30 16:03:30 ice-centos kernel: Goodbye world