内核启动第二阶段

 前言: 

本帖是学习韦东山老师linux kernel教程,粗略总结的课堂笔记及自己感悟。如想深入了解linux kernel,请绕路。

写到最后实在写不动了,太难写了。。。

本文分析重点:

1- 内核处理uboot传入的启动参数

2- 挂载文件系统

 相关帖子:

内核启动第一阶段

内核Makefile文件简单分析

内核配置文件分析--以CONFIG_DM9000为例

内核初体验:编译、下载

uboot启动第二阶段

uboot启动第一阶段

UBOOT初体验:编译、下载

初识uboot Makefile

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.");
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值