1.内核模块传参

内核模块作为一个可拓展的动态模块,为 Linux 内核提供了灵活性,但是有时我们需要根据不同的应用场景给内核模块传递不同的参数,例如在程序中开启调试模式、设置详细输出模式以及制定与具体模块相关的选项,为了满足这种需求,内核允许对内核模块指定参数,而这些参数可以在装载内核模块时改变。

Linux内核提供一个宏module_param来实现模块的参数传递,module_param宏定义在include/linux/moduleparam.h文件中。module_param宏的定义形式如下:

#definemodule_param(name, type, perm)

module_param需要三个参数:

name:变量的名称;

type:变量的类型,目前内核支持的类型有 byte, short, ushort, int, uint, long, ulong, charp, bool,invbool。其中 charp 表示的是字符指针, bool 是布尔类型,其值只能为 0 或者是 1; invbool 是反布尔类型,其值也是只能取 0 或者是 1,但是 true 值表示 0, false 表示 1。变量是 char 类型时,传参只能是 byte,char * 时只能是 charp;

perm:sysfs入口项的访问许可掩码,可选的值:S_IRUSR,S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH,S_IXOTH,S_IRUGO,S_IWUGO(这些值可以通过或的方式进行组合,比如S_IRUSR | S_IWUSR表示用户拥有读写权限)。对于这些宏的定义可以参考文件:include/linux/stat.h。

上述文件权限唯独没有关于可执行权限的设置,请注意,该文件不允许它具有可执行权限。如果强行给该参数赋予表示可执行权限的参数值 S_IXUGO,那么最终生成的内核模块在加载时会提示错误。

这里列举一个简单的内核模块参数传递的例子,module_param.c文件内容:

  1. /**
  2. * @file module_param.c
  3. * @author Ailson Jack (jackailson@foxmail.com)
  4. * @brief
  5. * @version 1.0
  6. * @date 2021-05-22
  7. *
  8. * @copyright Copyright (c) 2021
  9. *
  10. * @note blog:www.only2fire.com
  11. *
  12. */#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>staticintint_type =0;
  13. module_param(int_type,int,0);staticboolbool_type =0;
  14. module_param(bool_type,bool,0644);staticcharchar_type =0;
  15. module_param(char_type, byte,0);staticchar*str_type =0;
  16. module_param(str_type, charp, S_IRUGO | S_IWUSR);/* 内核模块加载函数 */staticint__initmodule_param_init(void){
  17. printk(KERN_ALERT"Module Param init!\r\n");
  18. printk(KERN_ALERT"int_type=%d\r\n", int_type);
  19. printk(KERN_ALERT"bool_type=%d\r\n", bool_type);
  20. printk(KERN_ALERT"char_type=%d\r\n", char_type);
  21. printk(KERN_ALERT"str_type=%s\r\n", str_type);return0;
  22. }/* 内核模块卸载函数 */staticvoid__exitmodule_param_exit(void){
  23. printk(KERN_ALERT"Module Param exit!\r\n");
  24. }

  25. module_init(module_param_init);
  26. module_exit(module_param_exit);


MODULE_LICENSE("GPL v2");//表示模块代码接受的软件许可协议MODULE_AUTHOR("Ailson Jack");//描述模块的作者信息MODULE_DESCRIPTION("module param");//对模块的简单介绍MODULE_ALIAS("module_param");//给模块设置一个别名

Makefile文件内容:

# 指向编译出来的 linux 内核具体路径KERNEL_DIR = ../../kernel/ebf-buster-linux/build_image/build# 定义变量,并且导出变量给子 Makefile 使用ARCH = arm
CROSS_COMPILE = arm-linux-gnueabihf-exportARCH CROSS_COMPILE# obj-m := <模块名>.o: 定义要生成的模块obj-m := module_param.o# 选项 "-C":让 make 工具跳转到 linux 内核目录下读取顶层 Makefile# "M=" 表示内核模块源码目录# $(CURDIR): Makefile 默认变量,值为当前目录所在路径# make modules: 执行 Linux 顶层 Makefile 的伪目标,它实现内核模块的源码读取并编译为.ko文件all:$(MAKE)-C$(KERNEL_DIR)M=$(CURDIR)modules.PHONY:clean copyclean:$(MAKE)-C$(KERNEL_DIR)M=$(CURDIR)cleancopy:cp *.ko /home/ailsonjack/share/nfs/temp

