kernel Param
"node" {
"label": "kernel param",
"categories": ["config"],
"info": "kernel and module param",
"depends": [
"kernel start",
"boot config",
"initcall",
"kernel taint",
"kernel security",
"procfs",
"sysfs",
"kernel module"
]
}
介绍
内核提供给各个模块注册参数接口,这些参数会在编译链接阶段存放在内核代码中,内核在启动各个子系统之前,通过对比用户传入的command line 选项 系统注册的参数进行匹配,一旦匹配成功,则调用对应参数设置的回调设置函数,完成对应模块参数的配置
kernel param 是内核当前主要在使用的机制,相比较于 obsolate param,功能更加完善且复杂
内核实现
__start___param 代码段
内核在代码里预留了一个段,被__param标识的变量, 会被链接在__start___param和__stop___param之间
__start___param = .;
KEEP(*(__param))
__stop___param = .; }
struct kernel_param
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);
};
/* Special one for strings we want to copy into */
struct kparam_string {
unsigned int maxlen;
char *string;
};
/* Special one for arrays */
struct kparam_array
{
unsigned int max;
unsigned int elemsize;
unsigned int *num;
const struct kernel_param_ops *ops;
void *elem;
};
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;
};
};
此结构体,相较于obsolete param 可以看到有了更加复杂的字段,这些字段表明 此参数机制 拥有更加丰富的功能
-
mod: 说明
param支持module -
ops:更加复杂和完整的参数操作接口
-
flags:
- KERNEL_PARAM_OPS_FL_NOARG : 表示选项无参数(可以用于Bool)
-
perm:
sysfs对应的文件权限(读写和访问权限) ,0表示不在sysfs显示 -
flags:
-
KERNEL_PARAM_FL_UNSAFE: 危险参数,会污染内核
-
HWPARAM: 受
LOCKDOWN_MODULE_PARAMETERS权限管控 -
level: 对应
initcall机制里的不同等级 -
arg: 参数内容
type
内核提供了一些基础的类型,以及对应的ops,使用基础类型的模块,按需调用对应的接口,目前提供的有:
- byte, hexint, 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).
这些类型,都提供了固固定类型的既定实现
-
int param_set_[type] (const char *val, const struct kernel_param *kp); -
int param_get_[type] (char *buffer, const struct kernel_param *kp); -
struct kernel_param_ops param_ops_[type]
API : core_param
接下来 我们会分析不同 param的API 以及解释他们的不同
#define core_param(name, var, type, perm)
-
name: 参数名
-
var: arg 参数对应的value 设置变量的内存
-
type: arg 的类型
-
byte, hexint, 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).
-
perm:
sysfs对应的文件权限(读写和访问权限) ,0表示不在sysfs显示
我们直接给出最终宏展开的一个示例
static int pause_on_oops;
core_param(pause_on_oops, pause_on_oops, int, 0644);
//-----------------------------------------------
//用于编译阶段检查传入变量类型是否一致
static unsigned int *__check_pause_on_oops(void) {
return &pause_on_oops;
}
static const char __param_str_pause_on_oops[] = "pause_on_oops";
static struct kernel_param _param_pause_on_oops
__used __section("__param")
=
{
__param_str_pause_on_oops,
THIS_MODULE,
¶m_ops_int,
0664,
-1,
0,
{ &pause_on_oops }
}
最主要的特点在于 通过此接口定义的结构体
-
level = -1;
-
参数名称等于实际的设置的名称(不包含
module name),参数属于kernel
API : device(subsys/core)_param_cb
此类API 格式类似, 主要用于在不同的initcall 调用阶段的初始化
#define late_param_cb(name, ops, arg, perm) \
__level_param_cb(name, ops, arg, perm, 7)
不同的接口 对应不同的level ,最终展开为
static const struct kernel_param_ops loop_hw_qdepth_param_ops = {
.set = loop_set_hw_queue_depth,
.get = param_get_int,
};
static int hw_queue_depth = LOOP_DEFAULT_HW_Q_DEPTH;
device_param_cb(hw_queue_depth, &loop_hw_qdepth_param_ops, &hw_queue_depth, 0444);
//------------------------------------------------------------------
static const char __param_str_hw_queue_depth[] = "loop.hw_queue_depth";
static struct kernel_param _param_hw_queue_depth
__used __section("__param")
=
{
__param_str_hw_queue_depth,
THIS_MODULE,
&loop_hw_qdepth_param_ops,
0444,
devic_level = 6 ,
0,
{ &hw_queue_depth }
}
接口特点,通过此接口定义的 结构体
-
level等于initcall中对应的级别 -
参数名称:
-
如果代码被直接编译进内核,变量名称为:
modname.param_name,参数属于具体模块 -
如果是以模块的形式加载,则表示以
module的方式加载
API :module_param
#define module_param(name, type, perm)
module_param_named(name, name, type, perm)
-
name: 参数名
-
type: arg 的类型
-
byte, hexint, 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).
-
perm:
sysfs对应的文件权限(读写和访问权限) ,0表示不在sysfs显示
这里和之前大部分相同,主要区别为:
-
增加了
__MODULE_INFO描述 -
level = -1
-
参数名称:
-
如果代码被直接编译进内核,变量名称为:
modname.param_name,参数属于具体模块 -
如果是以模块的形式加载,则表示以
module的方式加载
boot阶段的参数匹配
boot阶段,会对 链接到内核的参数进行解析 一共有两处
第一处 对应 level=-1的初始化
start_kerne()
// 初始化所有level = -1 的 参数
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);
print_unknown_bootoptions();
第二处 在 initcall 模块在调用各个initcall之前设置
static void __init do_initcall_level(int level, char *command_line)
{
initcall_entry_t *fn;
parse_args(initcall_level_names[level],
command_line, __start___param,
__stop___param - __start___param,
level, level,
NULL, ignore_unknown_bootoption);
trace_initcall_level(initcall_level_names[level]);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
}
module模式
如果代码被编译成模块,则kernel param 并不会被链接,所以对应的参数不会再start_param段里面 ,而是再模块加载的时候,才会被扫描加载
下面代码是段的变量扫描
static int find_module_sections(struct module *mod, struct load_info *info)
{
mod->kp = section_objs(info, "__param",
sizeof(*mod->kp), &mod->num_kp);
在module 加载时 才解析参数
/* Module is ready to execute: parsing args may do that. */
after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
-32768, 32767, mod,
unknown_module_param_cb);
module的参数传递格式为
# modeprobe test_mod test_param=1
sysfs
一旦为kernel param设置了非0的 perm选项的,都会生成对应的sysfs用户接口
device_param_cb(hw_queue_depth, &loop_hw_qdepth_param_ops, &hw_queue_depth, 0444);
会再对应的sysfs节点创建parameter,不属于任何模块的,也就内核的kernel param会在kernel module目录下
# cat /sys/module/[module]/parameters/xxx
# cat /sys/module/kernel/parameters/xxx
如果对应文件具备写权限,则还可以修改
sysfs 初始化在这里,比较简单 我们就不再跟踪了
/*
* param_sysfs_builtin_init - add sysfs version and parameter
* attributes for built-in modules
*/
static int __init param_sysfs_builtin_init(void)
{
if (!module_kset)
return -ENOMEM;
version_sysfs_builtin();
param_sysfs_builtin();
return 0;
}
late_initcall(param_sysfs_builtin_init);