完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
linux2.3.22.6内核启动第二阶段(start_kernel函数主要流程) [size=12.0000pt]一、前言 UBOOT在特定内存处( 0x30000100)以TAG格式设置好的参数传给了内核,内核当然是要去处理的。之前对于怎么处理一点概念也没有,以至于碰到的许多问题不知道去哪里解决,看了韦东山老师视频关于内核的讲解之后,柳暗花明又一村啊。对内核关于参数的处理,有了很明确的概念与方向,下面及时整理出一点心得,语言表达的不好,勿喷哈。 下面是韦东山老师在课程中对于内核启动的目的,很形象的描述 1、内核启动的终极目的就是运行应用程序,Windows的应用程序一般放在C盘、D盘中, 而我们LIUNUX的应用程序放在根文件系统中,所以内核想运行应用程序就得 首先挂载根文件系统。 [size=14.0000pt]2、之前U-boot辛辛苦苦的设置了一些参数,例如机器ID和以TAG格式存储在地址bi_boot_params开始处,并且以setup_start_tag (bd);开始,以setup_end_tag (bd)结束的参数,(主要就是这两块了)。UBOOT将这些参数传递给内核,内核除了挂载跟文件系统外首先得处理这些参数吧(要不然多对不起U-BOOT啊)。 二、内核2大任务的处理 下面开始正题 首先宏观上大体说一下内核是处理UBOOT传递过来的参数的,以便对处理过程有个整体的“鸟瞰式”的理解。内核对于参数的处理个人理解为:整体上分为2大块: [size=14.0000pt](1)机器ID,这个参数的处理主要在Head.s阶段中的__lookup_machine_type函数中处理。 [size=14.0000pt](2)对于TAG式参数的处理。这一块又分为两步处理。首先是对参数中非bootargs参数的处理及对bootargs参数的处理。也就是说内核对于存放在0x30000100中的参数处理时,bootargs这部分参数是单独拿出来处理的。 下面详细记录内核的C语言部分启动的主要流程,重点记录,内核处理参数的2大块,及挂载根文件系统、挂载根文件系统后启动的第一个应用程序。start_kernel是内核启动后运行的第一个C语言程序,在这里面调用了许多函数,其实大多函数我们没有必要去了解(因为内核太庞大了,等到我们需要时再仔细分析)这里主要针对任务1和2记录内核流程。 smp_setup_processor_id(); //这个函数现在是空的; lockdep_init(); //Runtime locking correctness validator, see Documentation/lockdep_design.txt debug_objects_early_init(); cgroup_init_early(); //Control group, read Documentation/cgroup.txt local_irq_disable(); early_boot_irqs_off(); early_init_irq_lock_class(); /* 基本上面几个函数就是初始化lockdep和cgroup,然后禁止IRQ,为后续的运行 创造条件 */ lock_kernel(); /* 看这个函数的之前我们首先来了解一段知识,linux kernel默认是支持 preemption(抢占)的。在SMP环境下为了实现kernel的锁定,kernel使用了一个 BKL(big kernel lock)的概念,在初始化的过程中先锁定这个BKL,然后再继续 进行其他启动或者初始化过程,这样就可以防止启动过程中被中断,执行到 res_init以后,kernel会释放这个锁,这样就确保了整个start_kernel过程都 不会被抢占或者中断。由此我们可以看到这主要是针对多处理器而言的,实际 上如果只有一个处理器的话它也不会被中断或者被抢占。 */ tick_init(); //和时钟相关的初始化,好像是注册notify事件,没有仔细研究过 boot_cpu_init(); //这个实际上是在SMP环境下选择CPU,这里直接CPUID选择的是0号cpu page_address_init(); //初始化high memory,在arm环境下实际上这个函数是空的,也就是说arm 不支持high memory printk(KERN_NOTICE); printk(linux_banner); //这里的KER_NOTICE是字符串<5>,不太明白它的意思。。。 后面的linux_banner定义在kernel/init/version.c里面, 这里的printk是门高深的学问,(根据韦老师所讲,这个函数不直接打印 输出,而是首先把他放到缓存里免去,等到初始化完console_init后 再打印输出) setup_arch(&command_line); 重量级函数,贴出代码一一分析 void __init setup_arch(char **cmdline_p) { struct tag *tags = (struct tag *)&init_tags; struct machine_desc *mdesc; char *from = default_command_line; setup_processor();//该函数其实就是调用第一阶段head.s中的函数 //lookup_processor_type mdesc = setup_machine(machine_arch_type); //该函数其实就是调用第一阶段head.s中的函数lookup_machine_type //将查找到的对应的机器型号的信息保存在结构体mdesc中,mdesc结构体代码此处也贴出来: struct machine_desc { /* * Note! The first four elements are used * by assembler code in head-armv.S */ unsigned int nr; /* architecture number */ unsigned int phys_io; /* start of physical io */ unsigned int io_pg_offst; /* byte offset for io * page tabe entry */ const char *name; /* architecture name */ unsigned long boot_params; /* tagged list */ unsigned int video_start; /* start of video RAM */ unsigned int video_end; /* end of video RAM */ unsigned int reserve_lp0 :1; /* never has lp0 */ unsigned int reserve_lp1 :1; /* never has lp1 */ unsigned int reserve_lp2 :1; /* never has lp2 */ unsigned int soft_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); };看到了吧,其中就有成员unsigned long boot_params; /* tagged list */ 这里面方的就是存放参数的地址啊!!! machine_name = mdesc->name; //给全局变量machine_name赋值 if (mdesc->soft_reboot) //设置reboot方式,默认是硬启动 reboot_setup("s"); if (mdesc->boot_params) tags = phys_to_virt(mdesc->boot_params);//将参数的物理地址转化为虚拟地址 /* * 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) tags = (struct tag *)&init_tags; //首先判断是不是正确的atag格式,如果是以前老版本的param_struct格式会首先将其转换成tag格式,如果转换以后还是不对,则使用默认的init_tags,这里判断的过程都是根据结构体第一个值是不是ATAG_CORE. if (mdesc->fixup) mdesc->fixup(mdesc, tags, &from, &meminfo); if (tags->hdr.tag == ATAG_CORE) { if (meminfo.nr_banks != 0) squash_mem_tags(tags); parse_tags(tags);//这里要说明一下这里的tags实际上是一个tag的数组或者说队列,里面有多个tag结构体.贴出tag结构体定义如下: struct tag { struct tag_header hdr; struct tag_header { __u32 size; __u32 tag; }; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision;//这些东西不就是U-BOOT中启动内核前设置的参数标记么 struct tag_videolfb videolfb; struct tag_cmdline cmdline; /* * Acorn specific */ struct tag_acorn acorn; /* * DC21285 specific */ struct tag_memclk memclk; } u; }; } //这里首先判断fixup函数指针,这里一般为空,如果不为空就会地用fixup来重新修改memory map,meminfo这个结构体定义在arch/arm/include/asm/setup.h里面,描述了内存块的信息,内存块的个数,每个内存块的起始地址和大小,如果修改了memory map则需要从atag参数里面去掉bootloader传过来的的memory map信息,然后是保存一下atag,这个保存函数在这里实际上是空的,没有做任何操作,最后是对atag参数进行解析。 进入parse_tags(tags);函数: static void __init parse_tags(const struct tag *t) { for (; t->hdr.size; t = tag_next(t)) if (!parse_tag(t)) printk(KERN_WARNING "Ignoring unrecognised tag 0x%08xn", t->hdr.tag); } 对tag数组中的每一个tag依次调用parse_tag(t)函数进行解析,进入parse_tag(t)函数: static int __init parse_tag(const struct tag *tag) { extern struct tagtable __tagtable_begin, __tagtable_end; struct tagtable *t; for (t = &__tagtable_begin; t < &__tagtable_end; t++) if (tag->hdr.tag == t->tag) { t->parse(tag); break; } return t < &__tagtable_end; } parse_tag(t)函数就是在段(此段在连接文件中规划) __tagtable_begin = .; *(.taglist.init) __tagtable_end = .;中依次比较,看看与段中的那个标记相同,如果找到就去调用标记对应的函数。 在内核中搜索“*(.taglist.init)”找到 #define __tag __used __attribute__((__section__(".taglist.init"))) #define __tagtable(tag, fn) static struct tagtable __tagtable_##fn __tag = { tag, fn } 在内核中搜索__tagtable找到 __tagtable(ATAG_MEM, parse_tag_mem32); __tagtable(ATAG_CMDLINE, parse_tag_cmdline); 。。。。。。。。。。。。 等定义,我们组要关心列出的这两个,ATAG_MEM与ATAG_CMDLINE这两个不正是UBOOT中定义的两个标记么。 通过以上分析其实解析参数的过程就是这个样子的:通过宏__tagtable()将各种标记及处理该标记的函数对应起来,通过附加强制属性将其放到段 __tagtable_begin = .; *(.taglist.init) __tagtable_end = . 中,解析函数时就是通过标记好在该段中查找,如若找到标记号,就通过该标记号对应的函数做进一步处理。 ATAG_MEM,对应的函数为:parse_tag_mem32,该函数实现功能就是:根据内存tag定义的内存起始地址、长度。在全局结构变量meminfo中增加内存的描述信息,以后内核就可以通过meminfo结构了解开发板的内存信息。 ATAG_CMDLINE对应的函数为parse_tag_cmdline这个函数实现的功能主要是:简单的将命令行tag的内容复制到字符串default_command_line(set_arch函数开头定义了这个变量)中。 接着往下分析 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; // 这就就是对init_mm结构体进行赋值,具体不了解这些东西咋用的,但是就是将text和data段给赋值了 memcpy(boot_command_line, from, COMMAND_LINE_SIZE);//将命令行信息复制给变量boot_command_line boot_command_line[COMMAND_LINE_SIZE-1] = ' |