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文件内容:
- /**
- * @file module_param.c
- * @author Ailson Jack (jackailson@foxmail.com)
- * @brief
- * @version 1.0
- * @date 2021-05-22
- *
- * @copyright Copyright (c) 2021
- *
- * @note blog:www.only2fire.com
- *
- */#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>staticintint_type =0;
- module_param(int_type,int,0);staticboolbool_type =0;
- module_param(bool_type,bool,0644);staticcharchar_type =0;
- module_param(char_type, byte,0);staticchar*str_type =0;
- module_param(str_type, charp, S_IRUGO | S_IWUSR);/* 内核模块加载函数 */staticint__initmodule_param_init(void){
- printk(KERN_ALERT"Module Param init!\r\n");
- printk(KERN_ALERT"int_type=%d\r\n", int_type);
- printk(KERN_ALERT"bool_type=%d\r\n", bool_type);
- printk(KERN_ALERT"char_type=%d\r\n", char_type);
- printk(KERN_ALERT"str_type=%s\r\n", str_type);return0;
- }/* 内核模块卸载函数 */staticvoid__exitmodule_param_exit(void){
- printk(KERN_ALERT"Module Param exit!\r\n");
- }
- module_init(module_param_init);
- 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!"
我们定义的四个模块参数,会在'/sys/module/模块名/parameters'下存在以模块参数为名的文件。由于 int_type 和 char_type 的权限是 0,所以我们没有权限查看该参数。
2.符号共享
符号共享是指内核模块能够使用其他内核模块导出的符号,或者内核模块将自己模块内的符号导出给其他内核模块使用。这里的符号指的是内核模块中导出的函数或者变量,在加载模块时被记录在公共内核符号表中,以供其他模块调用。这个机制,允许我们使用分层的思想解决一些复杂的模块设计。我们在编写一个驱动的时候,可以把驱动按照功能分成几个内核模块,借助符号共享去实现模块与模块之间的接口调用,变量共享。
通常情况下我们无需导出任何符号,但是如果其他模块想要从我们这个模块中获取某些符号的时候,就可以考虑导出符号为其提供服务,这被称为模块层叠技术。例如 msdos 文件系统依赖于由 fat 模块导出的符号;USB 输入设备模块层叠在 usbcore 和 input 模块之上。也就是我们可以将模块分为多个层,通过简化每一层来实现复杂的项目。
如果一个模块需要向其他模块导出符号,则应该使用下面的宏:
EXPORT_SYMBOL(name);EXPORT_SYMBOL_GPL(name);
符号必须在模块文件的全局部分导出,不能在函数中使用,EXPORT_SYMBOL_GPL使得导出的模块只能被 GPL 许可的模块使用。
这里使用内核模块传参小节的module_param.c文件为基础进行修改,作为一个导出内核模块参数的内核模块,module_param.c文件的内容如下:
- /**
- * @file module_param.c
- * @author Ailson Jack (jackailson@foxmail.com)
- * @brief
- * @version 1.0
- * @date 2021-05-22
- *
- * @copyright Copyright (c) 2021
- *
- * @note blog:www.only2fire.com
- *
- */#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>staticintint_type =0;
- module_param(int_type,int,0);
- EXPORT_SYMBOL(int_type);//导出 int_type 作为共享符号staticboolbool_type =0;
- module_param(bool_type,bool,0644);staticcharchar_type =0;
- module_param(char_type, byte,0);staticchar*str_type =0;
- module_param(str_type, charp, S_IRUGO | S_IWUSR);intdata_inc(intval){return(val +1);
- }
- EXPORT_SYMBOL(data_inc);//导出 data_inc 作为共享符号intdata_dec(intval){return(val -1);
- }
- EXPORT_SYMBOL(data_dec);//导出 data_dec 作为共享符号/* 内核模块加载函数 */staticint__initmodule_param_init(void){
- printk(KERN_ALERT"Module Param init!\r\n");
- printk(KERN_ALERT"int_type=%d\r\n", int_type);
- printk(KERN_ALERT"bool_type=%d\r\n", bool_type);
- printk(KERN_ALERT"char_type=%d\r\n", char_type);
- printk(KERN_ALERT"str_type=%s\r\n", str_type);return0;
- }/* 内核模块卸载函数 */staticvoid__exitmodule_param_exit(void){
- printk(KERN_ALERT"Module Param exit!\r\n");
- }
- module_init(module_param_init);
- module_exit(module_param_exit);
MODULE_LICENSE("GPL v2");//表示模块代码接受的软件许可协议MODULE_AUTHOR("Ailson Jack");//描述模块的作者信息MODULE_DESCRIPTION("module param");//对模块的简单介绍MODULE_ALIAS("module_param");//给模块设置一个别名
module_param.h文件内容:
- /**
- * @file module_param.h
- * @author Ailson Jack (jackailson@foxmail.com)
- * @brief
- * @version 1.0
- * @date 2021-05-23
- *
- * @copyright Copyright (c) 2021
- *
- * @note blog:www.only2fire.com
- *
- */#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__ */
- symbol_share.c文件内容:
- /**
- * @file symbol_share.c
- * @author Ailson Jack (jackailson@foxmail.com)
- * @brief
- * @version 1.0
- * @date 2021-05-23
- *
- * @copyright Copyright (c) 2021
- *
- * @note blog:www.only2fire.com
- *
- */#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>#include"module_param.h"/* 内核模块加载函数 */staticint__initsymbol_share_init(void){
- printk(KERN_ALERT"Symbol Share init!\r\n");
- printk(KERN_ALERT"int_type=%d\r\n", int_type);
- printk(KERN_ALERT"data_inc(int_type):%d\r\n", data_inc(int_type));
- printk(KERN_ALERT"data_dec(int_type):%d\r\n", data_dec(int_type));return0;
- }/* 内核模块卸载函数 */staticvoid__exitsymbol_share_exit(void){
- printk(KERN_ALERT"Symbol Share exit!\r\n");
- }
- module_init(symbol_share_init);
- 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内核模块将会加载失败。
查看module_param内核模块导出的符号:
cat /proc/kallsyms |grepint_type
cat /proc/kallsyms |grepdata_inc
cat /proc/kallsyms |grepdata_dec