微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 嵌入式设计讨论 > 嵌入式系统设计讨论 > linux2.3.22.6内核启动第二阶段(start_kernel函数主要流程)

linux2.3.22.6内核启动第二阶段(start_kernel函数主要流程)

时间:10-02 整理:3721RD 点击:

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%08x\n",

                                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] = '\0';

        parse_cmdline(cmdline_p, from);

//扫描命令行参数,对其中的一些参数做先期处理,这些参数用__early_param定义,

__early_param("mem=", early_mem);

__early_param("initrd=", early_initrd);

放到了

段:

        __early_begin = .;

                        *(.early_param.init)

                __early_end = .;               ( 脚本文件中规划好的)

中。(注意是命令行内部的参数处理,例如命令行参数中若含有“men=”字符串就会调用函数early_mem)

        paging_init(&meminfo, mdesc);//根据前面解析内存tag(arse_tag_mem32,该函数在全局结构变量meminfo中增加内存的描述信息)及setup_machine函数返回的mdesc。

        request_standard_resources(&meminfo, mdesc);

好了,到此为止下面的就不分析了,对我们理解参数解析作用不大了,现在来总结一下

内核解析参数,总的来说分两块,第一解析除命令行意外的标记,如内存标记等,命令行参数标记是单独拿出来解析的,而且还进行了先期处理。

#ifdef CONFIG_SMP

        smp_init_cpus();

#endif

        cpu_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

#endif

#if        defined(CONFIG_KGDB)

        extern void __init early_trap_init(void);

        early_trap_init();

#endif

}

回到函数start_kernel中接着往下看,下面还有两个与参数解析相关的函数。

函数:setup_command_line(command_line);

里面将boot_command_line内容复制给了saved_command_line

将command_line内容复制给了static_command_line

接着在函数start_kernel内往下看:

函数parse_early_param();

函数parse_args("Booting kernel", static_command_line, __start___param,

                   __stop___param - __start___param,

                   &unknown_bootoption);

函数parse_args调用函数unknown_bootoption对命令行参数进行一一解析。怎么解析呢,

跟进去,进入函数unknown_bootoption,调用了函数obsolete_checksetup,进入函数obsolete_checksetup,函数obsolete_checksetup实现的功能就是,将命令行中的参数在段

        __setup_start = .;

                        *(.init.setup)

                __setup_end = .;(在连接文件中已规划好)

中进行一一比对,若找到了就调用参数所指定的函数进行处理。具体实现见如下分析:

在内核中搜索.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 }

接着再内核中搜索“ __setup_param”找到了:如下的宏:

#define __setup_null_param(str, unique_id)                        \

        __setup_param(str, unique_id, NULL, 0)

#define __setup(str, fn)                                        \

        __setup_param(str, fn, fn, 0)

#define early_param(str, fn)                                        \

        __setup_param(str, fn, fn, 1)

继续跟进搜索“__setup”你会找到诸如一下这样的一系列的宏定义:

__setup("root=", root_dev_setup);

__setup("rootflags=", root_data_setup);

__setup("rootfstype=", fs_names_setup);

__setup("rootdelay=", root_delay_setup);

__setup("console=", console_setup);绿色部分不正是我们命令行中的字符串么!

执行完函数parse_args后,命令行参数就解析完了,至此UBOOT传递过来的所有参数也就都解析完毕了。

回到start_kernel函数中

找到函数console_init();这也是个比较重要的函数,具体分析见韦东山老师编写的《嵌入式Linux应用开发完全手册》:P325页。

start_kernel函数最后调用到了rest_init();函数,rest_init();函数又调用到了函数kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);相当于执行函数:kernel_init,函数kernel_init又调用到了函数prepare_namespace();,函数prepare_namespace();最终调用到了mount_root();挂载根文件系统,挂载成功后返回到函数prepare_namespace()中,又调用到了init_post函数,在函数init_post中调用

run_init_process("/sbin/init");

        run_init_process("/etc/init");

        run_init_process("/bin/init");

        run_init_process("/bin/sh");

开始执行应用程序。


Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top