模块参数
我们在运行用户空间的程序的时候可以接参数,驱动程序也可以接参数运行。参数在加载模块的时候指定,在模块代码中用module_param(参数名,参数类型,参数读写权限)来为模块定义参数。具体用法看代码如下:
#include <linux/init.h>
#include <linux/module.h>
static char* str = "hello";
static int value = 1;
static int hello_init(void) {
printk(KERN_EMERG "hello_init str: %s\n", str);
return 0;
}
static void hello_exit(void) {
printk(KERN_EMERG "hello_exit value: %d\n", value);
}
module_param(value, int, 0644);
module_param(str, charp, 0644);
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
上面的驱动在加载的时候就可以将a的值通过参数来指定,命令如下
sudo insmod hello.ko value=5 str="vegeta"
这样在加载模块时就会打印str=vegeta,卸载模块时打印value=5。
sysfs
sysfs是内核给资源创建的目录或者文件,模块会在/sys/module下创建一个跟模块名称一样的目录,目录在模块卸载的时候也会被一并删除掉。
例如上述hello模块加载之后,就会生成/sys/module/hello目录,在其中存放hello模块的资源信息。
上述模块传参中使用的str/value,会在/sys/module/hello/parameters目录下创建两个文件,跟参数名称一致
str和value中存放的的内容跟我们传参的内容是一致的
我所使用的版本,vim是可以修改value和str的内容的,并且是能生效的。例如当你修改了value之后,卸载模块打印的就是你修改之后的值了。
模块的文件格式
通过命令file hello.ko来查看模块是以何种格式存储在硬盘中。基本可以分为如下几个结构
名称 | 释义 |
---|---|
ELF Header | 描述整个文件的基本属性 |
.text | 代码段,存放文件的代码部分 |
.data | 数据段,存放已经初始化的数据等 |
.Section Table | 描述了ELF文件包含的所有段的信息 |
.symtab | 符号表,映射函数到真实内存地址的数据结构,就像一个字典,记录了在编译阶段无法确定地址的函数,在模块加载的时候由系统赋予真实的地址 |
模块通信(符号导出)
需要实现2个模块,目标是模块1(add_sub.ko)提供函数供模块2(hello.ko)来进行调用。
具体实现方式是将模块1的符号表提供给模块2,从而使得模块2能调用到模块1的函数和变量等。
模块(add_sub.ko)代码
/*模块1代码 add_sub.c,提供2个函数供其他模块调用*/
#include <linux/module.h>
#include <linux/init.h>
int add_integer(int value1, int value2) {
printk(KERN_EMERG "add_integer value1:%d value2:%d \n", value1, value2);
return value1 + value2;
}
int sub_integer(int value1, int value2) {
printk(KERN_EMERG "sub_integer value1:%d value2:%d \n", value1, value2);
return value1 - value2;
}
static int add_sub_init(void) {
return 0;
}
static void add_sub_exit(void) {
}
EXPORT_SYMBOL(add_integer);
EXPORT_SYMBOL(sub_integer);
module_init(add_sub_init);
module_exit(add_sub_exit);
MODULE_LICENSE("GPL");
模块1提供了加和减(add_integer/sub_integer)2个函数,这2个函数需要导出到内核符号表,才能被其他模块调用。EXPORT_SYMBOL()就是导出宏,就是定义函数能够被其他模块调用到。要注意的是内核函数很多,不能出现重名。但是编译器认为所有模块的函数都是私有的,不同模块之间出现重名不影响编译,所以如果只是模块自己内部使用的话不需要担心重名问题。
模块2(hello.ko)代码
/*模块2代码 hello.c,调用模块1的函数*/
/*模块2代码 hello.c,调用模块1的函数*/
#include <linux/module.h>
#include <linux/init.h>
static int value1 = 2;
static int value2 = 1;
extern int add_integer(int value1, int value2);
extern int sub_integer(int value1, int value2);
static int hello_init(void) {
int result = add_integer(value1, value2); /*调用模块1的函数*/
printk(KERN_EMERG "hello_init value1:%d value2:%d result: %d\n", value1, value2, result);
return 0;
}
static void hello_exit(void) {
int result = sub_integer(value1, value2); /*调用模块1的函数*/
printk(KERN_EMERG "hello_exit value1:%d value2:%d result: %d\n", value1, value2, result);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
模块2在加载的时候调用模块1的加法,卸载的时候调用模块2的减法。
模块通信的编译
add_sub模块的编译的Makefile与上一篇的hello world程序没有差异,直接可用。但是hello模块的Makefile需要修改一下,加上KBUILD_EXTRA_SYMBOLS
否则会报错找不到add_sub两个函数的实现。Makefile如下:
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
KBUILD_EXTRA_SYMBOLS += ./Modules.symvers
export KBUILD_EXTRA_SYMBOLS
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ codre .depend .*.cmd *.ko *.mod *.mod.c *.tmp_versions *.order *.symvers .cache.mk .tmp_versions
endif
编译的步骤为:
- 编译add_sub模块
- 将add_sub模块生成的Module.symvers复制到hello模块Makefile同级目录下
- 编译hello模块
模块通信测试
在加载的时候需要先加载add_sub.ko再加载hello.ko。在hello.ko加载的时候调用add_sub.ko中的加法,卸载的时候调用add_sub.ko中的减法。输出log如下
模块的加载过程解释
模块1(add_sub.ko)的加载过程:
1、内核为模块1分配空间,将模块的代码和数据装入分配的内存中
2、内核发现模块1的符号表中有函数可以导出,就将其内存地址记录在内核的符号表中
模块2(hello.ko)的加载过程:
1、内核为模块2分配空间,将模块的代码和数据装入分配的内存中
2、内核在模块2的符号表中发现一些未解析的函数。所以查找内核符号表找到对应的函数,将函数的地址填到模块2的符号表中,这个地址就是模块1中的函数地址。
通过上述的操作,就可以实现模块之间函数的调用