向内核模块传参
内核模块作为可扩展的动态模块,有时候需要根据不同的场景给内核模块传递不同的参数,让内核模块更加具有灵活性.在Linux内核中提供了宏module_param(name, type, perm)来实现模块的参数传递.
#define module_param(name, type, perm) \
module_param_named(name, name, type, perm)
- 宏module_param(name, type, perm)用来实现向内核模块进行参数传递.一共有3个参数: name 表示参数名, type 表示参数类型, perm 参数在 sysfs 中的可见性.
- type 参数类型可以是byte, short, ushort, int, uint, long, ulong, charp, bool, invbool(不熟悉).
- perm 的值可以是 0 ,表示不会出现在 sysfs 文件系统中.也可以是 S_IRUGO(0444),表示可以被所有人读取,但是不能修改.也可以是 S_IRUGO|S_IWUSR(0644),表示可以让拥有root权限的用户修改参数.
- linux 内核也提供了宏 MODULE_PARM_DESC(_parm, desc) 来对参数进行描述.
#define MODULE_PARM_DESC(_parm, desc) \
__MODULE_INFO(parm, _parm, #_parm ":" desc)
下面我们对上一篇文章中的helloworld的示例进行更改:通过传递的参数来决定是否输出日志.
...
static int debug = 1;
module_param(debug,int,0644);
MODULE_PARM_DESC(debug,"enable debugging info");
#define dprintk(args...) \
if (debug) { \
printk(KERN_DEBUG args); \
}
static int __init hello_init(void)
{
dprintk("hello World enter\n");
return 0;
}
static void __exit hello_exit(void)
{
dprintk("hello World exit\n");
}
...
- 增加了变量debug,作为传入的参数值的接收者,同时定义了其在文件系统中的可见性为0644.
- 通过变量debug,定义了宏dprintk(args...),如果debug的值为0则不输出日志,如果为1则输出日志.默认不打印.
- 用dprintk替代了printk.
接下来 重新编译并卸载然后加载 helloworld模块.
$make
make -C /lib/modules/4.15.0-142-generic/build M=/media/fukaiqiang/Disk960/Codes/kernel/hello_world
make[1]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
CC [M] /media/fukaiqiang/Disk960/Codes/kernel/hello_world/helloworld.o
Building modules, stage 2.
MODPOST 1 modules
CC /media/fukaiqiang/Disk960/Codes/kernel/hello_world/helloworld.mod.o
LD [M] /media/fukaiqiang/Disk960/Codes/kernel/hello_world/helloworld.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
sudo rmmod helloworld.ko
sudo insmod helloworld.ko debug=1
[32570.897361] hello World enter
到此我们通过控制debug的参数值实现了关闭还是打开调试信息.看到这里,感兴趣的小伙伴,不妨立刻行动起来,去实现一个向内核模块传递参数的示例.
补充: 在/sys/module/helloworld/parameters/中可以看到新增的参数debug.
{ fukaiqiang@superman /media/fukaiqiang/Disk960/Codes/kernel/hello_world }
$cd /sys/module/helloworld/parameters/
{ fukaiqiang@superman /sys/module/helloworld/parameters }
$ls
debug
内核模块符号共享
一个模块如果想调用另外一个模块的接口函数,需要接口函数的提供者把这些接口函数进行共享,这里可以借助两个宏来实现.
#define EXPORT_SYMBOL(name) \
__EXPORT_SYMBOL(name, KSYM_FUNC(name),)
#define EXPORT_SYMBOL_GPL(name) \
__EXPORT_SYMBOL(name, KSYM_FUNC(name), _gpl)
- EXPORT_SYMBOL 将函数以符号的方式导出给内核中的其他模块使用.
- EXPORT_SYMBOL_GPL 只能包含GPL许可的模块,当然使用者也必须显式地声明模块为GPL.
现在修改上面的helloworld的内核模块,增加以下代码,共享一个变量和一个函数.
static int age = 18;
static void say_my_age(void)
{
dprintk("my age is %d .", age);
}
EXPORT_SYMBOL(age);
EXPORT_SYMBOL(say_my_age);
然后重新编译并卸载和加载helloworld内核模块.
这个时候会在helloworld的内核模块目录下的Module.symvers文件中发现共享的变量和函数.
0x00000000 age /media/fukaiqiang/Disk960/Codes/kernel/hello_world/helloworld EXPORT_SYMBOL
0x00000000 say_my_age /media/fukaiqiang/Disk960/Codes/kernel/hello_world/helloworld EXPORT_SYMBOL
接下来把Module.symvers文件复制到另外一起模块,我们起名字为hellobeijing.这个新模块是从helloworld修改而来,大同小异,在hellobeijing.c文件中增加以下代码:
extern int age;
extern void say_my_age(void);
并在hello_init函数中进行调用:
static int __init hello_init(void)
{
dprintk("global variable age = %d .",age);
say_my_age();
dprintk("hello beijing init\n");
return 0;
}
完整代码如下:
#include <linux/init.h>
#include <linux/module.h>
static int debug = 0;
module_param(debug,int,0644);
MODULE_PARM_DESC(debug,"enable debugging info");
#define dprintk(args...) \
if (debug) { \
printk(KERN_DEBUG args); \
}
extern int age;
extern void say_my_age(void);
static int __init hello_init(void)
{
dprintk("global variable age = %d .",age);
say_my_age();
dprintk("hello beijing init\n");
return 0;
}
module_init(hello_init);
static void __exit hello_exit(void)
{
dprintk("hello beijing exit\n");
}
module_exit(hello_exit);
MODULE_AUTHOR("fukaiqiang");//作者
MODULE_LICENSE("GPL v2");//模块许可证声明,一般用GPL v2
MODULE_DESCRIPTION("A simple hello beijing module");//模块描述
MODULE_ALIAS("hb"); //别名
然后进行编译并加载.
$make
make -C /lib/modules/4.15.0-142-generic/build M=/media/fukaiqiang/Disk960/Codes/kernel/hello_beijing
make[1]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
CC [M] /media/fukaiqiang/Disk960/Codes/kernel/hello_beijing/hellobeijing.o
Building modules, stage 2.
MODPOST 1 modules
CC /media/fukaiqiang/Disk960/Codes/kernel/hello_beijing/hellobeijing.mod.o
LD [M] /media/fukaiqiang/Disk960/Codes/kernel/hello_beijing/hellobeijing.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
$sudo insmod hellobeijing.ko debug=1
[sudo] fukaiqiang 的密码:
现在看下日志内容:
[ 3361.894465] global variable age = 18 .
[ 3361.894466] my age is 18 .
[ 3361.894467] hello beijing init
到此,我们已经可以在一个模块中去使用另外一个模块的变量和函数.
对以上两个小节内容感兴趣的小伙伴,现在就把代码敲起来吧.
补充: 模块编译时,如何寻找使用的符号?
- 在本模块符号表中,寻找符号(函数或变量实现)
- 在内核全局符号表中寻找
- 在模块目录下的Module.symvers文件中寻找
参考
奔跑吧内核 入门篇第2版 第五章内核模块
网友评论