Linux内核模块通信

Linux模块间通讯方法非常的多,最便捷的方法莫过于函数符号导出,然后直接调用。

然而在linux2.6.26以后的内核中模块的符号导出经常会出现问题,一个模块中的导出符号不能被另外一个模块进行调用。这个使得处理有依赖关系的模块非常的头疼。

符号导出函数

  1. EXPORT_SYMBOL():括号中定义的函数对全部内核代码公开
  2. EXPORT_SYMBOL_GPL():和EXPORT_SYMBOL类似,但范围只适合GPL许可的模块进行调用

使用方法

加入B中调用A中导出函数:

  1. 在模块A中c文件或者头文件中使用EXPORT_SYMBOL(xxxx)导出函数(有些需要添加编译选项-DEXPORT_SYMTAB
  2. 在模块B中用关键字extern申明函数,申明以后能够直接使用导出的函数

在导出函数以后,可以使用cat /proc/kallsyms来查看所有的导出符号,其中属性为T的标识是不能被调用的,所以如果导出符号是T类型,那么无法直接被其他模块使用

无法导出问题解决

方法一: 在A模块编译好后会生成符号表文件Module.symvers,里面有函数地址和函数名对应关系,把该文件拷贝到需要调用的B的源代码下,替换B的该文件。然后重新编译B模块,即可让B调用A的函数,以后加载模块顺序也必须先A后B,卸载相反

方法二:在Makefile中加入KBUILD_EXTRA_SYMBOLS

1
2
3
# 假设Module B使用了Module A中export的函数,因此在Module B的Makefile中加上:
KBUILD_EXTRA_SYMBOLS += /path/to/Module A/Module.symvers
export KBUILD_EXTRA_SYMBOLS
  • 赋值时必须使用+=,而不能使用=及:=
  • KBUILD_EXTRA_SYMBOLS必须使用export处理一下

方法三: 将两个模块放在一个目录下,进行编译。其实和方法一类似。

这样就能够成功的实现两个模块之间的函数调用,比如KVM如果需要和驱动模块相互调用,就能使用这个方法。如果是两个模块之间需要相互调用,可以让驱动模块函数导出,KVM模块将函数指针当做回调函数传给驱动,是想双方的函数调用通讯。

记得这个问题出来以后一直没有很好的解决,而且这个问题也许是一种系统的需要,而且Linux开发小组也没有打算去处理这个问题。

实例

模块A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>

int calculate(int a,int b)
{
while(a != b)
{
if(a>b) a = a - b ;
else b = b - a ;
}
printk(KERN_ALERT "输入的两个参数的最大公约数为:%d",a);
return a;
}
EXPORT_SYMBOL(calculate);

static int __init init(void)
{
printk(KERN_ALERT "hello world! this is ModuleA.c");
return 0;
}

static void __exit exit(void)
{
printk(KERN_ALERT "ModuleA.ko exit !");
return ;
}

module_init(init);
module_exit(exit);
MODULE_LICENSE("GPL");

对应的Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
KERNEL_VER = $(shell uname -r)
obj-m += ModuleA.o
EXTRA_CFLAGS = -g -o0
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) modules
sudo insmod ./ModuleA.ko
lsmod | grep "ModuleA"
cat /proc/kallsyms |grep "calculate"
clean:
make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) clean
sudo rmmod ModuleA

模块B:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/moduleparam.h>

int x,y;

module_param(x,int,0644);
module_param(y,int,0644);

extern int calculate(int a,int b);

static int __init init(void)
{
printk(KERN_ALERT "hello world! this is ModuleB.c");
printk(KERN_ALERT "\n%d,%d,%d\n",x,y,calculate(x,y));
return 0 ;
}

static void __exit exit(void)
{
printk(KERN_ALERT "ModuleB.ko exit !");
return ;
}

module_init(init);
module_exit(exit);
MODULE_LICENSE("GPL");

对应的Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
KERNEL_VER = $(shell uname -r)
obj-m += ModuleB.o
EXTRA_CFLAGS = -g -o0

KBUILD_EXTRA_SYMBOLS += /home/null/Code/test/1/Module.symvers #ModuleA中编译出来的
export KBUILD_EXTRA_SYMBOLS

build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) modules
sudo insmod ./ModuleB.ko x=9 y=69
lsmod | grep "ModuleB"
clean:
make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) clean
sudo rmmod ModuleB

向模块传递参数

对于如何向模块传递参数,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
    2
    static 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
    2
    static unsigned int max_test = 9;
    module_param_name(maximum_line_test, max_test, int, 0);

字符串处理

通常使用charp类型定义是字符串的模块参数。内核会复制用户提供的字符串到内存,并且相对应的变量指向这个字符串。

1
2
static char *name;
module_param(name, charp, 0);

另一种方法是通过宏module_param_string()让内核把字符串直接复制到程序中的字符数组内:

1
module_param_string(name, string, len, perm);
  • name:外部的参数名

  • string:内部的变量名

  • len:以string命名的buffer大小(可以小于buffer的大小,但是没有意义)

  • perm:sysfs的访问权限(perm为零表示完全关闭相对应的sysfs项)

  • 典型例子:

    1
    2
    static 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
    3
    static 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
2
3
static unsigned short size = 1;
module_param(size, ushort, 0644);
MODULE_PARM_DESC(size, “The size in inches of the fishing pole connected to this computer.” );

其他说明

  • module_param()module_param_array()的作用就是让那些全局变量对 insmod 可见,使模块装载时可重新赋值。
  • module_param_array()宏的第三个参数用来记录用户 insmod 时提供的给这个数组的元素个数,NULL 表示不关心用户提供的个数
  • module_param()module_param_array()最后一个参数权限值不能包含让普通用户也有写权限,否则编译报错。这点可参考linux/moduleparam.h__module_param_call()宏的定义
  • 字符串数组中的字符串似乎不能包含逗号,否则一个字符串会被解析成两个

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <linux/module.h>
#include <linux/moduleparam.h>    /* Optional, to include module_param() macros */
#include <linux/kernel.h>    /* Optional, to include prink() prototype */
#include <linux/init.h>        /* Optional, to include module_init() macros */
#include <linux/stat.h>        /* Optional, to include S_IRUSR ... */

static int myint = -99;
static char *mystring = "i'm hungry";

static int myintary[]= {1,2,3,4};
static char *mystrary[] = {"apple", "orange", "banana"};
static int nstrs = 3;

module_param(myint, int, S_IRUSR|S_IWUSR);
MODULE_PARM_DESC(myint, "A trial integer");

module_param(mystring, charp, 0);
module_param_array(myintary, int, NULL, 0444);
module_param_array(mystrary, charp, &nstrs, 0664);

static int __init hello_init(void)
{
    int i;

    printk(KERN_INFO "myint is %d/n", myint);
    printk(KERN_INFO "mystring is %s/n", mystring);

    printk(KERN_INFO "myintary are");
    for(i = 0; i < sizeof(myintary)/sizeof(int); i++)
        printk(" %d", myintary[i]);
    printk("/n");

    printk(KERN_INFO "mystrary are");
    for(i=0; i < nstrs; i++)
        printk(" %s", mystrary[i]);
    printk("/n");

    return 0;
}

static void __exit hello_exit(void)
{
}

module_init(hello_init);
module_exit(hello_exit);

运行:

1
insmod ./hello.ko myint=100 mystring="abc" myintary=-1,-2 mystrary="a","b"

dmesg输出:

1
2
3
4
myint is 100
mystring is abc
myintary are -1 -2 3 4
mystrary are a b