前言:现代操作系统的一个发展方向是采用微内核,与传统的单一内核相比,不仅简单易于实现,而且特别灵活,Linux内核虽然没有采用微内核,但是它却采用了微内核的思想,设计了可装载的模块。
一、模块的作用
Linux的内核模块机制允许开发者动态的向内核添加功能,我们常见的文件系统、驱动程序等都可以通过模块的方式添加到内核而无需对内核重新编译,这在很大程度上减少了操作的复杂度。模块机制使内核预编译时不必包含很多无关功能,把内核做到最精简,后期可以根据需要进行添加。
而针对驱动程序,因为涉及到具体的硬件,很难使通用的,且其中可能包含了各个厂商的私密接口,厂商几乎不会允许开发者把源代码公开,这就和linux的许可相悖,模块机制很好的解决了这个冲突,允许驱动程序后期进行添加而不合并到内核。OK,下面结合源代码讨论下模块机制的实现。
二、模块的组成
2.1 模块加载函数<linux/init.h>
内核模块加载函数一般以_ _init 标识声明:static int _ _init initialization_function(void)
将它放在内核入口:module_init(initialization_function);
2.2 模块卸载函数<linux/init.h>
内核模块卸载函数一般以_ _exit 标识声明,static void _ _exit cleanup_function(void);
将它放在模块出口,module_exit(cleanup_function);;
2.3模块许可证<linux/module.h>
MODULE_LICENSE("GPL"); //模块许可证
* The following license idents are currently accepted as indicating free
* software modules
*
* "GPL" [GNU Public License v2 or later]
* "GPL v2" [GNU Public License v2]
* "GPL and additional rights" [GNU Public License v2 rights and more]
* "Dual BSD/GPL" [GNU Public License v2
* or BSD license choice]
* "Dual MIT/GPL" [GNU Public License v2
* or MIT license choice]
* "Dual MPL/GPL" [GNU Public License v2
* or Mozilla license choice]
*
* The following other idents are available
*
* "Proprietary" [Non free products]
2.4 模块传参
1.相关函数
(1)**module_param(name, type, perm)**
功能:接收加载驱动时传递的参数
参数:
@name :变量名字
@type : 类型 byte, short, ushort, int, uint, long, ulong,charp,bool,invbool ------>注意这里没有char类型
@perm : 权限 0664 0x775
(2)**MODULE_PARM_DESC(_parm, desc)**
功能:对传递参数的描述
参数:
@_parm:变量名字
@desc :写字符串描述参数
(3) module_param_named(name, value, type, perm)
功能:对变量重命名,传递参数是重命名
参数:
@name : 外部传递参数的名字,重命名的名字
@value :重命名前的名字
@type :类型
@perm:权限
(4)module_param_array(name, type, nump, perm)
功能:接收用户传递的数组
参数:
@name :数组名字
@type :类型
@nump :用户传递数据的个数
@perm :权限
(5)module_param_string(name, string, len, perm)
功能:接收字符数组
参数:
@name :数组名
@string:数组名
@len :长度
@perm :权限
下面对上述函数实例验证:
demo.c
#include <linux/init.h>
#include <linux/module.h>
int a=10;
module_param(a,int,0644);
MODULE_PARM_DESC(a,"this is int val");
short b=14;
module_param(b,short,0771);
MODULE_PARM_DESC(b,"this is short val");
char *p = "hello kernel";
module_param(p,charp,0664);
MODULE_PARM_DESC(p,"this is string val");
int tt[5];
int num;
module_param_array(tt,int,&num,0664);
MODULE_PARM_DESC(tt,"this is array");
int pp=500;
module_param_named(ww,pp,int,0664);
MODULE_PARM_DESC(ww,"this is array");
char buf[10];
module_param_string(buf,buf,10,0664);
MODULE_PARM_DESC(buf,"this is char array");
static int __init demo_init(void)
{
int i;
printk("a = %d\n",a);
printk("b = %d\n",b);
printk("p = %s\n",p);
printk("pp = %d\n",pp);
printk("buf = %s\n",buf);
for(i=0; i<num; i++){
printk("tt[%d] = %d\n",i,tt[i]);
}
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
static void __exit demo_exit(void)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
makefile
KERNELDIR := /lib/modules/$(shell uname -r)/build/
#KERNELDIR := /home/linux/kernel/kernel-3.4.39/
PWD := $(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
make -C $(KERNELDIR) M=$(PWD) clean
obj-m := demo.o
编译及加载:
a. 我们先不加任何参数加载驱动:insmod demo.o,dmesg查看打印如下:
b. 我们加入参数: //传递字符串是不能有空格
dmesg查看打印如下:
这里我们没有对PP变量该名称,没找到怎么修改,大家可以留言。
当权限不为0的时候,模块加载后会生成变量对应的文件,文件内容即变量值
2.4 导出符号
EXPORT_SYMBOL_GPL(sym) //功能:导出符号表
int add(int a,int b)
{}
return (a+b);
}
导出:EXPORT_SYMBOL_GPL(add);
调用:extern int add(int a,int b);
将导出者的Module.symvers拷贝到调用者的目录下,接着执行make编译调用者
执行的顺序:
1.先执行导出者
2.在执行调用者
卸载的顺序:
1.先卸载调用者
2.在卸载导出者
2. 模块声明与描述
声明:MODULE_AUTHOR 作者
MODULE_DESCRIPTION 描述
MODULE_ VERSION 版本
MODULE_DEVICE_TABLE 设备表
MODULE_ALIAS 别名