Board logo

标题: linux2.3.22.6内核启动第二阶段(start_kernel函数主要流程) [打印本页]

作者: yuyang911220    时间: 2017-2-20 19:09     标题: linux2.3.22.6内核启动第二阶段(start_kernel函数主要流程)

一、前言
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参数进行解析。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0