先看一个具体的例子:
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模块对应的变量。稍后将会知道,内核模块加载器件对模块参数的初始化过程发生在对模块初始化函数init的调用之前。所以在demo_init函数被调用时,已经可以得到从命令行传递过来的参数。
分析:
这一切是如何发生的呢,我们从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操作的调用里面被调用的。
load module 如何创建参数节点?
在模块加载时,如果有传入参数,在执行load_module时,会主动调用module_param_sysfs_setup创建参数节点文件:
module_param_sysfs_setup函数内部,会根据参数个数,创建对应的文件:
可以通过/sys/module/$module_name/parameters/$param_name修改预设的参数值:
当然,能够修改预设模块参数是有条件的,就是参数文件节点有写如权限,比如,如果将参数文件权限0664修改为0400后,即使在ROOT权限下,参数预设值也不可以修改。
其实现逻辑是,可写标志(S_IWUSR,0200)会控制store callback的设置,如果不可写,store回调将为空:
set函数是在store回调用中调用的,store为空,自然不会设置。
回调路径为:
总结
所以我们可以得出结论,无论是模块builtin编译,还是以KO模块的形式集成到系统,module_parm宏定义的参数都会发生作用,也就是说,我们可以通过 "paramxxx=xxx"的形式,在系统初始化参数,或者在模块安装的时候,向模块传递参数。