1. __setup()介绍
static __init int dma_debug_cmdline(char *str)
{
if (!str)
return -EINVAL;
if (strncmp(str, "off", 3) == 0) {
pr_info("debugging disabled on kernel command line\n");
global_disable = true;
}
return 1;
}
static __init int dma_debug_entries_cmdline(char *str)
{
if (!str)
return -EINVAL;
if (!get_option(&str, &nr_prealloc_entries))
nr_prealloc_entries = PREALLOC_DMA_DEBUG_ENTRIES;
return 1;
}
__setup("dma_debug=", dma_debug_cmdline);
__setup("dma_debug_entries=", dma_debug_entries_cmdline);
我们经常可以在一些模块中看到__setup(),模块利用__setup()可以解析命令行传递的参数信息,进而对模块功能进行定制化的配置。 如上所示:__setup(“dma_debug=”, dma_debug_cmdline); 假设cmdline中含有dma_debug=off 内容,则会将off 作为入参传递到dma_debug_cmdline()函数进行解析。
2. 实现原理
2.1 __setup()宏展开
__setup()是一个宏,我们将其定义展开,如下所示:
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
#define __setup_param(str, unique_id, fn, early) \
static const char __setup_str_##unique_id[] __initconst \
__aligned(1) = str; \
static struct obs_kernel_param __setup_##unique_id \
__used __section(".init.setup") \
__aligned(__alignof__(struct obs_kernel_param)) \
= { __setup_str_##unique_id, fn, early }
以__setup(“dma_debug=”, dma_debug_cmdline);为例,我们进行代入:
static const char __setup_str_dma_debug_cmdline[] __initconst _aligned(1) = "dma_debug=";
static struct obs_kernel_param __setup_dma_debug_cmdline __used __section(".init.setup") __aligned(__alignof__(struct obs_kernel_param)) = { __setup_dma_debug_cmdline, dma_debug_cmdline, 0 }
其实这个相当于做了一件事情,只是在init.setup 段中,定义了一个struct obs_kernel_param 结构体,并为其赋值而已,赋值后的struct obs_kernel_param 结构体内容是这样的:
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
obs_kernel_param.str = "dma_debug=";
obs_kernel_param.setup_func = dma_debug_cmdline;
obs_kernel_param.early = 0;
2.2 init.setup段位置
#define INIT_SETUP(initsetup_align) \
. = ALIGN(initsetup_align); \
__setup_start = .; \
KEEP(*(.init.setup)) \
__setup_end = .;
由定义可知:init.setup 段位于__setup_start - __setup_end 之间
2.3 __setup(str, fn) fn何时被调用
让我们一步步从内核启动的开始进行跟踪:
start_kernel()
-> parse_args()
-> parse_one()
-> unknown_bootoption()
因为__setup()设置的obs_kernel_param.early = 0 ,因此不会进入start_kernel()->parse_early_param()中去解析,另外由于__setup()将obs_kernel_param 结构体保存在__setup_start - __setup_end之中,因此在parse_one()函数解析时:
static int parse_one(char *param,
char *val,
const char *doing,
const struct kernel_param *params,
unsigned num_params,
s16 min_level,
s16 max_level,
void *arg,
int (*handle_unknown)(char *param, char *val,
const char *doing, void *arg))
{
unsigned int i;
int err;
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;
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(¶ms[i]))
err = params[i].ops->set(val, ¶ms[i]);
else
err = -EPERM;
kernel_param_unlock(params[i].mod);
return err;
}
}
这部分代码不会执行,最终会来到
if (handle_unknown) {
pr_debug("doing %s: %s='%s'\n", doing, param, val);
return handle_unknown(param, val, doing, arg);
}
这里进行处理,而handle_unknown函数指针的值是&unknown_bootoption ,因此最终__setup()的内容由unknown_bootoption() 函数进行处理。
现在我们来看一些unknown_bootoption() 函数的实现细节:
static int __init unknown_bootoption(char *param, char *val,
const char *unused, void *arg)
{
size_t len = strlen(param);
repair_env_string(param, val);
if (obsolete_checksetup(param))
return 0;
if (strnchr(param, len, '.'))
return 0;
if (panic_later)
return 0;
if (val) {
unsigned int i;
for (i = 0; envp_init[i]; i++) {
if (i == MAX_INIT_ENVS) {
panic_later = "env";
panic_param = param;
}
if (!strncmp(param, envp_init[i], len+1))
break;
}
envp_init[i] = param;
} else {
unsigned int i;
for (i = 0; argv_init[i]; i++) {
if (i == MAX_INIT_ARGS) {
panic_later = "init";
panic_param = param;
}
}
argv_init[i] = param;
}
return 0;
}
unknown_bootoption() 函数通过调用obsolete_checksetup() 函数进行进一步处理:
static bool __init obsolete_checksetup(char *line)
{
const struct obs_kernel_param *p;
bool had_early_param = false;
p = __setup_start;
do {
int n = strlen(p->str);
if (parameqn(line, p->str, n)) {
if (p->early) {
if (line[n] == '\0' || line[n] == '=')
had_early_param = true;
} else if (!p->setup_func) {
pr_warn("Parameter %s is obsolete, ignored\n",
p->str);
return true;
} else if (p->setup_func(line + n))
return true;
}
p++;
} while (p < __setup_end);
return had_early_param;
}
从obsolete_checksetup() 函数中可以看到,程序将__setup_start-__setup_end中保存的所有obs_kernel_param 结构体内容进行遍历,使用param (param是从cmdline中获取到的参数名称)进行匹配。如果param 和obs_kernel_param.str 相等,则会调用obs_kernel_param.setup_func() 函数进行处理。
对于cmdline中包含dma_debug=off 内容时,param=“dma_debug=”,此时就会将"off"作为参数传递到dma_debug_cmdline()函数中进行解析。
3. 其他细节
上述以__setup(“dma_debug=”, dma_debug_cmdline);为例,分析了整个解析过程。
但是还有一些细节需要注意,__setup(str, fn),对于fn函数的返回值是有要求的,当返回为1时,表示传入的参数可以被fn函数解析,如果返回为0,则代表传入的参数格式错误,无法被fn函数解析。
返回值为0的参数,会被内容所记录,流程如下: 如果fn函数的返回值为0,则obsolete_checksetup() 函数的返回值为false,则会由unknown_bootoption()函数进行进一步处理:
/*
* Unknown boot options get handed to init, unless they look like
* unused parameters (modprobe will find them in /proc/cmdline).
*/
static int __init unknown_bootoption(char *param, char *val,
const char *unused, void *arg)
{
size_t len = strlen(param);
repair_env_string(param, val);
/* Handle obsolete-style parameters */
if (obsolete_checksetup(param))
return 0;
/* Unused module parameter. */
if (strnchr(param, len, '.'))
return 0;
if (panic_later)
return 0;
if (val) {
/* Environment option */
unsigned int i;
for (i = 0; envp_init[i]; i++) {
if (i == MAX_INIT_ENVS) {
panic_later = "env";
panic_param = param;
}
if (!strncmp(param, envp_init[i], len+1))
break;
}
envp_init[i] = param;
} else {
/* Command line option */
unsigned int i;
for (i = 0; argv_init[i]; i++) {
if (i == MAX_INIT_ARGS) {
panic_later = "init";
panic_param = param;
}
}
argv_init[i] = param;
}
return 0;
}
如果传递的参数值不为NULL,则会将param信息保存到envp_init[]字符数组中,如果如果传递的参数值为NULL,则会将param信息保存到argv_init[]字符数组中。
最终这些保存到字符数组中的内容,则会由start_kernel()->print_unknown_bootoptions() 函数进行打印输出。
static void __init print_unknown_bootoptions(void)
{
char *unknown_options;
char *end;
const char *const *p;
size_t len;
if (panic_later || (!argv_init[1] && !envp_init[2]))
return;
len = 1;
for (p = &argv_init[1]; *p; p++) {
len++;
len += strlen(*p);
}
for (p = &envp_init[2]; *p; p++) {
len++;
len += strlen(*p);
}
unknown_options = memblock_alloc(len, SMP_CACHE_BYTES);
if (!unknown_options) {
pr_err("%s: Failed to allocate %zu bytes\n",
__func__, len);
return;
}
end = unknown_options;
for (p = &argv_init[1]; *p; p++)
end += sprintf(end, " %s", *p);
for (p = &envp_init[2]; *p; p++)
end += sprintf(end, " %s", *p);
pr_notice("Unknown kernel command line parameters \"%s\", will be passed to user space.\n",
&unknown_options[1]);
memblock_free(unknown_options, len);
}
因此__setup(str, fn) fn的返回值如果非1的话,传入的参数信息则会在出现在Unknown kernel command line parameters 日志中。
感谢大家的浏览,本文为博主原创,未经允许不可转载。
|