kernel的启动流程分析(未完)

来源:互联网 发布:魔术师约翰逊数据统计 编辑:程序博客网 时间:2024/05/14 08:04

文章只做学习记录。。。。。

先说一下内核启动时的工作:

1.处理uboot传入的参数(比如mem的大小,bootargs的参数)

2.挂接根文件系统,启动应用程序。


在内核启动之前有可能会有一个自解压的操作:

因为有可能编译出来的内核比较大,在运行内核之前会先进行自解压的操作,自解压的那段代码不分析(以后有时间分析)。

那么我们就首先分析下kernel是如何处理uboot传入的参数的,文件arch/arm/kernel/head.S


分析之前,先看看uboot是如何启动内核的,
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
这段指令跳转到了内核的入口地址,并且传入了三个参数,0,bd->bi_arch_number(机器ID),bd->bi_boot_params(TAG传入的起始地址)。
由寄存器的交互规则可知bd->bi_arch_number是存放在r1寄存器里的(具体的交互规则以后分析)。

ENTRY(stext)   //这个地址等同于theKernelmsrcpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode@ and irqs disabledmrcp15, 0, r9, c0, c0@ get processor idbl__lookup_processor_type@ r5=procinfo r9=cpuidmovsr10, r5@ invalid processor (r5=0)?beq__error_p@ yes, error 'p'bl__lookup_machine_type@ r5=machinfomovsr8, r5@ invalid machine (r5=0)?beq__error_a@ yes, error 'a'bl__create_page_tables/* * The following calls CPU specific code in a position independent * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of * xxx_proc_info structure selected by __lookup_machine_type * above.  On return, the CPU will be ready for the MMU to be * turned on, and r0 will hold the CPU control register value. */ldrr13, __switch_data@ address to jump to after@ mmu has been enabledadrlr, __enable_mmu@ return (PIC) addressaddpc, r10, #PROCINFO_INITFUNC

上面的代码主要做的工作如下:
一、判断是否支持这个CPU  __lookup_processor_type
二、__lookup_machine_type:判断是否支持这个单板,即bd->bi_arch_number
下面看看函数__lookup_machine_type的实现:

3: .long .                                            //3b的地址 .long __arch_info_begin .long __arch_info_end
 
__lookup_machine_type:adrr3, 3b                              //r3 == 3b的地址,真正的地址,物理地址ldmiar3, {r4, r5, r6}                    //r3 == '.'(3b的虚拟地址) r5 == __arch_info_begin r6 == __arch_info_endsubr3, r3, r4@ get offset between virt&phys 获取虚拟地址与物理地址偏差addr5, r5, r3@ convert virt addresses to    获取r5和r6的物理地址addr6, r6, r3@ physical address space1:ldrr3, [r5, #MACHINFO_TYPE]@ get machine typeteqr3, r1@ matches loader number?   判断是否和uboot传进来的机器id匹配beq2f@ found                    匹配的话,直接跳出   addr5, r5, #SIZEOF_MACHINE_DESC@ next machine_desccmpr5, r6blo1bmovr5, #0@ unknown machine2:movpc, lr

 

解析:
r3 = 3b的地址,即:
3: .long .
 .long __arch_info_begin
 .long __arch_info_end
__arch_info_begin和__arch_info_end分别是什么???
搜索一下发现这两个变量的定义在链接脚本中:

__arch_info_begin = .;*(.arch.info.init)__arch_info_end = .;

在这两个地址之间有一个*(.arch.info.init)段,这是一个什么段呢?继续搜索:发现在include\asm-arm\mach\Arch.h中定义了这个段的属性:

#define MACHINE_START(_type,_name)   \static const struct machine_desc __mach_desc_##_type \ __used       \ __attribute__((__section__(".arch.info.init"))) = { \ .nr  = MACH_TYPE_##_type,  \ .name  = _name,#define MACHINE_END    \};定义了一个宏MACHINE_START(_type,_name)结构体machine_desc放在了.arch.info.init段中。那么继续搜索MACHINE_START,发现在arch\arm\mach-s3c2440\Mach-smdk2440.c使用到了这个宏:代码如下:MACHINE_START(S3C2440, "SMDK2440") /* Maintainer: Ben Dooks <ben@fluff.org> */ .phys_io = S3C2410_PA_UART, .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, .boot_params = S3C2410_SDRAM_PA + 0x100, .init_irq = s3c24xx_init_irq, .map_io  = smdk2440_map_io, .init_machine = smdk2440_machine_init, .timer  = &s3c24xx_timer,MACHINE_END下面将这段代码展开:static const struct machine_desc __mach_desc_S3C2440 \ __used       \ __attribute__((__section__(".arch.info.init"))) = { \   ====>属性就是将段强制转换为了.arch.info.init .nr  = MACH_TYPE_S3C2440,  \   ====>这就是uboot传进来的机器ID,将它存放在了.arch.info.init段内 .name  = "SMDK2440", .phys_io = S3C2410_PA_UART, .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, .boot_params = S3C2410_SDRAM_PA + 0x100, //=====>这个就是0x30000100也就是bi->boot_params,所以感觉uboot传进来的TAG地址是不是根据内核中的这个地址决定的??? .init_irq = s3c24xx_init_irq, .map_io  = smdk2440_map_io, .init_machine = smdk2440_machine_init, .timer  = &s3c24xx_timer,};

