学习笔记 --- LINUX内核启动第二阶段分析(不考虑自解压过程)

来源:互联网 发布:网络霸气名字大全男孩 编辑:程序博客网 时间:2024/04/30 00:20

上篇文章中分析了Linux内核从head.s启动:

.section ".text.head", "ax"ENTRY(stext)setmodePSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ 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__vet_atagsbl__create_page_tables

1 开始设置ARM模式为管理模式,禁止中断,然后获取处理器ID

2 调用__lookup_processor_type 判断内核是否支持该处理器,这里略,不讲!

3 处理器ID验证完后调用  __lookup_machine_type  判断内核是否支持该机器类型:

__lookup_machine_type:adrr3, 4bldmiar3, {r4, r5, r6}subr3, r3, r4@ get offset between virt&physaddr5, r5, r3@ convert virt addresses toaddr6, r6, r3@ physical address space1:ldrr3, [r5, #MACHINFO_TYPE]@ get machine typeteqr3, r1@ matches loader number?beq2f@ foundaddr5, r5, #SIZEOF_MACHINE_DESC@ next machine_desccmpr5, r6blo1bmovr5, #0@ unknown machine2:movpc, lrENDPROC(__lookup_machine_type)
第二行4b的位置是:

4:.long..long__arch_info_begin.long__arch_info_end
所以2,3行执行之后:

r4 = .  r5=__arch_info_begin  r6=__arch_info_end
4,5,6行都有解释,目的就是为了计算出__arch_info_begin 和__arch_info_end在SDRAM里面实际存放地址,这两个东西是链接文件里面定义的arch.info.init段的起始和结束标志:

__arch_info_begin = .;*(.arch.info.init)__arch_info_end = .;
而arch.info.init段存放的是一个结构体,从代码可以看出:

/* * Set of macros to define architecture features.  This is built into * a table by the linker. */#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\};

该结构体格式为:

struct machine_desc {/* * Note! The first four elements are used * by assembler code in head.S, head-common.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_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,     //就是0x30000100.init_irq= s3c24xx_init_irq,.map_io= smdk2440_map_io,.init_machine= smdk2440_machine_init,.timer= &s3c24xx_timer,MACHINE_END

还有很多很多不同的单板都定义了这个,可以知道,这个arch.info.init段里面放的就是linux内核支持的所有单板的参数信息,再回到汇编,7-10行就是判断每个单板的机器类型(读到寄存器r3)是否和uboot传入的单板参数(存在寄存器r1)匹配,如果匹配则内核支持该单板。

4 接着执行__vet_atags :

__vet_atags:tstr2, #0x3@ aligned?bne1fldrr5, [r2, #0]@ is first tag ATAG_CORE?cmpr5, #ATAG_CORE_SIZEcmpner5, #ATAG_CORE_SIZE_EMPTYbne1fldrr5, [r2, #4]ldrr6, =ATAG_COREcmpr5, r6bne1fmovpc, lr@ atag pointer is ok1:movr2, #0movpc, lrENDPROC(__vet_atags)
这个函数就是去验证uboot传入的TAG参数区域的ATAG_CORE_SIZE与ATAG_CORE是否和内核的匹配,这段TAG参数起始地址存在寄存器r2,读出来再比较这两项是否一致,如果一致则通过。

5 接下来建立页表

6 使能MMU

最后跳转到 start_kernel 进入C语言代码段,开始打印版本信息,然后执行下面这段代码,主要作用就是解析uboot传入的参数:

void __init setup_arch(char **cmdline_p){struct tag *tags = (struct tag *)&init_tags;struct machine_desc *mdesc;char *from = default_command_line;unwind_init();setup_processor();mdesc = setup_machine(machine_arch_type); //设置机器类型,返回machine_desc结构体,这个结构体通过MACHINE_START设置好了machine_name = mdesc->name;if (mdesc->soft_reboot)reboot_setup("s");if (__atags_pointer)tags = phys_to_virt(__atags_pointer);else if (mdesc->boot_params)  //就是结构体里面的S3C2410_SDRAM_PA + 0x100 = 0x30000100,这个地址存放uboot设置好的TAG参数tags = phys_to_virt(mdesc->boot_params); //转为虚拟地址,返回tags,tags指向uboot存放的TAG参数起始地址/* * If we have the old style parameters, convert them to * a tag list. */if (tags->hdr.tag != ATAG_CORE) convert_to_tag_list(tags);if (tags->hdr.tag != ATAG_CORE)   //如果TAG参数不匹配,则使用内核默认的参数tags = (struct tag *)&init_tags;if (mdesc->fixup)mdesc->fixup(mdesc, tags, &from, &meminfo);if (tags->hdr.tag == ATAG_CORE) {if (meminfo.nr_banks != 0)squash_mem_tags(tags);save_atags(tags);parse_tags(tags);}init_mm.start_code = (unsigned long) _text;init_mm.end_code   = (unsigned long) _etext;init_mm.end_data   = (unsigned long) _edata;init_mm.brk   = (unsigned long) _end;memcpy(boot_command_line, from, COMMAND_LINE_SIZE);boot_command_line[COMMAND_LINE_SIZE-1] = '\0';  parse_cmdline(cmdline_p, from);  //解析命令行,就是解析uboot传入的bootargs环境变量,它为TAG参数的一部分paging_init(mdesc);request_standard_resources(&meminfo, mdesc);#ifdef CONFIG_SMPsmp_init_cpus();#endifcpu_init();tcm_init();/* * Set up various architecture-specific pointers */init_arch_irq = mdesc->init_irq;  //把结构体里的参数都放在独自的变量里单独表示system_timer = mdesc->timer;init_machine = mdesc->init_machine;  #ifdef CONFIG_VT#if defined(CONFIG_VGA_CONSOLE)conswitchp = &vga_con;#elif defined(CONFIG_DUMMY_CONSOLE)conswitchp = &dummy_con;#endif#endifearly_trap_init();}
上面执行完后,返回bootargs参数到cmdline_p,然后再执行下面函数,将命令行地址存放在全局指针saved_command_line里面:

static void __init setup_command_line(char *command_line){saved_command_line = alloc_bootmem(strlen (boot_command_line)+1);static_command_line = alloc_bootmem(strlen (command_line)+1);strcpy (saved_command_line, boot_command_line);strcpy (static_command_line, command_line);}
然后下面会用unknown_bootoption函数来解析这个命令行,比如文件系统挂载到哪个地方,使用串口几作为监控端等;

接下来是一大堆初始化,执行到最后rest_init:

kernel_init ---- prepare_namespace ---- mount_root (挂接根文件系统)

挂接好之后 init_post:

static noinline int init_post(void)__releases(kernel_lock){/* need to finish all async __init code before freeing the memory */async_synchronize_full();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)  //打开终端文件 ----用于标准printf打印printk(KERN_WARNING "Warning: unable to open an initial console.\n");(void) sys_dup(0);  //复制终端文件 用于 标准scanf输入(void) sys_dup(0);  //复制终端文件 用于 标准error打印current->signal->flags |= SIGNAL_UNKILLABLE;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);  //执行uboot传入的“init=xxx”指定的应用脚本printk(KERN_WARNING "Failed to execute %s.  Attempting ""defaults...\n", execute_command);}run_init_process("/sbin/init");  //如果上面uboot没有指定,那么往下执行默认路径的脚本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.");}
上面挂接文件系统mount_root ,挂接到哪里是由环境变量bootargs里面的“root=” 类参数决定的,是通过unknown_bootoption函数解析出来的。

假设uboot传进来参数为  “root=/dev/mtdblock3” 则表示挂载到mtd设备的第4个分区,至于这个分区对应哪里是在nandflash的驱动里面指定的:

static struct mtd_partition friendly_arm_default_nand_part[] = {[0] = {.name= "uboot",.size= 0x00040000,.offset= 0,},[1] = {.name= "param",.offset = 0x00040000,.size= 0x00020000,},[2] = {.name= "Kernel",.offset = 0x00060000,.size= 0x00500000,},[3] = {                 .name= "root",.offset = 0x00560000,.size= 1024 * 1024 * 1024, },[4] = {.name= "nand",.offset = 0x00000000,.size= 1024 * 1024 * 1024, //}};

上面可以知道第4个分区就是root根文件系统分区,这个在一直nand驱动的时候要和uboot传进的参数对应起来。















0 0