代码模板-Linux内核模块添加参数的方法、原理与内部细节?(smod x.ko param1=xxx;MODULE_PARM_DESC;module_param)

背景

有些时候内核模块需要动态设置参数。本文简单介绍如何在ko中添加以及如何设置,以及如何查看ko有哪些参数的方法。

要点

  • 主体分几个定义参数,指定module_param参数,以及指定描述。
  • module_param本质是定义一个struct kernel_param的结构,并且有标准的回调处理函数的
  • 也可以使用module_param_cb来直接定义一个自定义的处理函数,类似doca的argp模块。是修改_param代码段的信息,包括有struct kernel_param_ops
  • MODULE_PARM_DESC本质是去修改.modinfo代码段的信息
  • 定义设置:
static int param1 = 0;

// 模块参数
module_param(param1, int, S_IRUGO); //注意这里的参数
MODULE_PARM_DESC(param1, "Debug level (0-2)");
  • insmod指定:insmod x.ko xxx=xxx
insmod param.ko param1=100
  • 参数可以指定:
    S_IRUSR(Read by owner):文件所有者有读权限。
    S_IWUSR(Write by owner):文件所有者有写权限。
    S_IRGRP(Read by group):文件所在组有读权限。
    S_IWGRP(Write by group):文件所在组有写权限。

代码

ko文件param.c


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

// 全局调试级别变量
static int param1 = 0;

// 模块参数
module_param(param1, int, S_IRUGO);
MODULE_PARM_DESC(param1, "Debug level (0-2)");

// 初始化函数
static int __init param1_init(void) {
    printk(KERN_INFO "param1: Initializing module with param1: %d\n", param1);
    return 0;
}

// 清理函数
static void __exit param1_exit(void) {
    printk(KERN_INFO "param1: Exiting module\n");
}

