美文网首页
向内核模块传参以及共享内核模块中的符号

向内核模块传参以及共享内核模块中的符号

作者: 付凯强 | 来源:发表于2022-06-26 20:22 被阅读0次

向内核模块传参

内核模块作为可扩展的动态模块,有时候需要根据不同的场景给内核模块传递不同的参数,让内核模块更加具有灵活性.在Linux内核中提供了宏module_param(name, type, perm)来实现模块的参数传递.

#define module_param(name, type, perm)           \
   module_param_named(name, name, type, perm)
  1. 宏module_param(name, type, perm)用来实现向内核模块进行参数传递.一共有3个参数: name 表示参数名, type 表示参数类型, perm 参数在 sysfs 中的可见性.
  2. type 参数类型可以是byte, short, ushort, int, uint, long, ulong, charp, bool, invbool(不熟悉).
  3. perm 的值可以是 0 ,表示不会出现在 sysfs 文件系统中.也可以是 S_IRUGO(0444),表示可以被所有人读取,但是不能修改.也可以是 S_IRUGO|S_IWUSR(0644),表示可以让拥有root权限的用户修改参数.
  4. 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");
}
...
  1. 增加了变量debug,作为传入的参数值的接收者,同时定义了其在文件系统中的可见性为0644.
  2. 通过变量debug,定义了宏dprintk(args...),如果debug的值为0则不输出日志,如果为1则输出日志.默认不打印.
  3. 用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)
  1. EXPORT_SYMBOL 将函数以符号的方式导出给内核中的其他模块使用.
  2. 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

到此,我们已经可以在一个模块中去使用另外一个模块的变量和函数.

对以上两个小节内容感兴趣的小伙伴,现在就把代码敲起来吧.

补充: 模块编译时,如何寻找使用的符号?

  1. 在本模块符号表中,寻找符号(函数或变量实现)
  2. 在内核全局符号表中寻找
  3. 在模块目录下的Module.symvers文件中寻找

参考

奔跑吧内核 入门篇第2版 第五章内核模块

相关文章

网友评论

      本文标题:向内核模块传参以及共享内核模块中的符号

      本文链接:https://www.haomeiwen.com/subject/uumgvrtx.html