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: %sn", 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] == '