// 模块宏定义
module_init(param1_init);
module_exit(param1_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("beiming");
MODULE_DESCRIPTION("A simple module with a parameter");
MODULE_VERSION("0.01");

Makefile

obj-m += param.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

实操

  • 编译: make
    在这里插入图片描述

  • 安装与日志:insmod param.ko
    在这里插入图片描述

其他

查看某个ko有哪些参数modinfo

在这里插入图片描述

内核实现原理初探

本质是将参数定义并且放入对应的代码段。在insmod或者modinfo的时候,根据入参去判断params中是否有这个参数,如果有直接修改对应的值。当然还会做一些权限校验。

  • 在include/linux/moduleparam.h文件中

module_param(param1, int, S_IRUGO);

#define module_param(name, type, perm)				\
	module_param_named(name, name, type, perm)

#define module_param_named(name, value, type, perm)			   \
	param_check_##type(name, &(value));				   \
	module_param_cb(name, &param_ops_##type, &value, perm);		   \
	__MODULE_PARM_TYPE(name, #type)


#define __MODULE_INFO_PREFIX KBUILD_MODNAME "."

#define __MODULE_INFO(tag, name, info)					  \
static const char __UNIQUE_ID(name)[]					  \
  __used __section(".modinfo") __attribute__((unused, aligned(1)))	  \
  = __MODULE_INFO_PREFIX __stringify(tag) "=" info

#define __MODULE_PARM_TYPE(name, _type)					  \
  __MODULE_INFO(parmtype, name##type, #name ":" _type)

可见使用type在.modinfo的代码段定义了字符串数组列表。也就是modinfo的时候输出的信息。
可见定义了一个module_param_cb的函数,将名字以及比如param_ops_int的处理函数传入(包括地址和权限),然后进行处理。

更多细节:

#define __module_param_call(prefix, name, ops, arg, perm, level, flags)	\
	/* Default value instead of permissions? */			\
	static const char __param_str_##name[] = prefix #name;		\
	static struct kernel_param __moduleparam_const __param_##name	\
	__used								\
    __section("__param") __attribute__ ((unused, aligned(sizeof(void *)))) \
	= { __param_str_##name, THIS_MODULE, ops,			\
	    VERIFY_OCTAL_PERMISSIONS(perm), level, flags, { arg } }

#define module_param_cb(name, ops, arg, perm)				      \
	__module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1, 0)

可见将代码放入__param代码段,并且以__param_str_作为前缀的字符串变量,并且存入这个变量。比如前面就是 char __param_str_param1 = "param1",它使用#的宏定义将变量转化为一个字符串。
然后定义了一个struct kernel_param的数据结构(更多kernel_param数据结构参考后文),并且使用了结构化初始化定义:
将name使用了__param_str_param1的这个变量,而这个变量的实际值是:“param1”,也就是module_param的第一个参数,然后THIS_MODULE是一个指针指向struct modue。还有他的操作函数ops有前面可以得知假设是int就是:param_ops_int的ops,他是一个struct kernel_param_ops 的结构具体参考后文。定义了set get free的回调。
在这里插入图片描述

MODULE_PARM_DESC

#define MODULE_PARM_DESC(_parm, desc) \
	__MODULE_INFO(parm, _parm, #_parm ":" desc)

比如:MODULE_PARM_DESC(param1, "Debug level (0-2)");
可见在modinfo的代码段添加数组,以param1为前缀转化为字符串 然后添加: 然后添加描述
最终执行的后的效果参考modinfo中的:
在这里插入图片描述

struct kernel_param 数据结构

可见以struct kernel_param 为主,包括有name mod、ops以及权限perm还有flags以及他的string和array。
具体结构参考:

struct kernel_param {
	const char *name;
	struct module *mod;
	const struct kernel_param_ops *ops;
	const u16 perm;
	s8 level;
	u8 flags;
	union {
		void *arg;
		const struct kparam_string *str;
		const struct kparam_array *arr;
	};
};

在这里插入图片描述

关于一些函数类型的处理函数 struct kernel_param_ops

extern const struct kernel_param_ops param_ops_int;
extern int param_set_int(const char *val, const struct kernel_param *kp);
extern int param_get_int(char *buffer, const struct kernel_param *kp);
struct kernel_param_ops {
	/* How the ops should behave */
	unsigned int flags;
	/* Returns 0, or -errno.  arg is in kp->arg. */
	int (*set)(const char *val, const struct kernel_param *kp);
	/* Returns length written or -errno.  Buffer is 4k (ie. be short!) */
	int (*get)(char *buffer, const struct kernel_param *kp);
	/* Optional function to free kp->arg when module unloaded. */
	void (*free)(void *arg);
};

高阶玩法

比如自定义module_param_cb替换module_param来定义一个struct kernel_param,以及他自己的处理函数nx_huge_pages_ops。这样就能在初始化的时候能够调用想要处理参数的业务逻辑,提高更多可玩性。但本后的本质还是定义了struct kernel_param_ops。然后这个ops会在

static int __read_mostly nx_huge_pages = -1;
module_param_cb(nx_huge_pages, &nx_huge_pages_ops, &nx_huge_pages, 0644);
__MODULE_PARM_TYPE(nx_huge_pages, "bool");

static const struct kernel_param_ops nx_huge_pages_ops = {
	.set = set_nx_huge_pages,
	.get = param_get_bool,
};

static int set_nx_huge_pages(const char *val, const struct kernel_param *kp)
{
	bool old_val = nx_huge_pages;
	bool new_val;

	/* In "auto" mode deploy workaround only if CPU has the bug. */
	if (sysfs_streq(val, "off"))
		new_val = 0;
	else if (sysfs_streq(val, "force"))
		new_val = 1;
	else if (sysfs_streq(val, "auto"))
		new_val = get_nx_auto_mode();
	else if (strtobool(val, &new_val) < 0)
		return -EINVAL;

	__set_nx_huge_pages(new_val);

	if (new_val != old_val) {
		struct kvm *kvm;

		mutex_lock(&kvm_lock);

		list_for_each_entry(kvm, &vm_list, vm_list) {
			mutex_lock(&kvm->slots_lock);
			kvm_mmu_zap_all_fast(kvm);
			mutex_unlock(&kvm->slots_lock);

			wake_up_process(kvm->arch.nx_lpage_recovery_thread);
		}
		mutex_unlock(&kvm_lock);
	}

	return 0;
}

int param_get_bool(char *buffer, const struct kernel_param *kp)
{
	/* Y and N chosen as being relatively non-coder friendly */
	return sprintf(buffer, "%c\n", *(bool *)kp->arg ? 'Y' : 'N');
}

综述

本文介绍了如何在Linux内核模块中添加和设置动态参数,以及如何查看模块参数的方法。包括:
使用module_param宏定义模块参数,可以指定参数类型和权限。
module_param_cb允许定义自定义的处理函数,用于更复杂的参数处理。
MODULE_PARM_DESC用于添加参数描述,这些描述存储在.modinfo代码段。
在加载模块时,可以通过insmod命令指定参数值,如insmod param.ko param1=100。
并且介绍了struct kernel_param和struct kernel_param_ops结构,这些结构用于定义参数和处理函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值