前言:
本帖是学习韦东山老师linux kernel教程,粗略总结的课堂笔记及自己感悟。如想深入了解linux kernel,请绕路。
写到最后实在写不动了,太难写了。。。
本文分析重点:
1- 内核处理uboot传入的启动参数
2- 挂载文件系统
相关帖子:
1- 内核启动流程概述
从arch/arm/kernel/head.S文件调用start_kernel(),进入内核启动第二阶段。该阶段继续做一些内存、串口、时钟、中断等初始化后,最后挂载文件系统,运行应用程序。内核的最终目的也是运行应用程序。
在arch/arm/kernel/head.S中已经处理了uboot传入的机器ID,并根据机器ID找到了 struct machine_desc __mach_desc_S3C2440结构体。uboot传入的启动参数将在start_kernel中处理。
除了处理uboot传入的启动参数,内核还会挂载根文件系统、启动应用程序。内核启动的最终目的是启动应用程序。
#linux-2.6.22.6\init\main.c
start_kernel
/* 处理uboot传入启动参数 */
setup_arch(&command_line);
setup_command_line(command_line);
/* 处理uboot传入earlt属性启动参数 */
parse_early_param();
do_early_param
/* 处理uboot传入非earlt属性启动参数 */
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
/* 挂载根文件系统 启动应用程序 */
rest_init();
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
init_post();
2- 处理uboot传入的参数
根据机器ID machine_arch_type,找到单板描述结构体struct machine_desc __mach_desc_S3C2440
#linux-2.6.22.6\arch\arm\kernel\setup.c
setup_arch(&command_line);
/* 找到struct machine_desc __mach_desc_S3C2440结构体 */
mdesc = setup_machine(machine_arch_type);
/* 找到 boot_params信息,并进行物理地址->虚拟地址映射。 .boot_params = S3C2410_SDRAM_PA + 0x100=0x3000 0100 */
if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);
/* ??? 2440好像没有定义mdesc->fixup */
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
/* 处理uboot的 struct tag信息 */
/* 将uboot传递的boot_params保存到boot_command_line */
memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
/* 初始化.early_param.init属性的参数 */
parse_cmdline(cmdline_p, from);
2.1- 参数起始地址
struct machine_desc __mach_desc_S3C2440保存了单板的初始化、uboot传入参数的物理起始地址等
/* .boot_params=0x3000 0100 */
.boot_params = S3C2410_SDRAM_PA + 0x100,
SDRAM中uboot传入起始地址分布(参考帖子uboot启动第二阶段):
2.2- 预处理early参数处理(每太明白要干什么)
parse_cmdline 会先处理具有__early_begin和__early_end段之间的参数,链接脚本中定义如下:
__early_begin = .;
*(.early_param.init)
__early_end = .;
源码中搜寻early_param.init,可知"initrd="、"mem="等命令会被放置在__early_begin和__early_end段之间。根据之前分析,这种段属性的信息为某个结构体struct early_params __early_##fn __used。
root@book-virtual-machine:/home/lhk/2440_learn/kernel/linux-2.6.22.6/linux-2.6.22.6# grep "__early_param" -nR ./
./include/asm/setup.h:220:#define __early_param(name,fn) \
./include/asm-arm/setup.h:220:#define __early_param(name,fn) \
./arch/arm/kernel/setup.c:437:__early_param("initrd=", early_initrd);
./arch/arm/kernel/setup.c:482:__early_param("mem=", early_mem);
./arch/arm/mm/mmu.c:120:__early_param("cachepolicy=", early_cachepolicy);
./arch/arm/mm/mmu.c:128:__early_param("nocache", early_nocache);
./arch/arm/mm/mmu.c:136:__early_param("nowb", early_nowrite);
./arch/arm/mm/mmu.c:148:__early_param("ecc=", early_ecc);
./arch/arm26/kernel/vmlinux-arm26.lds.in:36: *(__early_param)
./arch/arm26/kernel/vmlinux-arm26-xip.lds.in:35: *(__early_param)
#./include/asm-arm/setup.h:220
#define __early_param(name,fn) \
static struct early_params __early_##fn __used \
__attribute__((__section__(".early_param.init"))) = { name, fn }
/*
* Initial parsing of the command line.
*/
static void __init parse_cmdline(char **cmdline_p, char *from)
{
char c = ' ', *to = command_line;
int len = 0;
for (;;) {
if (c == ' ') {
extern struct early_params __early_begin, __early_end;
struct early_params *p;
for (p = &__early_begin; p < &__early_end; p++) {
int len = strlen(p->arg);
if (memcmp(from, p->arg, len) == 0) {
if (to != command_line)
to -= 1;
from += len;
p->fn(&from);
while (*from != ' ' && *from != '\0')
from++;
break;
}
}
}
c = *from++;
if (!c)
break;
if (COMMAND_LINE_SIZE <= ++len)
break;
*to++ = c;
}
*to = '\0';
*cmdline_p = command_line;
}
2.3 处理early命令parse_early_param()
parse_early_param
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_args("early options", tmp_cmdline, NULL, 0, do_early_param);
/* Check for early params. */
static int __init do_early_param(char *param, char *val)
{
struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++) {
if (p->early && strcmp(param, p->str) == 0) {
if (p->setup_func(val) != 0)
printk(KERN_WARNING
"Malformed early option '%s'\n", param);
}
}
/* We accept everything at this stage. */
return 0;
}
do_early_param接口中处理__setup_start到__setup_end段之间内容。
查看链接脚本,定义如下:
__setup_start = .;
*(.init.setup)
__setup_end = .;
显然do_early_param处理的的命令被定义为.init.setup段属性的结构体中。
代码中搜寻.init.setup,可知do_early_param执行的命令包括使用__setup_null_param、__obsolete_setup、__setup宏定的代码。
#linux-2.6.22.6\include\linux\init.h
/*
* 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 char __setup_str_##unique_id[] __initdata = str; \
static struct obs_kernel_param __setup_##unique_id \
__attribute_used__ \
__attribute__((__section__(".init.setup"))) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }
#define __setup_null_param(str, unique_id) \
__setup_param(str, unique_id, NULL, 0)
#define __obsolete_setup(str) \
__setup_null_param(str, __LINE__)
#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)
/* NOTE: fn is as per module_param, not __setup! Emits warning if fn
* returns non-zero. */
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
例如我们传入的参数,root=/dev/mtdblock3、init=/linuxrc参数,均是需要在do_early_param接口中处理的,处理函数分别为root_dev_setup、init_setup。
bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200
__setup("root=", root_dev_setup);
__setup("init=", init_setup);
2.4 处理非early命令unknown_bootoption
略
3- 挂接文件系统
rest_init
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
kernel_init
/* 挂载文件系统 */
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
/* 执行应用程序 */
init_post();
3.1- 挂载应用程序
参考:第3阶段——内核启动分析之prepare_namespace()如何挂载根文件系统和mtd分区介绍(6) - 诺谦 - 博客园
uboot传入参数表明文件系统被挂载在:root=/dev/mtdblock3
这个是Nandflash的第三个分区,是认为规定的。代码定义处:
# linux-2.6.22.6\arch\arm\plat-s3c24xx\common-smdk.c
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
.name = "bootloader",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "params",
.offset = MTDPART_OFS_APPEND,
.size = 0x00020000,
},
[2] = {
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00200000,
},
[3] = {
.name = "root",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
};
内核启动过程中也有打印扇区情况:
Creating 4 MTD partitions on "NAND 256MiB 3,3V 8-bit":
0x00000000-0x00040000 : "bootloader"
0x00040000-0x00060000 : "params"
0x00060000-0x00260000 : "kernel"
0x00260000-0x10000000 : "root"
3.2- 执行应用程序
/* This is a non __init function. Force it to be noinline otherwise gcc
* makes it inline to init() and it becomes part of init.text section
*/
static int noinline init_post(void)
{
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel.");
}