1.平台简介:
硬件平台: QCA9531
软件平台: Openwrt-trunk Kernel-4.1.15
2.软件调试:
2.1 __setup()宏
在研究uboot传递给内核的cmdline解析的时候,我们经常会看到一个宏__setup(), 如console变量:
--/kernel/printk/printk.c
......
__setup("console=", console_setup);
......
这到底是什么意思呢? 我们先来看看__setup()宏的定义就知道了:
--/include/linux/init.h
#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)
struct obs_kernel_param { #NOTE:该变量定义在/include/linux/init.h中
const char *str;
int (*setup_func)(char *);
int early; #NOTE:此字段用于控制命令行参数的初始化等级
};
宏变量的作用主要是用来替换,这里使用console变量为例进行替换,看看最终的替换结果:
__setup("console=", console_setup) -->(NOTE:此次变换,直接替换参数即可)
_setup_param("console=", console_setup, console_setup, 0) --> (NOTE:此次变换,替换后定义了两个变量:
一个字符数组__setup_str_console_setup,并初始化其为"console=";
一个struct obs_kernel_param对象__setup_console_setup,并使用__setup_str_console_setup, console_setup, 0分别对其成员进行初始化。
)
static const char __setup_str_console_setup[] __initconst \
__aligned(1) = "console="; \
static struct obs_kernel_param __setup_console_setup \
__used __section(.init.setup) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_console_setup, console_setup, 0 } (#NOTE:此处引用了上面定义的字符数组变量)
(#NOTE:上述替换过程中出现了一些编译器选项,下面一一进行解答:
__initconst: 编译器处理选项,主要用于控制链接器链接时,程序的代码段布局,其定义在include/linux/init.h,其内容如下:
#define __init __section(.init.text) __cold notrace
#define __initdata __section(.init.data)
#define __initconst __constsection(.init.rodata) #NOTE:该选项表明作用对象链接到程序的.init.rodata段中
#define __exitdata __section(.exit.data)
#define __exit_call __used __section(.exitcall.exit)
__aligned(1): 存储类修饰符,主要用于控制字节对其,这里使用1字节对其,与char数组常规对其一致,因此此处没有什么实际作用,只是为了安全考虑。
__used: 变量或函数属性,当未被使用时编辑器将提供警告信息,作用与__unused相反。
__section(.init.setup) :指定代码段为.init.setup代码段,此处即为该参数处理的入口。这里我们看看链接脚本arch/mips/kernel/vmlinux.lds,此段是如何链接的:
__setup_start = .; KEEP(*(.init.setup)) __setup_end = .;
__attribute__((aligned((sizeof(long))))) :指定对其长度为sizeof(long)
)
2.1 Kernel对 bootargs的接收
替换结果产生了一个字符数组和一个结构体变量,在探究参数引用之前,我们先探究以下这些参数的来源,即内核对bootargs的接收?
booargs在openwrt的框架中的内核中有3种方式进行配置和修改:
(1) 采用uboot动态传递参数的形式
(2) 采用menuconfig中的Default kernel command string配置选项进行配置。
(3) 采用openwrt中patch-cmdline工具进行替换,make kernel_menuconifg 时使用[*] OpenWrt specific image command line hack定义CONFIG_IMAGE_CMDLINE_HACK选项。
--/init/main.c
char __initdata boot_command_line[COMMAND_LINE_SIZE];
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
......
setup_arch(&command_line);
......
mangle_bootargs(command_line);
setup_command_line(command_line);
......
pr_notice("Kernel command line: %s\n", boot_command_line); #NOTE:启动时命令行参数的打印信息出自此处
......
}
--/arch/mips/kernel/setup.c
static char __initdata command_line[COMMAND_LINE_SIZE];
char __initdata arcs_cmdline[COMMAND_LINE_SIZE];
#ifdef CONFIG_CMDLINE_BOOL
static char __initdata builtin_cmdline[COMMAND_LINE_SIZE] = CONFIG_CMDLINE;
#endif
void __init setup_arch(char **cmdline_p)
{
......
prom_init(); #NOTE:此处初始化prom,对9531而言,调用arch/mips/ath79/prom.c中的prom_init()函数
......
arch_mem_init(cmdline_p);
......
}
static void __init arch_mem_init(char **cmdline_p)
{
......
(#NOTE:下述宏定义来自于内核的.config,位于kernel_menuconfig的如下位置:
Kernel hacking-->
[*]Built-in kernel command line --> (CONFIG_CMDLINE_BOOL)
(rootfstype=squashfs,jffs2 noinitrd)Default kernel command string (CONFIG_CMDLINE)
[ ] Built-in command line overrides firmware arguments(CONFIG_CMDLINE_OVERRIDE)
)
#ifdef CONFIG_CMDLINE_BOOL
#ifdef CONFIG_CMDLINE_OVERRIDE
#NOTE:采用静态CMDLINE, 若定义了OVERRIDE配置时,直接用builtin_cmdline(CONFIG_CMDLINE)替换boot_command_line
strlcpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);
#else
if (builtin_cmdline[0]) {
#NOTE:采用静态CMDLINE,若未定义OVERRIDE配置时,用builtin_cmdline(CONFIG_CMDLINE)追加在arcs_cmdline之后,
然后替换boot_command_line,arcs_cmdline的首次初始化在setup_arch()函数中调用的prom_init()函数中进行,
详情参见"2.3 Openwrt patch-cmdline机制"
strlcat(arcs_cmdline, " ", COMMAND_LINE_SIZE);
strlcat(arcs_cmdline, builtin_cmdline, COMMAND_LINE_SIZE); }
strlcpy(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE);
#endif
#else
#NOTE:不采用静态CMDLINE,则直接用arcs_cmdline替换boot_command_line中的内容
strlcpy(boot_command_line, arcs_cmdline, COMMAND_LINE_SIZE);
#endif
strlcpy(command_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = command_line;
parse_early_param(); #NOTE:此处在平台中对解析了struct obs_kernel_param中early字段为1的对象
......
}
2.2 bootargs的引用
替换结果产生了一个字符数组和一个结构体变量,这些变量又是什么时候被内核引用的呢?
--/init/main.c
static int __init obsolete_checksetup(char *line)
{
const struct obs_kernel_param *p;
int had_early_param = 0;
p = __setup_start; #NOTE:__setup_start定义在链接文件中,其为.init.setup段的首部。
do {
int n = strlen(p->str); #NOTE:取struct obs_kernel_param中的str的长度,如"console="。
if (parameqn(line, p->str, n)) { #NOTE: 匹配对应的str字段
if (p->early) { #NOTE: 标记此标记的参数,已经在函数parse_early_param()中处理。
/* 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 = 1;
} else if (!p->setup_func) { #NOTE: 处理函数为空则忽略该参数
pr_warn("Parameter %s is obsolete, ignored\n",
p->str);
return 1;
} else if (p->setup_func(line + n)) #NOTE:调用处理函数,并将参数的值传入,line+n指示的字符串即为命令行参数中对应参数=后面的值。
return 1;
}
p++;
} while (p < __setup_end); #NOTE:__setup_end定义在链接文件中,其为.init.setup段的尾部。
return had_early_param;
}
命令行参数分两次解析,首先解析early为1的特权参数,然后解析early为0的unknown参数,下面我们再来看看命令行参数解析的入口:
--/init/main.c
asmlinkage __visible void __init start_kernel(void)
{
......
pr_notice("Kernel command line: %s\n", boot_command_line); #NOTE:启动时命令行参数的打印信息出自此处
parse_early_param(); #NOTE:第一次解析参数,解析struct obs_kernel_param中early字段为1的参数,该动作可能在setup_arch()函数中已经完成。
after_dashes = parse_args("Booting kernel", #NOTE:第二次解析参数,解析函数为unknown_bootoption
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, &unknown_bootoption);
......
}
第一次参数解析:
void __init parse_early_param(void)
{
static int done __initdata;
static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;
if (done)
return;
/* All fall through to do_early_param. */
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE); #NOTE:拷贝boot_command_line,为解析early参数做准备
parse_early_options(tmp_cmdline); #NOTE:解析命令行参数
done = 1;
}
void __init parse_early_options(char *cmdline)
{
#NOTE:这里可以看出,第一次和第二次参数解析调用函数一致,只是携带的处理函数不一样,这里携带的是do_early_param()回调函数。
parse_args("early options", cmdline, NULL, 0, 0, 0, do_early_param); }
/* Check for early params. */
static int __init do_early_param(char *param, char *val, const char *unused)
{
const struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++) { #NOTE:这里也是以.init.setup段中的__setup_start和__setup_end为搜索边界
if ((p->early && parameq(param, p->str)) ||
(strcmp(param, "console") == 0 &&
strcmp(p->str, "earlycon") == 0)
) {
if (p->setup_func(val) != 0)
pr_warn("Malformed early option '%s'\n", param);
}
}
/* We accept everything at this stage. */
return 0;
}
命令行解析函数:
--/kernel/params.c
/* Args looks like "foo=bar,bar2 baz=fuz wiz". */
char *parse_args(const char *doing, char *args, const struct kernel_param *params, unsigned num, s16 min_level, s16 max_level, int (*unknown)(char *param, char *val, const char *doing))
{
char *param, *val;
/* Chew leading spaces */
args = skip_spaces(args);
if (*args)
pr_debug("doing %s, parsing ARGS: '%s'\n", doing, args);
while (*args) {
int ret;
int irq_was_disabled;
args = next_arg(args, ?m, &val); #NOTE: 此处进行参数分割
/* Stop at -- */
if (!val && strcmp(param, "--") == 0)
return args;
irq_was_disabled = irqs_disabled();
ret = parse_one(param, val, doing, params, num,
min_level, max_level, unknown);
if (irq_was_disabled && !irqs_disabled())
pr_warn("%s: option '%s' enabled irq's!\n",
doing, param);
switch (ret) {
case -ENOENT:
pr_err("%s: Unknown parameter `%s'\n", doing, param);
return ERR_PTR(ret);
case -ENOSPC:
pr_err("%s: `%s' too large for parameter `%s'\n",
doing, val ?: "", param);
return ERR_PTR(ret);
case 0:
break;
default:
pr_err("%s: `%s' invalid for parameter `%s'\n",
doing, val ?: "", param);
return ERR_PTR(ret);
}
}
/* All parsed OK. */
return NULL;
}
(NOTE:此处以args 为"board=PIONEER-9531 console=ttyS0,115200 rootfstype=squashfs,jffs2 noinitrd"进行分析)
static char *next_arg(char *args, char **param, char **val)
{
unsigned int i, equals = 0;
int in_quote = 0, quoted = 0;
char *next;
if (*args == '"') { #NOTE:第一次调用此函数时,此条件不成立,*args='b'
args++;
in_quote = 1;
quoted = 1;
}
for (i = 0; args[i]; i++) {
if (isspace(args[i]) && !in_quote) #NOTE:第一次调用时in_quote为0,直接跳出
break;
if (equals == 0) {
if (args[i] == '=')
equals = i;
}
if (args[i] == '"')
in_quote = !in_quote;
}
*param = args;
if (!equals) #NOTE:第一次调用时,变量equals为0,满足条件
*val = NULL;
else {
args[equals] = '\0';
*val = args + equals + 1;
/* Don't include quotes in value. */
if (**val == '"') {
(*val)++;
if (args[i-1] == '"')
args[i-1] = '\0';
}
}
if (quoted && args[i-1] == '"')#NOTE:第一次调用时,变量quoted为0,不满足条件
args[i-1] = '\0';
if (args[i]) {
args[i] = '\0';
next = args + i + 1;
} else
next = args + i;
/* Chew up trailing spaces. */
return skip_spaces(next);
}
2.3 Openwrt patch-cmdline机制
Openwrt在原有内核的的bootargs配置的基础上,添加了CMDLINE_HACK的方式,若需要使用此方式需要选中[*] OpenWrt specific image command line hack选项。内核在编译固件的时候会使用patch-cmdline工具添加bootargs.
其中patch-cmdline工具的作用方式如下,(编译输出信息)
staging_dir/host/bin/patch-cmdline build_dir/target-mips_34kc_musl-1.1.11/linux-ar71xx_generic/pioneer-qca9531-64m-16m-kernel.bin 'board=PIONEER-9531 console=ttyATH0,115200'
其中,patch-cmdline工具源码如下:
tools/patch-image/src/patch-cmdline.c
int main()
{
......
if (((fd = open(argv[1], O_RDWR)) < 0) ||
(ptr = (char *) mmap(0, SEARCH_SPACE + CMDLINE_MAX, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == (void *) (-1)) { #NOTE:此处利用mmap将固件内容映射出来
fprintf(stderr, "Could not open kernel image");
goto err2;
}
for (p = ptr; p < (ptr + SEARCH_SPACE); p += 4) {
if (memcmp(p, "CMDLINE:", 8) == 0) { #NOTE:此处匹配head.s中设置的标记字符CMDLINE
found = 1;
p += 8;
break;
}
}
if (!found) {
fprintf(stderr, "Command line marker not found!\n");
goto err3;
}
memset(p, 0, CMDLINE_MAX - 8); #NOTE:下述利用调用工具时传入的参数替换固件中的bootargs
strcpy(p, argv[2]);
msync(p, CMDLINE_MAX, MS_SYNC|MS_INVALIDATE);
ret = 0;
......
}
Openwrt的CMDLINE_HACK机制是在head.S中添加标记字符CMDLINE:和预留其空间的方式进行实现,具体实现方式如下:
--arch/mips/kernel/head.S
#ifdef CONFIG_IMAGE_CMDLINE_HACK
.ascii "CMDLINE:" #NOTE: 定义一个"CMDLINE:" ascii标记字符串,注意没有使用asciiz,末尾没有\0
EXPORT(__image_cmdline)#NOTE: 到处一个符号标识,这里可以看成是后面1024字节数组的数组名称
.fill 0x400 #NOTE: 在字符串标记后,开辟1024B的空间,用于存放bootargs
#endif /* CONFIG_IMAGE_CMDLINE_HACK */
--arch/mips/ath79/prom.c
void __init prom_init(void)
{
const char *env;
if (ath79_prom_init_myloader())
return;
if (!ath79_use_image_cmdline())
fw_init_cmdline();
......
}
#ifdef CONFIG_IMAGE_CMDLINE_HACK #NOTE:这里是CMDLINE_HACK的配置开关
extern char __image_cmdline[];
static int __init ath79_use_image_cmdline(void)
{
char *p = __image_cmdline; #NOTE:指向head.s中的1024字节的bootargs区域.
int replace = 0;
if (*p == '-') { #NOTE: 用patch-cmdline工具打入的bootargs,若以-开头,表示强制覆盖,否则按追加处理.
replace = 1;
p++;
}
if (*p == '\0') #NOTE: 若bootargs中的字符串开头即为\0,则立即返回
return 0;
if (replace) { #NOTE: 根据上述bootargs是否前缀-来控制是替换还是追加到arcs_cmdline
strlcpy(arcs_cmdline, p, sizeof(arcs_cmdline));
} else {
strlcat(arcs_cmdline, " ", sizeof(arcs_cmdline));
strlcat(arcs_cmdline, p, sizeof(arcs_cmdline));
}
/* Validate and setup environment pointer */
if (fw_arg2 < CKSEG0) #NOTE:重新校正环境变量指针
_fw_envp = NULL;
else
_fw_envp = (int *)fw_arg2;
return 1;
}
#else
static inline int ath79_use_image_cmdline(void) { return 0; }
#endif
------未完待续