Linux移植之tag参数列表解析过程分析中已经将内核传入的各个参数的处理过程大概的讲述了。root=/dev/mtdblock3参数的解析分析到最后发现它被存储在了saved_root_name中,最后发现是prepare_namespace函数调用了它。调用它的作用是为了挂接根文件系统,什么是挂接根文件系统?这个需要深入了解文件系统相关代码才能知道,我的简单理解是:内核一开始是在RAM中运行的,内核为了调用存储在flash中的程序而需要将flash的内容以某种格式读出来,存放到RAM中运行。所以需要指定flash中程序的地址,这个地址就是根文件系统存放的地址。这就是需要挂接根文件系统的原因。

这一篇主要就是讲述prepare_namespace怎么样挂接根文件系统。先看下start_kernel的程序调用层次。

start_kernel
    setup_arch                  //解析UBOOT传入的启动参数
    setup_command_line //解析UBOOT传入的启动参数
    do_early_param         //解析early参数,uboot中没传这个参数
    unknown_bootoption//解析到了命令行参数,saved_root_name在这块初始化
    console_init();//控制台初始化
    rest_init
        kernel_thread
            kernel_init
                prepare_namespace   //解析命令行参数解析成功挂接在哪个分区
                    mount_root//挂接根文件系统
                init_post
                    //执行应用程序

进入rest_init函数分析,它位于init\main.c下:

426    static void noinline __init_refok rest_init(void)
427        __releases(kernel_lock)
428    {
429        int pid;
430
431        kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);//创建kernel_init内核进程 pid=1
432        numa_default_policy();
433        pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);//创建kthreadd内核进程 pid=2
434        kthreadd_task = find_task_by_pid(pid);
435        unlock_kernel();
436
437        /*
438         * The boot idle thread must execute schedule()
439         * at least one to get things moving:
440         */
441        preempt_enable_no_resched();
442        schedule();
443        preempt_disable();
444
445        /* Call into cpu_idle with preempt disabled */
446        cpu_idle();
447    } 

可以看到rest_init函数首先创建了一个kernel_init线程,进入kernel_init线程函数,它位于init\main.c中,截取部分内容

787    static int __init kernel_init(void * unused)
788    {
            ...
            ...
            ...
            
826        if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
827            ramdisk_execute_command = NULL;
828            prepare_namespace();//挂接文件系统
829        }
        
            ...
            ...
            ...
838    }

直接看到prepare_namespace函数,它的字面意思是准备名字空间,其实它的功能就是挂接文件系统,它位于init\do_mounts.c中

414    void __init prepare_namespace(void)
415    {
416        int is_floppy;
417
418        if (root_delay) {
419            printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
420                   root_delay);
421            ssleep(root_delay);
422        }
423
424        /* wait for the known devices to complete their probing */
425        while (driver_probe_done() != 0)
426            msleep(100);
427
428        md_run_setup();
429
430        if (saved_root_name[0]) {//如果saved_root_name里面有内容,里面的内容是从uboot参数传过来的
431            root_device_name = saved_root_name;//絪aved_root_name的内容拷贝到boot_device_name中
432            if (!strncmp(root_device_name, "mtd", 3)) {//如果root_device_name内容的前面三个字符是mtd
433                mount_block_root(root_device_name, root_mountflags);//挂接root_device_name区的文件系统
434                goto out;//挂接完退出
435            }
436            ROOT_DEV = name_to_dev_t(root_device_name);//将root_device_name与设备号ROOT_DEV联系起来
437            if (strncmp(root_device_name, "/dev/", 5) == 0)//如果root_device_name前5个字符是/dev/
438                root_device_name += 5;//去掉/dev/字符,找到mtd开头的字符
439        }
440    
441        is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
442
443        if (initrd_load())
444        goto out;
445
446        if (is_floppy && rd_doload && rd_load_disk(0))
447            ROOT_DEV = Root_RAM0;
448
449        mount_root();//根据ROOT_DEV设备号挂接到/dev/root设备或者网络文件系统
450    out:
451        sys_mount(".", "/", NULL, MS_MOVE, NULL);
452        sys_chroot(".");
453        security_sb_post_mountroot();
454    }

总结一下这个函数的功能就是将saved_root_name的内容处理之后得到一个ROOT_DEV设备号,然后mount_root函数根据这个设备号ROOT_DEV创建/dev/root设备然后内核挂接这个文件系统。再看一下mount_root函数,它同样位于init\do_mounts.c中

382    void __init mount_root(void)
383    {
384    #ifdef CONFIG_ROOT_NFS//挂接网络文件系统
385        if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {//如果需要挂接网络文件系统
386            if (mount_nfs_root())
387                return;
388
389            printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.\n");
390            ROOT_DEV = Root_FD0;
391        }
392    #endif
393    #ifdef CONFIG_BLK_DEV_FD
394        if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {
395            /* rd_doload is 2 for a dual initrd/ramload setup */
396            if (rd_doload==2) {
397                if (rd_load_disk(1)) {
398                    ROOT_DEV = Root_RAM1;
399                    root_device_name = NULL;
400                }
401            } else
402                change_floppy("root floppy");
403        }
404    #endif
405    #ifdef CONFIG_BLOCK
406        create_dev("/dev/root", ROOT_DEV);//创建dev/root设备,设备号为ROOT_DEV
407        mount_block_root("/dev/root", root_mountflags);//挂接到/dev/root设备
408    #endif
409    }

这个函数可以根据ROOT_DEV的值判断是否挂接网络文件系统。若不挂接的话,那么创建/dev/root设备节点,然后将内核挂接到/dev/root设备,它指向的是mtdblock3块设备。浅尝辄止,至于设备的挂接过程与原理后面深入了解了源码再去分析。

 

posted on 2018-08-04 14:10  andy_fly  阅读(788)  评论(0编辑  收藏  举报