内核模块参数

背景

接上一篇 内核启动参数cmdline

上一篇主要讨论了不是模块的情况下,内核启动参数的传递解析过程及使用:early_param()__setup(),这篇主要讨论下模块的情况

相关内核文档:Documentation/admin-guide/kernel-parameters.rst

内核模块添加参数

module_param(参数名, 参数类型, 0644);
MODULE_PARM_DESC(参数名, "参数说明.");

内核模块参数使用

分3种方式:

  • 内核启动cmdline
    模块名.参数=xxx
  • 模块加载cmdline
    modprobe 模块名 参数=xxx
  • 动态修改
    / sys / module / <模块名> / parameters/参数

例如:

(kernel command line) usbcore.blinkenlights=1
(modprobe command line) modprobe usbcore blinkenlights=1

内核模块定义和解析过程

定义

下面是module_param定义(include/linux/moduleparam.h):

module_param
	-> module_param_named
		-> module_param_cb
			-> __module_param_call
/**
 * module_param - typesafe helper for a module/cmdline parameter
 * @value: the variable to alter, and exposed parameter name.
 * @type: the type of the parameter
 * @perm: visibility in sysfs.
 *
 * @value becomes the module parameter, or (prefixed by KBUILD_MODNAME and a
 * ".") the kernel commandline parameter.  Note that - is changed to _, so
 * the user can use "foo-bar=1" even for variable "foo_bar".
 *
 * @perm is 0 if the the variable is not to appear in sysfs, or 0444
 * for world-readable, 0644 for root-writable, etc.  Note that if it
 * is writable, you may need to use kernel_param_lock() around
 * accesses (esp. charp, which can be kfreed when it changes).
 *
 * The @type is simply pasted to refer to a param_ops_##type and a
 * param_check_##type: for convenience many standard types are provided but
 * you can create your own by defining those variables.
 *
 * Standard types are:
 *	byte, short, ushort, int, uint, long, ulong
 *	charp: a character pointer
 *	bool: a bool, values 0/1, y/n, Y/N.
 *	invbool: the above, only sense-reversed (N = true).
 */
#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_param_cb(name, ops, arg, perm)				      \
	__module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1, 0)

/* This is the fundamental function for registering boot/module
   parameters. */
#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								\
    __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
	= { __param_str_##name, THIS_MODULE, ops,			\
	    VERIFY_OCTAL_PERMISSIONS(perm), level, flags, { arg } }

这里以下面代码为例:

module_param(xxxxx int, 0444);

最后展开为:

param_check_int(xxxxx, &(xxxxx));				   \
	 
static const char __param_str_xxxxx[] = prefix xxxxx;		\
static struct kernel_param __moduleparam_const __param_xxxxx	\
__used								\
   __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
= { 
		__param_str_xxxxx, 
		THIS_MODULE, 
		&param_ops_int,			\
    	VERIFY_OCTAL_PERMISSIONS(0444), 
		-1, 
		0, 
		{  &xxxxx } 
	}   \

__MODULE_PARM_TYPE(xxxxx, int)

主要定义了struct kernel_param类型的数据存放在__param

struct kernel_param(include/linux/moduleparam.h)类型定义为:

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;
	};
};

解析过程

在load模块的时候会去查找对应的段,并将各个参数解析处理(kernel/module.c):

insmod/modprobe 模块
-> sys_init_module()   //系统调用
	-> load_module()   //加载模块
		-> find_module_sections()  // 查找相应的段 `__param`段保存在mod->kp
		-> parse_args()   //解析内核参数,  这里开始就和内核启动cmdline处理一致了
			-> parse_one()   //一个个解析参数,并与用户传进来的参数匹配,成功就设置模块内核参数

我们看下比较核心的 parse_one()函数的部分代码:

/* Find parameter */
	for (i = 0; i < num_params; i++) {
		if (parameq(param, params[i].name)) {
			if (params[i].level < min_level
			    || params[i].level > max_level)
				return 0;
			/* No one handled NULL, so do it here. */
			if (!val &&
			    !(params[i].ops->flags & KERNEL_PARAM_OPS_FL_NOARG))
				return -EINVAL;
			pr_debug("handling %s with %p\n", param,
				params[i].ops->set);
			kernel_param_lock(params[i].mod);
			if (param_check_unsafe(&params[i]))
				err = params[i].ops->set(val, &params[i]);   /* 参数匹配成功就会调用  struct kernel_param 结构的ops函数集,就是对应上面定义的param_ops_int*/
			else
				err = -EPERM;
			kernel_param_unlock(params[i].mod);
			return err;
		}
	}

因为这里参数的类型为int, 所以参数的操作集为内核自带的param_ops_int,主要就是set(param_set_int)和get(param_get_int)的实现,
也可以使用module_param_cb()自定义参数的操作集,这里就不展开说了。

param_set_intparam_get_int主要使用STANDARD_PARAM_DEF宏(kernel/params.c)来定义的,这里也不多说了

参考

  • 内核源码