转自:
http://blog.csdn.net/zhenwenxian/article/details/5988729
Linux允许用户通过bootloader传递内核配置选项给内核,例如
console=ttyS1,115200n8 androidboot.hardware=eee_701 vga=788
内核在初始化过程中调用parse_args函数对这些选项进行解析,并调用相应的处理函数。
内核启动时怎么处理启动参数的了:通过__setup宏定义obs_kernel_param结构变量都被放入.init.setup段中,这样一来实际是使.init.setup段变成一张表,Kernel在处理每一个启动参数时,都会来查找这张表,与每一个数据项中的成员str进行比较,如果完全相同,就会调用该数据项的函数指针成员setup_func所指向的函数(该函数是在使用__setup宏定义该变量时传入的函数参数),并将启动参数如root=后面的内容传给该处理函数。
精髓是利用__setup宏建立了字符和对应处理函数集合的表,parse_args函数依次将命令行参数与这个表比较,找到匹配了就执行对应的处理函数。
__setup宏的来源及使用
内核组件用__setup宏来注册关键字及相关联的任何文本都会作为输入传给
function_handler。
不同的内核选项可以关联相同的处理函数,比如内核选项netdev和ether都关联了netdev_boot_setup函数。
理。
两次解析
相应于__setup宏和early_param宏两种注册形式,内核在初始化时,调用了两次parse_args函数进行解析。
parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
parse_args的第一次调用就在parse_early_param函数里面,为什么会出现两次调用parse_args的情况?这是因为内核选项又分成了两种,就像现实世界中的我们,一种是普普通通的,一种是有特权的,有特权的需要在普通选项之前进行处理。
使用early_param声明的那些选项就会首先由parse_early_param去解析。
下面的例子来自于init/do_mounts.c,其中root_dev_setup作为处理程序被注册给“root=”关键字:
__setup("root=", root_dev_setup);
比如我们在启动向参数终有
noinitrd root=/dev/mtdblock2 console=/linuxrc
setup_arch解释时会发现root=/dev/mtdblock2,然后它就会调用root_dev_setup
static int __init root_dev_setup(char *line)
{
strlcpy(saved_root_name, line, sizeof(saved_root_name));
return 1;
}
我们容易看出,这个函数就是给saved_root_name赋值,saved_root_name这个全局变量非常重要,
比如printk。cstatic int __init console_setup(char *str)
{
char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */
char *s, *options, *brl_options = NULL;
int idx;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
if (!memcmp(str, "brl,", 4)) {
brl_options = "";
str += 4;
} else if (!memcmp(str, "brl=", 4)) {
brl_options = str + 4;
str = strchr(brl_options, ',');
if (!str) {
printk(KERN_ERR "need port name after brl=/n");
return 1;
}
*(str++) = 0;
}
#endif
/*
* Decode str into name, index, options.
*/
if (str[0] >= '0' && str[0] <= '9') {
strcpy(buf, "ttyS");
strncpy(buf + 4, str, sizeof(buf) - 5);
} else {
strncpy(buf, str, sizeof(buf) - 1);
}
buf[sizeof(buf) - 1] = 0;
if ((options = strchr(str, ',')) != NULL)
*(options++) = 0;
#ifdef __sparc__
if (!strcmp(str, "ttya"))
strcpy(buf, "ttyS0");
if (!strcmp(str, "ttyb"))
strcpy(buf, "ttyS1");
#endif
for (s = buf; *s; s++)
if ((*s >= '0' && *s <= '9') || *s == ',')
break;
idx = simple_strtoul(s, NULL, 10);
*s = 0;
ARM linux 系统通过bootable通过命令行参数传给内核kernel,内核启动时读出命令行参数cmdline,通过start_kernel对命令行处理。
如果为了实现自己的特殊功能,通过bootloader传参数给内核,对内核进行特殊的操作,就要增加代码。首先在bootloader要在命令行增加对应的参数项。另外还要改内核增加对这个参数的处理函数。比如增加一个参数,用来设置开机模式。在bootloader的命令行增加boot_mode=test。
在内核增加
int saved_boot_mode;
static int __init boot_mode_setup(char *str)
{
char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */
char *s, *options, *brl_options = NULL;
if (!strcmp(str, "test"))
saved_boot_mode=str;
return 1;
}
__setup("boot_mode=", boot_mode_setup);
怎样通过__setup宏就将参数boot_mode和对应处理函数加入到了init。tab 区,执行start_kernel的时候对bootloader的命令行进行解析,解析到字符boot_mode就执行boot_mode_setup函数。
这样内核就可以知道命令行参数boot_mode的内容了。