先看一个具体的例子:
demo.c
#include <linux/module.h>
#include <linux/kernel.h>
static int param1;
static int param2;
module_param(param1, int, 0);
module_param(param2, int, 0);
static int demo_init(void)
{
printk("%s line %d, param1=%d, param2=%d.\n", __func__, __LINE__, param1, param2);
return 0;
}
static void demo_exit(void)
{
printk("%s line %d, param1=%d, param2=%d.\n", __func__, __LINE__, param1, param2);
return;
}
module_init(demo_init);
module_exit(demo_exit);
Makefile:
ifneq ($(KERNELRELEASE),)
obj-m:=demo.o
else
KERNELDIR:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko *.symvers *.mod *.order
endif
编译后:
验证:
依次执行
$ sudo insmod demo.ko parm1=7 param2=8
$ sudo rmmod demo.ko
可以看到通过命令行成功的将参数传递给了ko模块对应的变量。
分析:
这一切是如何发生的呢,我们从module_param开始倒查,试图找到整条调用链条,解释整个发生过程。
module_param->module_param_named
之后调用param_check_int进行参数检查
module_param_cb->__module_param_call
可以看到, 最终定义了一个static struct kernel_param 的对象结构体,并通过__section__ ("__param")塞到了__param段里,这里面有一个很重要的成员ops,它来源于module_parm_named定义。
param_ops_##type的定义如下,它的定义形式是通过预处理连接符定义的。
所有对参数的操作都是通过param_set/param_get_##type函数来进行的。
调用链:
前面知道了模块参数的定义是通过宏的精巧使用,构造了一个包含参数地址和参数操作函数的对象,并利用编译器的特性,将对象放到了特定的段中,但是我们还不知道这些定义好的段是如何发挥作用的。核心函数在parse_args中。
通过上面查找,我们看到了主要有两个调用链 调用了parse_arg函数,第一个是从start_kernel发起,在系统初始化阶段调用每个模块的参数初始化函数。
另一个则是在load_module函数中,它是在insmod操作的调用里面被调用的。
总结
所以我们可以得出结论,无论是模块builtin编译,还是以KO模块的形式集成到系统,module_parm宏定义的参数都会发生作用,也就是说,我们可以通过 "paramxxx=xxx"的形式,在系统初始化参数,或者在模块安装的时候,向模块传递参数。