Linux内核模块通信
Linux内核模块通信
Linux模块间通讯方法非常的多,最便捷的方法莫过于函数符号导出,然后直接调用。
然而在linux2.6.26以后的内核中模块的符号导出经常会出现问题,一个模块中的导出符号不能被另外一个模块进行调用。这个使得处理有依赖关系的模块非常的头疼。
符号导出函数
EXPORT_SYMBOL()
:括号中定义的函数对全部内核代码公开EXPORT_SYMBOL_GPL()
:和EXPORT_SYMBOL
类似,但范围只适合GPL许可的模块进行调用
使用方法
加入B中调用A中导出函数:
- 在模块A中c文件或者头文件中使用
EXPORT_SYMBOL(xxxx)
导出函数(有些需要添加编译选项-DEXPORT_SYMTAB
) - 在模块B中用关键字
extern
申明函数,申明以后能够直接使用导出的函数
在导出函数以后,可以使用
cat /proc/kallsyms
来查看所有的导出符号,其中属性为T
的标识是不能被调用的,所以如果导出符号是T
类型,那么无法直接被其他模块使用
无法导出问题解决
方法一: 在A模块编译好后会生成符号表文件Module.symvers
,里面有函数地址和函数名对应关系,把该文件拷贝到需要调用的B的源代码下,替换B的该文件。然后重新编译B模块,即可让B调用A的函数,以后加载模块顺序也必须先A后B,卸载相反。
方法二:在Makefile中加入KBUILD_EXTRA_SYMBOLS
:
1 | # 假设Module B使用了Module A中export的函数,因此在Module B的Makefile中加上: |
- 赋值时必须使用+=,而不能使用=及:=
KBUILD_EXTRA_SYMBOLS
必须使用export
处理一下
方法三: 将两个模块放在一个目录下,进行编译。其实和方法一类似。
这样就能够成功的实现两个模块之间的函数调用,比如KVM如果需要和驱动模块相互调用,就能使用这个方法。如果是两个模块之间需要相互调用,可以让驱动模块函数导出,KVM模块将函数指针当做回调函数传给驱动,是想双方的函数调用通讯。
记得这个问题出来以后一直没有很好的解决,而且这个问题也许是一种系统的需要,而且Linux开发小组也没有打算去处理这个问题。
实例
模块A:
1 |
|
对应的Makefile:
1 | KERNEL_VER = $(shell uname -r) |
模块B:
1 |
|
对应的Makefile:
1 | KERNEL_VER = $(shell uname -r) |
向模块传递参数
对于如何向模块传递参数,Linux kernel 提供了一个简单的框架。其允许驱动程序声明参数,并且用户在系统启动或模块装载时为参数指定相应值,在驱动程序里,参数的用法如同全局变量。
使用下面的宏时需要包含头文件<linux/moduleparam.h>
单一参数
通过宏module_param()
定义一个模块参数:
1 | module_param(name, type, perm); |
-
name:既是用户看到的参数名,又是模块内接受参数的变量
-
type:参数的数据类型,是下列之一:byte, short, ushort, int, uint, long, ulong, charp(字符指针类型,内存为用户提供的字符串分配,即char *), bool, invbool(颠倒了值的bool类型)
-
perm:指定了在sysfs中相应文件的访问权限。访问权限与linux文件访问权限相同的方式管理,如0644,或使用stat.h中的宏如S_IRUGO表示。0表示完全关闭在sysfs中相对应的项。
-
必须写在模块源文件的开头部分(int_var是全局的)
-
该宏不会声明变量,因此在使用宏之前,必须声明变量,典型地用法如下:
1
2static unsigned int int_var = 0;
module_param(int_var, uint, S_IRUGO);
通过module_param_named()
可以使模块源文件内部的变量名与外部的参数名有不同的名字:
1 | module_param_named(name, variable, type, perm); |
-
name:外部可见的参数名
-
variable:源文件内部的全局变量名
-
module_param
本质上是通过module_param_named
实现的,只不过name与variable相同 -
典型用法:
1
2static unsigned int max_test = 9;
module_param_name(maximum_line_test, max_test, int, 0);
字符串处理
通常使用charp
类型定义是字符串的模块参数。内核会复制用户提供的字符串到内存,并且相对应的变量指向这个字符串。
1 | static char *name; |
另一种方法是通过宏module_param_string()
让内核把字符串直接复制到程序中的字符数组内:
1 | module_param_string(name, string, len, perm); |
-
name:外部的参数名
-
string:内部的变量名
-
len:以string命名的buffer大小(可以小于buffer的大小,但是没有意义)
-
perm:sysfs的访问权限(perm为零表示完全关闭相对应的sysfs项)
-
典型例子:
1
2static char species[BUF_LEN];
module_param_string(specifies, species, BUF_LEN, 0);
多参数
如果需要传递多个参数可以通过宏module_param_array()
实现:
1 | module_param_array(name, type, nump, perm); |
-
name:数组,既是外部模块的参数名又是程序内部的变量名(必须静态分配)
-
type:数据类型
-
nump:指针,指向一个整数,其值表示有多少个参数存放在数组name中
-
perm:sysfs的访问权限
-
典型例子:
1
2
3static int finsh[MAX_FISH];
static int nr_fish;
module_param_array(finsh, int, &nr_fish, 0444); //最终传递数组元素个数存在nr_fish中
通过宏module_param_array_named()
使得内部的数组名与外部的参数名有不同的名字:
1 | module_param_array_named(name, array, type, nump, perm); |
参数说明
通过宏MODULE_PARM_DESC()
可以对参数进行说明:
1 | static unsigned short size = 1; |
其他说明
module_param()
和module_param_array()
的作用就是让那些全局变量对 insmod 可见,使模块装载时可重新赋值。module_param_array()
宏的第三个参数用来记录用户 insmod 时提供的给这个数组的元素个数,NULL 表示不关心用户提供的个数module_param()
和module_param_array()
最后一个参数权限值不能包含让普通用户也有写权限,否则编译报错。这点可参考linux/moduleparam.h
中__module_param_call()
宏的定义- 字符串数组中的字符串似乎不能包含逗号,否则一个字符串会被解析成两个
实例
1 |
|
运行:
1 | insmod ./hello.ko myint=100 mystring="abc" myintary=-1,-2 mystrary="a","b" |
dmesg输出:
1 | myint is 100 |