openwrt命令和Linux一样吗,Openwrt Linux __setup() 处理机制及命令行参数的解析机制

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

------未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值