Linux内核与驱动学习记录-内核模块传参和符号共享
<p class="artical_littlestyle1">1.内核模块传参</p><p style="text-indent: 2em;">内核模块作为一个可拓展的动态模块,为 Linux 内核提供了灵活性,但是有时我们需要根据不同的应用场景给内核模块传递不同的参数,例如在程序中开启调试模式、设置详细输出模式以及制定与具体模块相关的选项,为了满足这种需求,内核允许对内核模块指定参数,而这些参数可以在装载内核模块时改变。</p><p style="text-indent: 2em;">Linux内核提供一个宏module_param来实现模块的参数传递,module_param宏定义在include/linux/moduleparam.h文件中。module_param宏的定义形式如下:</p><pre class="brush:cpp;toolbar:false PrismJs">#define module_param(name, type, perm)</pre><p style="text-indent: 2em;">module_param需要三个参数:</p><p style="text-indent: 2em;">name:变量的名称;<br/></p><p style="text-indent: 2em;">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;</p><p style="text-indent: 2em;">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。<br/></p><p style="text-indent: 2em;">上述文件权限唯独没有关于可执行权限的设置,请注意,该文件不允许它具有可执行权限。如果强行给该参数赋予表示可执行权限的参数值 S_IXUGO,那么最终生成的内核模块在加载时会提示错误。</p><p style="text-indent: 2em;">这里列举一个简单的内核模块参数传递的例子,module_param.c文件内容:<br/></p><pre class="brush:cpp;toolbar:false PrismJs">/**
* @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>
static int int_type = 0;
module_param(int_type, int, 0);
static bool bool_type = 0;
module_param(bool_type, bool, 0644);
static char char_type = 0;
module_param(char_type, byte, 0);
static char *str_type = 0;
module_param(str_type, charp, S_IRUGO | S_IWUSR);
/* 内核模块加载函数 */
static int __init module_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);
return 0;
}
/* 内核模块卸载函数 */
static void __exit module_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"); //给模块设置一个别名</pre><p style="text-indent: 2em;">Makefile文件内容:</p><pre class="brush:cpp;toolbar:false PrismJs"># 指向编译出来的 linux 内核具体路径
KERNEL_DIR = ../../kernel/ebf-buster-linux/build_image/build
# 定义变量,并且导出变量给子 Makefile 使用
ARCH = arm
CROSS_COMPILE = arm-linux-gnueabihf-
export ARCH 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 copy
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
copy:
cp *.ko /home/ailsonjack/share/nfs/temp</pre><p style="text-indent: 2em;">编译module_param内核模块,将生成的module_param.ko文件通过nfs或者scp拷贝到开发板,然后在开发板中执行命令:</p><pre class="brush:plain;toolbar:false PrismJs">sudo insmod module_param.ko int_type=12 bool_type=1 char_type=201 str_type="hello!"</pre><p style="text-align:center"><img src="/uploads/AilsonJack/2021.05.23/200823892771688.png" onclick="preview_image('/uploads/AilsonJack/2021.05.23/200823892771688.png')"/></p><p style="text-indent: 2em;">我们定义的四个模块参数,会在'/sys/module/模块名/parameters'下存在以模块参数为名的文件。由于 int_type 和 char_type 的权限是 0,所以我们没有权限查看该参数。</p><p style="text-align:center"><img src="/uploads/AilsonJack/2021.05.23/200823893593251.png" onclick="preview_image('/uploads/AilsonJack/2021.05.23/200823893593251.png')"/></p><p class="artical_littlestyle2">2.符号共享</p><p style="text-indent: 2em;">符号共享是指内核模块能够使用其他内核模块导出的符号,或者内核模块将自己模块内的符号导出给其他内核模块使用。这里的符号指的是内核模块中导出的函数或者变量,在加载模块时被记录在公共内核符号表中,以供其他模块调用。这个机制,允许我们使用分层的思想解决一些复杂的模块设计。我们在编写一个驱动的时候,可以把驱动按照功能分成几个内核模块,借助符号共享去实现模块与模块之间的接口调用,变量共享。</p><p style="text-indent: 2em;">通常情况下我们无需导出任何符号,但是如果其他模块想要从我们这个模块中获取某些符号的时候,就可以考虑导出符号为其提供服务,这被称为模块层叠技术。例如 msdos 文件系统依赖于由 fat 模块导出的符号;USB 输入设备模块层叠在 usbcore 和 input 模块之上。也就是我们可以将模块分为多个层,通过简化每一层来实现复杂的项目。</p><p style="text-indent: 2em;">如果一个模块需要向其他模块导出符号,则应该使用下面的宏:</p><pre class="brush:cpp;toolbar:false PrismJs">EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);</pre><p style="text-indent: 2em;">符号必须在模块文件的全局部分导出,不能在函数中使用,EXPORT_SYMBOL_GPL使得导出的模块只能被 GPL 许可的模块使用。</p><p style="text-indent: 2em;">这里使用内核模块传参小节的module_param.c文件为基础进行修改,作为一个导出内核模块参数的内核模块,module_param.c文件的内容如下:</p><pre class="brush:cpp;toolbar:false PrismJs">/**
* @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>
static int int_type = 0;
module_param(int_type, int, 0);
EXPORT_SYMBOL(int_type); //导出 int_type 作为共享符号
static bool bool_type = 0;
module_param(bool_type, bool, 0644);
static char char_type = 0;
module_param(char_type, byte, 0);
static char *str_type = 0;
module_param(str_type, charp, S_IRUGO | S_IWUSR);
int data_inc(int val)
{
return (val + 1);
}
EXPORT_SYMBOL(data_inc); //导出 data_inc 作为共享符号
int data_dec(int val)
{
return (val - 1);
}
EXPORT_SYMBOL(data_dec); //导出 data_dec 作为共享符号
/* 内核模块加载函数 */
static int __init module_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);
return 0;
}
/* 内核模块卸载函数 */
static void __exit module_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"); //给模块设置一个别名</pre><p style="text-indent: 2em;">module_param.h文件内容:</p><pre class="brush:cpp;toolbar:false PrismJs">/**
* @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 __cplusplus
extern "C" {
#endif /* __cplusplus */
// 下面定义的是 module_param 内核模块导出的变量和函数
extern int int_type;
int data_inc(int val);
int data_dec(int val);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __MODULE_PARAM_H__ */</pre><p style="text-indent: 2em;">symbol_share.c文件内容:</p><pre class="brush:cpp;toolbar:false PrismJs">/**
* @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"
/* 内核模块加载函数 */
static int __init symbol_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));
return 0;
}
/* 内核模块卸载函数 */
static void __exit symbol_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"); //给模块设置一个别名</pre><p style="text-indent: 2em;">Makefile文件内容:</p><pre class="brush:plain;toolbar:false PrismJs"># 指向编译出来的 linux 内核具体路径
KERNEL_DIR = ../../kernel/ebf-buster-linux/build_image/build
# 定义变量,并且导出变量给子 Makefile 使用
ARCH = arm
CROSS_COMPILE = arm-linux-gnueabihf-
export ARCH 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 copy
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
copy:
cp *.ko /home/ailsonjack/share/nfs/temp</pre><p style="text-indent: 2em;">编译module_param内核模块和symbol_share内核模块,将生成的module_param.ko和symbol_share.ko文件通过nfs或者scp拷贝到开发板,然后在开发板中执行命令:</p><pre class="brush:plain;toolbar:false PrismJs">sudo insmod module_param.ko int_type=12 bool_type=1 char_type=201 str_type="hello!"
sudo insmod symbol_share.ko</pre><p style="text-indent: 2em;">注意,要先加载module_param内核模块,再加载symbol_share内核模块,因为symbol_share内核模块会依赖module_param内核模块导出的符号,如果先加载symbol_share内核模块,symbol_share内核模块将会加载失败。</p><p style="text-align:center"><img src="/uploads/AilsonJack/2021.05.23/200823208251279.png" onclick="preview_image('/uploads/AilsonJack/2021.05.23/200823208251279.png')"/></p><p style="text-indent: 2em;">查看module_param内核模块导出的符号:</p><pre class="brush:plain;toolbar:false PrismJs">cat /proc/kallsyms | grep int_type
cat /proc/kallsyms | grep data_inc
cat /proc/kallsyms | grep data_dec</pre><p style="text-align:center"><img src="/uploads/AilsonJack/2021.05.23/200823577477245.png" onclick="preview_image('/uploads/AilsonJack/2021.05.23/200823577477245.png')"/></p>
你可能也喜欢:
暂无评论,要不要来个沙发
发表评论
JLink V9掉固件修复(灯不亮) 3Zephyr笔记2 - 在STM32F429上运行HelloWorld 2计算NandFlash要传入的行地址和列地址 1Linux MMC子系统 - 6.eMMC 5.1工作模式-设备识别模式 0Linux MMC子系统 - 5.eMMC 5.1工作模式-引导模式 0Linux MMC子系统 - 4.eMMC 5.1常用命令说明(2) 0
标签云
Linux嵌入式实用技巧ARM内核学习问题集合CC++编程语言阅读笔记汇编Linux内核完全注释Windows驱动开发计算机基础ARM11ARMv7-ASTM32IDESublimeLinux内核学习eMMCMMC子系统Ubuntu操作系统OfficeVMWareAPUEgccRTOS中断漫游世界随笔感悟开发工具软件应用编程VsCodearmccarmclang编译器ZephyrSPIJLink网卡驱动安装各种芯片库函数NFSμCOS内核sambaFlashUnix命令与脚本输入法Linux内核设计与实现gitRIFFWAVJATGFTPar8161安装centos有线上网μCGUI字库工程建立右键菜单网络文件系统Firefox百度NTFS文件系统CodeBlocksCentOS数据结构算法PhotoShop51KeilQTUltraEditscanfglibc宏定义UIDGID优先级娱乐天地SourceInsight磁盘扇区总线I2CPDFBComparePythonI2SFPUMakefileSWDCPUARP软件推荐FileZilla