首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

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

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

进入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");
开始执行应用程序。
继承事业,薪火相传
返回列表