初步了解Linux内核 (3)

对于start_kernel函数的具体内容如下:

asmlinkage void __init start_kernel(void)
{
    char * command_line;
    extern struct kernel_param __start___param[], __stop___param[];

    ...

    printk(linux_banner);
    setup_arch(&command_line);

    ...

    parse_early_param();
    parse_args("Booting kernel", static_command_line, __start___param,
           __stop___param - __start___param,
           &unknown_bootoption);

    ...

    /* Do the rest non-__init'ed, we're now alive */
    rest_init();
}

在start_kernel函数中,会调用setup_arch函数,具体内容如下:

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();
    mdesc = setup_machine(machine_arch_type);
    machine_name = mdesc->name;

    if (mdesc->soft_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;

    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);
    }

    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);

    ...

}

在该函数中通过调用setup_machine(machine_arch_type),以板子的机器ID找到struct machine_desc类型的结构体,里面指出了Uboot传入的参数所保存的地址位置。接着调用tags = phys_to_virt(mdesc->boot_params),内核去该地址上获取Uboot传入的参数,然后解析这些参数。

接着调用以下两条语句:

parse_early_param();
parse_args("Booting kernel", static_command_line, __start___param, __stop___param - __start___param, &unknown_bootoption);

这两条指令都是从__setup_start与__setup_end之间取出相应的数据结构后然后进行处理。

至于__setup_start与__setup_end则是在链接脚本中定义的,打开链接脚本可以看见有以下几句话:

 __setup_start = .;
   *(.init.setup)
  __setup_end = .

在__setup_start与__setup_end之间存放.init.setup段,这里有一个疑问,这些数据结构内容是如何放到.init.setup段去的,通过搜索发现,找到以下一个定义:

#define __setup(str, fn) \
    __setup_param(str, fn, fn, 0)

#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(“root=”, root_dev_setup)为例,其中参数一是要匹配的名字,参数二则是匹配成功后需要执行的函数。
展开的内容则为:

static char __setup_str_root_dev_setup[] __initdata = root=;    
    static struct obs_kernel_param __setup_root_dev_setup   
        __attribute_used__              
        __attribute__((__section__(".init.setup"))) 
        __attribute__((aligned((sizeof(long)))))    
        = { __setup_str_root_dev_setup, root_dev_setup, early }

而函数root_dev_setup内容为:

static int __init root_dev_setup(char *line)
{
    strlcpy(saved_root_name, line, sizeof(saved_root_name));
    return 1;
}

其中early则是标记该数据结构是否在parse_early_param()中处理,该函数根据early值进行早期的参数处理,在里面会调用do_early_param 函数。若early标记为0的话,例如__setup(“root=”, root_dev_setup),则该数据结构的处理则会在unknown_bootoption函数中处理。而该函数会再去调用obsolete_checksetup函数。在该函数中做具体处理。obsolete_checksetup函数内容如下:

static int __init obsolete_checksetup(char *line)
{
    struct obs_kernel_param *p;
    int had_early_param = 0;

    p = __setup_start;
    do {
        int n = strlen(p->str);
        if (!strncmp(line, p->str, n)) {
            if (p->early) {
                if (line[n] == '\0' || line[n] == '=')
                    had_early_param = 1;
            } else if (!p->setup_func) {
                printk(KERN_WARNING "Parameter %s is obsolete,"
                       " ignored\n", p->str);
                return 1;
            } else if (p->setup_func(line + n))
                return 1;
        }
        p++;
    } while (p < __setup_end);

    return had_early_param;
}

在该函数中,在__setup_start和__setup_end之间找出相对应的结构体。
以bootargs环境变量为例,它的值为:

noinitrd root=/dev/nfs nfsroot=192.168.3.16:/work/nfs_root/first ip=192.168.3.123:192.168.3.16:192.168.3.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,

然后通过该值来查找有无对应的结构体,例如已知有定义__setup(“root=”, root_dev_setup),所以就会找到这么一项root=/dev/nfs,接着在obsolete_checksetup函数中执行p->setup_func语句调用与之对应的结构体中保存的函数,对于root=来说,则是调用root_dev_setup函数,它的作用则是将root=的内容保存到saved_root_name变量中。接着循环其它的__setup(),查看有无与第一个参数匹配的项,有的话则调用与之对应的结构体中保存的函数。

接下来回到start_kernel函数,在该函数最后,调用了rest_init函数。在rest_init函数中又会去调用kernel_init函数,继续跟踪,在这函数中会调用到prepare_namespace函数。对prepare_namespace函数进行分析。函数内容如下:

void __init prepare_namespace(void)
{
    ...

    if (saved_root_name[0]) {
        root_device_name = saved_root_name;
        if (!strncmp(root_device_name, "mtd", 3)) {
            mount_block_root(root_device_name, root_mountflags);
            goto out;
        }
        ROOT_DEV = name_to_dev_t(root_device_name);
        if (strncmp(root_device_name, "/dev/", 5) == 0)
            root_device_name += 5;
    }

    is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

    if (initrd_load())
        goto out;

    if (is_floppy && rd_doload && rd_load_disk(0))
        ROOT_DEV = Root_RAM0;

    mount_root();
}

从以上代码可以发现根文件系统的名字保存在变量saved_root_name中,而该变量的内容则通过之前的obsolete_checksetup函数已经实现了赋值。函数最后则会调用mount_root(),挂载该根文件系统。返回到kernel_init函数后,在该函数中最后还会调用init_post函数,该函数的作用则是执行应用程序。

整个内核的启动流程大致总结为:

arch/arm/kernel/head.S
__switch_data
    start_kernel
        setup_arch(&command_line)        //获取及解析u-boot传入的启动参数
        parse_early_param   
            //从__setup_start到__setup_end,调用early函数 
            do_early_param               //早期参数的一些初始化
        unknown_bootoption          
            //从__setup_start到__setup_end,调用非early函数
            obsolete_checksetup
        rest_init
            kernel_init
                prepare_namespace
                    mount_root           //挂载根文件系统
                init_post            //执行应用程序

到现在初步了解了整个内核的启动过程,但是我知道还远远不够。。很多内容没有去深究,都只是粗略的顺藤摸瓜,说的比较浅,但是让自己对内核有所大概的了解,在以后的路上我也一定会加油,谢谢各位阅读,希望有错或者有疑问的地方可以提出来,一块交流。

你可能感兴趣的:(linux,kernel,挂载,jz2440)