编译module_param内核模块,将生成的module_param.ko文件通过nfs或者scp拷贝到开发板,然后在开发板中执行命令:

sudoinsmod module_param.ko int_type=12bool_type=1char_type=201str_type="hello!"

image.png


我们定义的四个模块参数,会在'/sys/module/模块名/parameters'下存在以模块参数为名的文件。由于 int_type 和 char_type 的权限是 0,所以我们没有权限查看该参数。

image.png


2.符号共享

符号共享是指内核模块能够使用其他内核模块导出的符号,或者内核模块将自己模块内的符号导出给其他内核模块使用。这里的符号指的是内核模块中导出的函数或者变量,在加载模块时被记录在公共内核符号表中,以供其他模块调用。这个机制,允许我们使用分层的思想解决一些复杂的模块设计。我们在编写一个驱动的时候,可以把驱动按照功能分成几个内核模块,借助符号共享去实现模块与模块之间的接口调用,变量共享。

通常情况下我们无需导出任何符号,但是如果其他模块想要从我们这个模块中获取某些符号的时候,就可以考虑导出符号为其提供服务,这被称为模块层叠技术。例如 msdos 文件系统依赖于由 fat 模块导出的符号;USB 输入设备模块层叠在 usbcore 和 input 模块之上。也就是我们可以将模块分为多个层,通过简化每一层来实现复杂的项目。

如果一个模块需要向其他模块导出符号,则应该使用下面的宏:

EXPORT_SYMBOL(name);EXPORT_SYMBOL_GPL(name);

符号必须在模块文件的全局部分导出,不能在函数中使用,EXPORT_SYMBOL_GPL使得导出的模块只能被 GPL 许可的模块使用。

这里使用内核模块传参小节的module_param.c文件为基础进行修改,作为一个导出内核模块参数的内核模块,module_param.c文件的内容如下:

  1. /**
  2. * @file module_param.c
  3. * @author Ailson Jack (jackailson@foxmail.com)
  4. * @brief
  5. * @version 1.0
  6. * @date 2021-05-22
  7. *
  8. * @copyright Copyright (c) 2021
  9. *
  10. * @note blog:www.only2fire.com
  11. *
  12. */#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>staticintint_type =0;
  13. module_param(int_type,int,0);
  14. EXPORT_SYMBOL(int_type);//导出 int_type 作为共享符号staticboolbool_type =0;
  15. module_param(bool_type,bool,0644);staticcharchar_type =0;
  16. module_param(char_type, byte,0);staticchar*str_type =0;
  17. module_param(str_type, charp, S_IRUGO | S_IWUSR);intdata_inc(intval){return(val +1);
  18. }
  19. EXPORT_SYMBOL(data_inc);//导出 data_inc 作为共享符号intdata_dec(intval){return(val -1);
  20. }
  21. EXPORT_SYMBOL(data_dec);//导出 data_dec 作为共享符号/* 内核模块加载函数 */staticint__initmodule_param_init(void){
  22. printk(KERN_ALERT"Module Param init!\r\n");
  23. printk(KERN_ALERT"int_type=%d\r\n", int_type);
  24. printk(KERN_ALERT"bool_type=%d\r\n", bool_type);
  25. printk(KERN_ALERT"char_type=%d\r\n", char_type);
  26. printk(KERN_ALERT"str_type=%s\r\n", str_type);return0;
  27. }/* 内核模块卸载函数 */staticvoid__exitmodule_param_exit(void){
  28. printk(KERN_ALERT"Module Param exit!\r\n");
  29. }

  30. module_init(module_param_init);
  31. module_exit(module_param_exit);



MODULE_LICENSE("GPL v2");//表示模块代码接受的软件许可协议MODULE_AUTHOR("Ailson Jack");//描述模块的作者信息MODULE_DESCRIPTION("module param");//对模块的简单介绍MODULE_ALIAS("module_param");//给模块设置一个别名

