zoukankan      html  css  js  c++  java
  • 制作根文件系统之内核挂接文件系统步骤分析

    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函数分析,它位于initmain.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线程函数,它位于initmain.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函数,它的字面意思是准备名字空间,其实它的功能就是挂接文件系统,它位于initdo_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...
    ",
    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函数,它同样位于initdo_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.
    ");
    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块设备。浅尝辄止,至于设备的挂接过程与原理后面深入了解了源码再去分析。

  • 相关阅读:
    [转发]深入理解git,从研究git目录开始
    iOS系统网络抓包方法
    charles抓包工具
    iOS多线程中performSelector: 和dispatch_time的不同
    IOS Core Animation Advanced Techniques的学习笔记(五)
    IOS Core Animation Advanced Techniques的学习笔记(四)
    IOS Core Animation Advanced Techniques的学习笔记(三)
    IOS Core Animation Advanced Techniques的学习笔记(二)
    IOS Core Animation Advanced Techniques的学习笔记(一)
    VirtualBox复制CentOS后提示Device eth0 does not seem to be present的解决方法
  • 原文地址:https://www.cnblogs.com/andyfly/p/9418249.html
Copyright © 2011-2022 走看看