目录
简介:
一、__setup() 分析
1、__setup() 展开
2、init.setup 段定义
3、init.setup 段内容如何被调用
4、Linux中如何使用 __setup()
二、__setup() 示例
简介:
__setup 宏的作用是将命令行参数与处理函数绑定,在Linux启动时匹配 cmdline 命令行参数则调用绑定的函数处理这些参数。
一、__setup() 分析
1、__setup() 展开
Linux中__setup()宏源码如下:
/* 路径:include/linux/init.h */
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
/*
* Only for really core code. See moduleparam.h for the normal way.
*
* Force the alignment so the compiler doesn't space elements of the
* obs_kernel_param "array" too far apart in .init.setup.
*/
#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) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
以 __setup("test_setup=", test_setup_cmdline) 为例,展开后:
static const char __setup_str_test_setup_cmdline[] __initconst __aligned(1) = "test_setup=";
static struct obs_kernel_param __setup_test_setup_cmdline __used __section(.init.setup) __attribute__((aligned((sizeof(long))))) = { __setup_str_test_setup_cmdline, test_setup_cmdline, 0 }
其实就是在 init.setup 段中定义了 static struct obs_kernel_param 结构体,如下:
/* init.setup段定义此结构体 */
static struct obs_kernel_param __setup_test_setup_cmdline = {
.str = "test_setup=",
.setup_func = __setup_str_test_setup_cmdline,
.early = 0,
}
2、init.setup 段定义
init.setup段定义在 include/asm-generic/vmlinux.lds.h 文件中
#define INIT_SETUP(initsetup_align) \
. = ALIGN(initsetup_align); \
__setup_start = .; \
KEEP(*(.init.setup)) \
__setup_end = .;
init.setup段位于 __setup_start 到 __setup_end 之间。
3、init.setup 段内容如何被调用
Linux启动后代码运行流程如下:
/* 路径:init/main.c */
start_kernel()
--->parse_early_param()
--->parse_args()
--->--->parse_one()
--->--->--->unknown_bootoption()
--->--->--->--->obsolete_checksetup()
--->--->--->--->--->setup_func() //调用obs_kernel_param定义的setup_func(),即init_setup()
--->--->--->--->--->--->init_setup()
__setup() 设置 obs_kernel_param.early 为0,因此不会被 parse_early_param() 解析。init.setup段数据在 parse_args() 函数中被处理,直接看下 parse_one() 会调用 unknown_bootoption 处理 __setup() 定义的数据。
/* parse_args 调用 parse_one()函数 */
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);
/* kernel/params.c */
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;
/* 处理 __start___param - __stop___param 段的数据 */
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(¶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);
}
pr_debug("Unknown argument '%s'\n", param);
return -ENOENT;
}
因为 __setup() 定义的 struct obs_kernel_param 结构体放在 __setup_start - __setup_end,因此最终__setup() 的内容由 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)
{
repair_env_string(param, val, unused, NULL);
/* Handle obsolete-style parameters */
if (obsolete_checksetup(param))
return 0;
/* Unused module parameter. */
if (strchr(param, '.') && (!val || strchr(param, '.') < val))
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], val - param))
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;
}
unknown_bootoption() 调用 obsolete_checksetup() 处理 __setup() 的内容。
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) {
/* Already done in parse_early_param?
* (Needs exact match on param part).
* Keep iterating, as we can have early
* params and __setups of same names 8( */
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 结构体,将 obs_kernel_param.str 和 cmdline中的参数进行匹配,相等就调用 obs_kernel_param.setup_func()。
4、Linux中如何使用 __setup()
cmdline 中的 "console=ttyS0" 表示使用 ttyS0 作为控制台。在 kernel/printk/printk.c 中调用了
__setup("console=", console_setup);
Linux内核启动时,如果命令行中出现 console= 参数,内核就会调用 console_setup 函数来处理这个参数。例如,如果命令行为 console=ttyS0,115200n8 内核就会将控制台设置为 ttyS0,并设置相应的波特率和参数。
二、__setup() 示例
Linux内核中模块使用 __setup() 可以解析 cmdline 传递的参数信息,进而对模块功能进行定制化的配置。
static int test_setup_value = 0;
static __init int test_setup_cmdline(char *str)
{
if (!str)
return -EINVAL;
if (!strncmp(str, "ON", 2)) {
test_setup_value = 1;
} else if (!strncmp(str, "OFF", 3)) {
test_setup_value = 0;
}
pr_info("test_setup_value = %d\n", test_setup_value);
return 1;
}
__setup("test_setup=", test_setup_cmdline);
如果cmdline命令行中含有 "test_setup=ON" 内容,会执行 test_setup_cmdline()函数,将 "test_setup=" 后面的 "ON" 作为 test_setup_cmdline() 函数的入参。