module_param.h文件内容:

  1. /**
  2. * @file module_param.h
  3. * @author Ailson Jack (jackailson@foxmail.com)
  4. * @brief
  5. * @version 1.0
  6. * @date 2021-05-23
  7. *
  8. * @copyright Copyright (c) 2021
  9. *
  10. * @note blog:www.only2fire.com
  11. *
  12. */#ifndef__MODULE_PARAM_H__#define__MODULE_PARAM_H__#ifdef__cplusplusextern"C"{#endif/* __cplusplus */// 下面定义的是 module_param 内核模块导出的变量和函数externintint_type;intdata_inc(intval);intdata_dec(intval);#ifdef__cplusplus}#endif/* __cplusplus */#endif/* __MODULE_PARAM_H__ */

  13. symbol_share.c文件内容:

  14. /**
  15. * @file symbol_share.c
  16. * @author Ailson Jack (jackailson@foxmail.com)
  17. * @brief
  18. * @version 1.0
  19. * @date 2021-05-23
  20. *
  21. * @copyright Copyright (c) 2021
  22. *
  23. * @note blog:www.only2fire.com
  24. *
  25. */#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>#include"module_param.h"/* 内核模块加载函数 */staticint__initsymbol_share_init(void){
  26. printk(KERN_ALERT"Symbol Share init!\r\n");
  27. printk(KERN_ALERT"int_type=%d\r\n", int_type);
  28. printk(KERN_ALERT"data_inc(int_type):%d\r\n", data_inc(int_type));
  29. printk(KERN_ALERT"data_dec(int_type):%d\r\n", data_dec(int_type));return0;
  30. }/* 内核模块卸载函数 */staticvoid__exitsymbol_share_exit(void){
  31. printk(KERN_ALERT"Symbol Share exit!\r\n");
  32. }

  33. module_init(symbol_share_init);
  34. module_exit(symbol_share_exit);


MODULE_LICENSE("GPL v2");//表示模块代码接受的软件许可协议MODULE_AUTHOR("Ailson Jack");//描述模块的作者信息MODULE_DESCRIPTION("symbol share");//对模块的简单介绍MODULE_ALIAS("symbol_share");//给模块设置一个别名

Makefile文件内容:

# 指向编译出来的 linux 内核具体路径KERNEL_DIR = ../../kernel/ebf-buster-linux/build_image/build# 定义变量,并且导出变量给子 Makefile 使用ARCH = arm
CROSS_COMPILE = arm-linux-gnueabihf-exportARCH CROSS_COMPILE# obj-m := <模块名>.o: 定义要生成的模块obj-m := module_param.o symbol_share.o# 选项 "-C":让 make 工具跳转到 linux 内核目录下读取顶层 Makefile# "M=" 表示内核模块源码目录# $(CURDIR): Makefile 默认变量,值为当前目录所在路径# make modules: 执行 Linux 顶层 Makefile 的伪目标,它实现内核模块的源码读取并编译为.ko文件all:$(MAKE)-C$(KERNEL_DIR)M=$(CURDIR)modules.PHONY:clean copyclean:$(MAKE)-C$(KERNEL_DIR)M=$(CURDIR)cleancopy:cp *.ko /home/ailsonjack/share/nfs/temp

编译module_param内核模块和symbol_share内核模块,将生成的module_param.ko和symbol_share.ko文件通过nfs或者scp拷贝到开发板,然后在开发板中执行命令:

sudoinsmod module_param.ko int_type=12bool_type=1char_type=201str_type="hello!"sudo insmod symbol_share.ko

注意,要先加载module_param内核模块,再加载symbol_share内核模块,因为symbol_share内核模块会依赖module_param内核模块导出的符号,如果先加载symbol_share内核模块,symbol_share内核模块将会加载失败。

image.png


查看module_param内核模块导出的符号:

cat /proc/kallsyms |grepint_type
cat /proc/kallsyms |grepdata_inc
cat /proc/kallsyms |grepdata_dec

image.png