__setup宏
我们在跟踪rootfs挂载过程的时候,会在init/main.c看到有如下一个函数rdinit_setup,并通过__setup宏进行声明:
//init/main.c
static int __init rdinit_setup(char *str)
{
unsigned int i;
ramdisk_execute_command = str;
/* See "auto" comment in init_setup */
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("rdinit=", rdinit_setup);
那么rdinit_setup是何时会被调用呢?__setup又做了什么?
实际__setup是一个宏定义:
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
继续来看__setup_param的宏定义:
/*
* 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 }
从这个宏定义可以看出,这是一个内核核心代码(基本不会被驱动代码等使用)。
可以看到它实际是定义了一个struct obs_kernel_param结构体变量,我们继续看下这个结构体是怎么定义的:
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
这里str就是参数的名称,setup_func就是解析参数结果后的回调函数,early表明了是否在early阶段执行回调,至此上面的
__setup(“rdinit=”, rdinit_setup),通过make init/main.i 我们可以看到预处理做如下的替换:
static const char __setup_str_rdinit_setup[]
__attribute__((__section__(".init.rodata")))
__attribute__((__aligned__(1))) = "rdinit=";
static struct obs_kernel_param __setup_rdinit_setup
__attribute__((__used__))
__attribute__((__section__(".init.setup"))) __attribute__((aligned((sizeof(long))))) = {
__setup_str_rdinit_setup,
rdinit_setup,
0
};
也就是__setup_rdinit_setup这个结构体会被存放到.init.setup段,从vmlinux.lds链接脚本中看到的片段如下
.init.data : {
......
. = ALIGN(16); __setup_start = .; KEEP(*(.init.setup)) __setup_end = .;
.....
}
那么这个结构体将在什么时候被使用?
主要是在两种情况下使用,一种是在parse_early_param中执行,主要是字段early为1的参数,一种是在parse_args过程中调用,主要字段early为0
start_kernel
|--setup_arch
| |--parse_early_param
| |--strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE)
| |--parse_early_options(tmp_cmdline)
| |--parse_args("early options", cmdline, NULL, 0, 0, 0, NULL,do_early_param);
|--parse_args("Booting kernel",...&unknown_bootoption)
|--parse_args("Setting init args",...set_init_arg)
|--parse_args("Setting extra init args",....set_init_arg)
parse_early_param对于obs_kernel_param.early为1的参数将会得到解析,并执行它的回调,可以看出它是针对架构早期需要执行的一些参数解析和回调而定义的,比如:
//arm64架构相关
early_param("acpi", parse_acpi);
early_param("irqchip.gicv3_pseudo_nmi", early_enable_pseudo_nmi);
early_param("mem", early_mem);
early_param("rodata", parse_rodata);
early_param("numa", numa_parse_early_param);
//kernel相关
early_param("log_buf_len", log_buf_len_setup);
early_param("oops", oops_setup);
early_param("percpu_alloc", percpu_alloc_setup);
parse_args对于obs_kernel_param.early为0的参数将会得到解析,并执行它的回调
通过gdb也可以看到rdinit的回调调用栈,它的回调就是在parse_args(“Booting kernel”…)中得到调用
#0 rdinit_setup (str=0xffff00003cdf8d47 "/linuxrc") at init/main.c:593
#1 0xffff8000114507c8 in obsolete_checksetup (line=0xffff00003cdf8d40 "rdinit=/linuxrc") at init/main.c:216
#2 unknown_bootoption (param=0xffff00003cdf8d40 "rdinit=/linuxrc", val=0xffff00003cdf8d47 "/linuxrc", unused=<optimized out>, arg=<optimized out>) at init/main.c:539
#3 0xffff8000100b88c4 in parse_one (handle_unknown=0xffff8000114506f8 <unknown_bootoption>, arg=0xffffffff00100000, max_level=8, min_level=-32768, num_params=367, params=0x0, doing=0x0, val=0xffff00003cdf8d47 "/linuxrc",
param=0xffff00003cdf8d40 "rdinit=/linuxrc") at kernel/params.c:153
#4 parse_args (doing=0x0, doing@entry=0xffff800011233288 "Booting kernel", args=0xffff00003cdf8d50 "console=ttyAMA0 cma=48M", params=0x0, num=367, min_level=-32768, min_level@entry=-1, max_level=8, max_level@entry=-1,
arg=0xffffffff00100000, arg@entry=0x0, unknown=unknown@entry=0xffff8000114506f8 <unknown_bootoption>) at kernel/params.c:188
#5 0xffff800011450e4c in start_kernel () at init/main.c:885
最后我们说明下,rdinit这个参数的作用是什么。它可以通过在command line中通过rdinit=xxx来指定要执行的init进程,而不是系统默认的/init,如可以在command line中指定”rdinit=/linuxrc“
early_param宏
需要注意的是__setup是定义了非early的参数和回调,而early_param宏是定义了early param的参数和回调,如下:
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
总结
- __setup定义了一个struct obs_kernel_param结构体变量,指明了参数名,非early,回调函数,并放到.init.setup段,它们是由parse_args进行解析并执行它的回调;
- early_param与__setup类似,但是指明是early的,因此会由parse_early_param进行解析,并执行它的回调