下面列出machine_desc的定义

struct machine_desc {/* * Note! The first four elements are used * by assembler code in head-armv.S */unsigned intnr;/* architecture number*/unsigned intphys_io;/* start of physical io*/unsigned intio_pg_offst;/* byte offset for io  * page tabe entry*/const char*name;/* architecture name*/unsigned longboot_params;/* tagged list*/unsigned intvideo_start;/* start of video RAM*/unsigned intvideo_end;/* end of video RAM*/unsigned intreserve_lp0 :1;/* never has lp0*/unsigned intreserve_lp1 :1;/* never has lp1*/unsigned intreserve_lp2 :1;/* never has lp2*/unsigned intsoft_reboot :1;/* soft reboot*/void(*fixup)(struct machine_desc *, struct tag *, char **, struct meminfo *);void(*map_io)(void);/* IO mapping function*/void(*init_irq)(void);struct sys_timer*timer;/* system tick timer*/void(*init_machine)(void);};

对于内核支持的单板的machine_desc信息都会存放在.arch.info.init段中,我们的这个单板的ID为MACH_TYPE_S3C2440,通过__lookup_machine_type进行匹配

2440 2410 所支持的单板信息都保存在.arch.info.init段中,然后从__arch_info_begin 到__arch_info_end中的信息依次和机器ID比对。如果吻合的话。表示这个内核支持这个单板。

到这里uboot传进来的机器ID和内核已经匹配了。

三、建立页表,启动mmu  。__create_page_tables 和 __enable_mmu
四、跳转到start_kernel(这也是内核的第一个c函数start_kernel)

ldr r13, __switch_data

     __mmap_switched   =========> start_kernel

内核的启动流程参照开发手册上面的一幅图


大致总结一下内核的启动流程:

arch/am/kernel/head.Sstart_kernel        setup_arch   //解析uboot传入的启动参数        setup_command_line(command_line);//解析uboot传入的启动参数        parse_early_param                do_early_param //从 __setup_start到 __setup_end;执行标志early的函数,这个没哟走        obsolete_checksetup   //走的是这个从 __setup_start到 __setup_end;执行标志非early的函数        rest_init              kernel_init                   prepare_namespace                            mount_root //挂接根文件系统                   init_post //执行应用程序 

 

在挂接根文件系统的时候内核是如何识别的??
root=/dev/mtdblock3

static int __init root_dev_setup(char *line){ strlcpy(saved_root_name, line, sizeof(saved_root_name)); return 1;}__setup("root=", root_dev_setup);

继续搜索看看__setup()宏的实现:定义在: include\linux\Init.h

#define __setup(str, fn)\__setup_param(str, fn, fn, 0)

__setup_param的定义:

#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 }

__setup("root=", root_dev_setup);展开:

#define __setup_param("root=", root_dev_setup, root_dev_setup, 0)\static char __setup_str_root_dev_setup[] __initdata = "root=";\static struct obs_kernel_param __setup_root_dev_setup\__attribute_used__\__attribute__((__section__(".init.setup")))\__attribute__((aligned((sizeof(long)))))\= { __setup_str_root_dev_setup, root_dev_setup, 0}

定义了一个.init.setup段obs_kernel_param类型的结构体

定义如下:

struct obs_kernel_param {const char *str;int (*setup_func)(char *);int early;};

我们先搜索:

  __setup_start = .;  .init.setup : { *(.init.setup) }  __setup_end = .;


继续搜索__setup_start的使用地方(发现有两个地方用到了):
第一个地方:

static int __init obsolete_checksetup(char *line){struct obs_kernel_param *p;int had_early_param = 0;p = __setup_start;do {int n = strlen(p->str);if (!strncmp(line, p->str, n)) {if (p->early) {/* 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) {printk(KERN_WARNING "Parameter %s is obsolete,"       " ignored\n", p->str);return 1;} else if (p->setup_func(line + n))return 1;}p++;} while (p < __setup_end);return had_early_param;}


第二个地方:

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在p->early == 1的时候才会执行p->setup_func(val),而我们传下来的p->early == 0,所以使用的是第一个地方obsolete_checksetup,这个p->setup_func就是前面所讲的
root_dev_setup,这个函数就将分区的信息赋值给了 saved_root_name

 

root=/dev/mtdblock3也是在内核中写死的。要和uboot对应
grep -inr "\"Bootloader\"" *

本板子的mtd分区定义在 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,}};
原创粉丝点击