Linux内核与驱动学习记录-内核模块传参和符号共享

By: Ailson Jack
Date: 2021.05.23
个人博客:http://www.only2fire.com/
本文在我博客的地址是:http://www.only2fire.com/archives/135.html,排版更好,便于学习,也可以去我博客逛逛,兴许有你想要的内容呢。

1.内核模块传参

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

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

#define module_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>

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"); //给模块设置一个别名

Makefile文件内容:

# 指向编译出来的 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

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

sudo insmod module_param.ko int_type=12 bool_type=1 char_type=201 str_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>

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"); //给模块设置一个别名

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 __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__ */

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"

/* 内核模块加载函数 */
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"); //给模块设置一个别名

Makefile文件内容:

# 指向编译出来的 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

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

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

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

在这里插入图片描述
查看module_param内核模块导出的符号:

cat /proc/kallsyms | grep int_type
cat /proc/kallsyms | grep data_inc
cat /proc/kallsyms | grep data_dec

在这里插入图片描述
欢迎关注博主的公众号呀:

在这里插入图片描述
如果文中有什么问题欢迎指正,毕竟博主的水平有限。

如果这篇文章对你有帮助,记得点赞和关注博主就行了^_^。

排版更好的内容见我博客的地址:http://www.only2fire.com/archives/135.html

注:转载请注明出处,谢谢!^_^

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jackailson

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值