文件子系统-(rootfs)根文件系统挂载流程03【转】

转自:http://news.migage.com/articles/%E6%96%87%E4%BB%B6%E5%AD%90%E7%B3%BB%E7%BB%9F%28rootfs%29%E6%A0%B9%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%8C%82%E8%BD%BD%E6%B5%81%E7%A8%8B03_3547130_csdn.html

文件子系统-(rootfs)根文件系统挂载流程03
Source

NO1.linux内核启动+Android系统启动过程详解
https://blog.csdn.net/ahaochina/article/details/72533442

NO2.内核的配置和编译及代码分析(一)
https://blog.csdn.net/kakasingle/article/details/12851963?utm_source=jiancool

NO3.linux的initrd机制和initramfs机制之initrd
http://blog.chinaunix.net/uid-20279362-id-4924898.html
http://cukdd.blog.chinaunix.net/uid-29431466-id-4834509.html

NO4.android用initrd文件系统启动流程
https://blog.csdn.net/wangcong02345/article/details/51659201

NO5.initrd(ramdisk)的内核处理流程
https://blog.csdn.net/yiyeguzhou100/article/details/78318100

linux文件系统-根文件系统(rootfs)挂载流程
文件系统的制作…

rootfs挂载流程分析

默认根目录已经挂上去了,此处为挂载具体的文件系统。
linux/init

不同类型挂载情况梳理:
1. 在initramfs或ramdisk的initramfs
注册阶段:
start_kernel
->vfs_cache_init
-->mnt_init
--->init_rootfs
---->ini_mount_tree.//注册类型为rootfs的fs(文件系统),挂载并设置rootfs为vfs?用户?根目录。

挂载阶段:
-rest_init
--kernel_init
--->do_basic_setup
--->do_initcall(按优先级调用内核初始化启动函数)
---->rootfs_initcall(populate_rootfs)(注册populate_rootfs函数)
----->populate_rootfs(解压initramfs到rootfs,initramfs必须包含init文件),
------>sys_access(kernel_init检测init是否存在,如果有就不会调用prepare_namespace,即不会挂载其他文件系统了)
--回到kernel_init
--->run_init_process(ramdisk_execute_command);//即执行rootfs下的init

2. 没有Ramdisk,假如使用mtd.
sys_access(kernel_init检测init是否存在,不存在这调用prepare_namespace).
prepare_namespace
-->mount_block_root(root_device_name, root_mountflags) //直接挂载mtd设备
--->回到kernel_init
---->执行try_to_run_init_process("/sbin/init")


3. 在ramdisk为initrd时
sys_access(kernel_init检测init是否存在,不存在这调用prepare_namespace).
---->prepare_namespace
----->initrd_load
------->rd_load_image(加载initrd.image)
------->如果ROOT_DEV != Root_RAM0,则调用handle_init通过linuxrc启动用户态。(即initrd=0Xxxx,但是root=/dev/metdblock2矛盾导致)
------->如果ROOT_DEV = Root_RAM0,rd_load_image加载initrd.image后,通过mount_root挂载Root_RAM0
--------->**mount_root** //将/dev/ram0设备中的跟文件系统挂载到/root目录,进入该目录并将/root设置为当前目录。
image-initrd &cpio-initrd 处理流程描述
- initrd的解释是initialized RAM disk,就是启动的时候由uboot来初始化内存,当做disk来使用。
- uboot启动的时候,uboot会将存储介质中(如Flash)的initrd文件加载到内存,内核启动时会在访问挂载的根文件系统前先访问该内存中的initrd文件系统。
- 在uboot配置了initrd的情况下,文件系统启动被分成两个阶段:
    *第一阶段先执行initrd文件系统中的"某个可执行文件"(linuxrc或init,下面会讲到具体会执行哪一个),完成加载驱动模块等任务。
    *第二阶段是挂载真正的根文件系统中,然后执行/sbin/init进程
image-initrd
(1) boot loader(一般大家常用的是grub,关于它的介绍可以到网上搜索)把 initrd.img 初始化成一个设备 /dev/intrd,接着boot loader 把内核以及/dev/initrd的内容加载到内存特定位置。
(2) 内核判断initrd的文件格式,如果不是cpio格式,将其作为image-initrd处理,保存在rootfs下的/initrd.image文件中,随后内核把 /dev/initrd 设备的内容解压缩并拷贝到/dev/ram0设备中,也就是读入了一个内存盘中。
(3) 内核以可读写的方式把 /dev/ram0 设备挂载为原始的根文件系统。
(4) 如果 /dev/ram0 被指定为真正的根文件系统,那么内核不会执行(5)、(6)、(7)的操作,因为这下操作是为了帮内核加载最终的根文件系统做的工作。
(root=/dev/ram的设置,是告诉内核我们最终要挂载的文见系统实际就是被拷贝的内存里的这个文件系统,不要让内核再去费力去挂载其他的文件系统了)
(5) 执行 initrd 上的 /linuxrc 文件,linuxrc 通常是一个脚本文件,负责加载内核访问根文件系统必须的驱动,以及加载根文件系统。
(6) /linuxrc 执行完毕,真正的根文件系统被挂载,执行权交给内核。
(7) 如果真正的根文件系统存在/initrd 目录,那么/dev/ram0将从/移动到 /initrd;否则 /dev/ram0 将被卸载。
(8) 在真正的根文件系统上进行正常启动过程 ,执行 /sbin/init,对于image-initrd格式的镜像,它执行的是linuxrc文件。

cpio-initrd

(1) boot loader 把内核以及 initrd 文件加载到内存的特定位置
(2) 内核判断initrd的文件格式,如果是cpio格式。
(3) 将initrd的内容释放到/rootfs中。(rootfs本身也是一个基于内存的文件系统。这样就省掉了ramdisk的挂载、卸载等)
(4) 执行initrd中的/init文件,执行到这一点,内核的工作全部结束,完全交给/init文件处理。也就是其实到了最后一步,内核就已经完成了自己所有的工作,直接移交给initrd 的/init

两种格式镜像比较
1. cpio-initrd的制作方法比image-initrd简单。
2. cpio-initrd的内核处理流程相比image-initrd更简单,因为:
a. 根据上面的流程对比可知,cpio-initrd格式的镜像是释放到rootfs中的,不需要额外的文件系统支持, 而image-initrd格式的镜像先是被挂载成虚拟文件系统,而后被卸载,基于具体的文件系统
b. image-initrd内核在执行完/linuxrc进程后,还要返回执行内核进行一些收尾工作,  并且要负责执行真正的根文件系统的/sbin/init。
initrd

initrd镜像的制作
cpio-initrd格式镜像制作:
进入到要制作的文件系统的根目录;
bash# find . | cpio -c -o > ../initrd.img
bash# gzip ../initrd.img

image-initrd格式镜像制作:
进入到要制作的文件系统的根目录;
bash# dd if=/dev/zero of=../initrd.img bs=512k count=5
bash# mkfs.ext2 -F -m0 ../initrd.img
bash# mount -t ext2 -o loop ../initrd.img /mnt
bash# cp -r * /mnt
bash# umount /mnt
bash# gzip -9 ../initrd.img

对于image-initrd格式镜像的制作,往往采用制作工具,如genext2fs
在这里插入图片描述

->rest_init()                //加载initranfs文件系统包
   -->kernel_init(void unused)
    --kernel_init_freeable();
    //初始化设备驱动,加载静态内核模块,启动所有直接编译进内核的模块。 
    ---do_basic_setup(); 
    //启动所有在__initcall_start和__initcall_end段的函数,静态编译进内核的modules也会将其入口放置在这段区间。
     ----do_initcalls(); 
     -----do_initcall_level(level); 
     ------do_one_initcall(fn)
       |//注册文件系统相关的初始化函数。
    |--------rootfs_initcall(populate_rootfs);

   //加载不同类型的文件系统
     ----------- populate_rootfs
   //(解压initramfs系统包到rootfs中
     --------------  unpack_to_rootfs
rest_init
负责加载initrd文件,扩展VFS树,创建基本的文件系统目录拓扑;
initrd是一个临时文件系统,由bootload负责加载到内存中,里面包含了基本的可执行程序和驱动程序。在linux初始化的初级阶段,它提供了一个基本的运行环境。当成功加载磁盘文件系统后,系统将切换到磁盘文件系统并卸载initrd

tatic noinline void __ref rest_init(void)
{
    struct task_struct *tsk;
    int pid;

    rcu_scheduler_starting();
    /*
     * We need to spawn init first so that it obtains pid 1, however
     * the init task will end up wanting to create kthreads, which, if
     * we schedule it before we create kthreadd, will OOPS.
     */
    pid = kernel_thread(kernel_init, NULL, CLONE_FS);
    /*
     * Pin init on the boot CPU. Task migration is not properly working
     * until sched_init_smp() has been run. It will set the allowed
     * CPUs for init to the non isolated CPUs.
     */
    rcu_read_lock();
    tsk = find_task_by_pid_ns(pid, &init_pid_ns);
    set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
    rcu_read_unlock();

    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();

    /*
     * Enable might_sleep() and smp_processor_id() checks.
     * They cannot be enabled earlier because with CONFIG_PRREMPT=y
     * kernel_thread() would trigger might_sleep() splats. With
     * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
     * already, but it's stuck on the kthreadd_done completion.
     */
    system_state = SYSTEM_SCHEDULING;

    complete(&kthreadd_done);

    /*
     * The boot idle thread must execute schedule()
     * at least once to get things moving:
     */
    schedule_preempt_disabled();
    /* Call into cpu_idle with preempt disabled */
    cpu_startup_entry(CPUHP_ONLINE);
}
kernel_init
static int __ref kernel_init(void *unused)
{
    int ret;
    /*重点函数*/
    kernel_init_freeable(); 
    /* need to finish all async __init code before freeing the memory */
    async_synchronize_full();
    ftrace_free_init_mem();
    free_initmem();
    mark_readonly();
    system_state = SYSTEM_RUNNING;
    numa_default_policy();

    rcu_end_inkernel_boot();

    //Initramfs从这里启动init
    pr_emerg("run init
");
    //所以如果uboot传过来的命令行参数有rdinit=xxx,则会执行
    if (ramdisk_execute_command) {
        ret = run_init_process(ramdisk_execute_command);
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d)
",
               ramdisk_execute_command, ret);
    }

    /*
     * We try each of these until one succeeds.
     *
     * The Bourne shell can be used instead of init if we are
     * trying to recover a really broken machine.
     */
    //从initrd、nfs和磁盘下启动init
    //所以如果uboot传过来的命令行参数有init=xxx,则会执行
    if (execute_command) {
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
        panic("Requested init %s failed (error %d).",
              execute_command, ret);
    }
//就会执行/sbin/init, /etc/init,, /bin/init,/bin/sh
//如果uboot传过来的命令行参数没有init=xxx或者rdinit=xxx,则会执行该进程,一去不复返,后面的就不会执行了
    if (!try_to_run_init_process("/sbin/init") ||
        !try_to_run_init_process("/etc/init") ||
        !try_to_run_init_process("/bin/init") ||
        !try_to_run_init_process("/bin/sh"))
        return 0;

    panic("No working init found.  Try passing init= option to kernel. "
          "See Linux Documentation/admin-guide/init.rst for guidance.");
}

static int __init init_setup(char *str)
{
    unsigned int i;

    //execute_command获取了init=xxx的值
    execute_command = str;
    /*
     * In case LILO is going to boot us with default command line,
     * it prepends "auto" before the whole cmdline which makes
     * the shell think it should execute a script with such name.
     * So we ignore all arguments entered _before_ init=... [MJ]
     */
    for (i = 1; i < MAX_INIT_ARGS; i++)
        argv_init[i] = NULL;
    return 1;
}
__setup("init=", init_setup);

static int __init rdinit_setup(char *str)
{
    unsigned int i;
    
    //ramdisk_execute_command获取了rdinit= xxx 的值
    ramdisk_execute_command = str;
    /* See "auto" comment in init_setup */
    for (i = 1; i < MAX_INIT_ARGS; i++)
        argv_init[i] = NULL;
    return 1;
}
__setup("rdinit=", rdinit_setup);
kernel_init_freeable
此处完成根目录的设置

static noinline void __init kernel_init_freeable(void)
{
    /*
     * Wait until kthreadd is all set-up.
     */
    wait_for_completion(&kthreadd_done);

    /* Now the scheduler is fully set up and can do blocking allocations */
    gfp_allowed_mask = __GFP_BITS_MASK;

    /*
     * init can allocate pages on any node
     */
    set_mems_allowed(node_states[N_MEMORY]);

    cad_pid = task_pid(current);

    smp_prepare_cpus(setup_max_cpus);

    workqueue_init();

    init_mm_internals();

    do_pre_smp_initcalls();
    lockup_detector_init();

    smp_init();
    sched_init_smp();

    page_alloc_init_late();
    /* Initialize page ext after all struct pages are initialized. */
    page_ext_init();

    do_basic_setup();//初始化设备驱动,加载静态内核模块;释放Initramfs到rootfs

    test_executor_init();

    /* 在rootfs上打开/ dev / console */
    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
        pr_err("Warning: unable to open an initial console.
");

    (void) sys_dup(0);
    (void) sys_dup(0);
    /*
     * check if there is an early userspace init.  If yes, let it do all
     * the work
     */
    //ramdisk_execute_command:在kernel解析引导参数的时候使用
    //如果用户指定了init文件路径,即使用了“init=”,,一般不会出现该赋值。
    if (!ramdisk_execute_command)
        //默认为是/init,即文件系统是initramfs或cpio-initrd
        ramdisk_execute_command = "/init";

    //检查此时跟文件系统中是否存在文件init,反之调用prepare_namespace()加载initfd、nfs或磁盘文件系统,
    //即磁盘的文件系统挂载至rootfs/root目录,并设置系统current对应的根目录项为磁盘根目录项、系统current根文件系统为磁盘文件系统。
    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        
    //对于initramfs和cpio-initrd的情况,都会将文件系统(其实是一个VFS)解压到根文件系统。如果不存在/init文件,则执行函数prepare_namespace()
        prepare_namespace();
    }

    /*
     * Ok, we have completed the initial bootup, and
     * we're essentially up and running. Get rid of the
     * initmem segments and start the user-mode stuff..
     *
     * rootfs is available now, try loading the public keys
     * and default modules
     */

    integrity_load_keys();
    load_default_modules();
}
do_basic_setup();
//初始化设备驱动,加载静态内核模块;释放Initramfs到rootfs
调用所有模块的初始化函数,包括initramfs的初始化函数populate_rootfs,

在这里执行了 populate_rootfs,即检测initrd的类型并且将其释放到目录中

函数populate_rootfs 通过rootfs_initcall(populate_rootfs)导出.
static void __init do_basic_setup(void)
{
    cpuset_init_smp();
    shmem_init();
    driver_init();
    init_irq_proc();
    do_ctors();
    usermodehelper_enable();
    // 内核模块的初始化函数都在这里执行
    do_initcalls();
}
void __init driver_init(void)
{
    /* These are the core pieces */
    devtmpfs_init();// 初始化devtmpfs
    devices_init(); // 初始化sysfs
    buses_init();   // sys/bus sys/system
    classes_init(); // sys/class
    firmware_init();// sys/firmware 设备树放在这里
    hypervisor_init();

    /* These are also core pieces, but must come after the
     * core core pieces.
     */
    platform_bus_init();// 初始化platform_bus
    cpu_dev_init();     // 初始化驱动模型中的devices/system/cpu子系统
    memory_dev_init();
    container_dev_init();
    of_core_init();
}
do_initcalls();
initcalls机制

do_initcalls()将按顺序从由__initcall_start开始,到__initcall_end结束的section中以函数指针的形式取出这些编译到内核的驱动模块中初始化函数起始地址,来依次完成相应的初始化。而这些初始化函数由__define_initcall(level,fn)指示编译器在编译的时候,将这些初始化函数的起始地址值按照一定的顺序放在这个section中
initcall简介
linux在代码段中定义了一个特殊的段initcall,该段中存放的都是函数指针;linux初始化阶段调用do_initcalls()依次执行该段的函数。关于该段的详细信息可以参见vmlinux.lds.S链接脚本
https://www.cnblogs.com/downey-blog/p/10486653.html
https://blog.csdn.net/mcsbary/article/details/90644101

1. 分析 __define_initcall(level,fn) 宏定义

kernel4.14/include/linux/init.h

//__define_initcall(fn, n),n是一个数字或者是数字+s,这个数字代表这个fn执行的优先级,数字越小,优先级越高,带s的fn优先级低于不带s的fn优先级。
  
  #define early_initcall(fn)        __define_initcall(fn, early)
  #define pure_initcall(fn)        __define_initcall(fn, 0)
  #define core_initcall(fn)        __define_initcall(fn, 1)
  #define core_initcall_sync(fn)        __define_initcall(fn, 1s)
  #define postcore_initcall(fn)        __define_initcall(fn, 2)
  #define postcore_initcall_sync(fn)    __define_initcall(fn, 2s)
  #define arch_initcall(fn)        __define_initcall(fn, 3)
  #define arch_initcall_sync(fn)        __define_initcall(fn, 3s)
  #define subsys_initcall(fn)        __define_initcall(fn, 4)
  #define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
  #define fs_initcall(fn)            __define_initcall(fn, 5)
  #define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
  #define rootfs_initcall(fn)        __define_initcall(fn, rootfs)
  #define device_initcall(fn)        __define_initcall(fn, 6)
  #define device_initcall_sync(fn)    __define_initcall(fn, 6s)
  #define late_initcall(fn)        __define_initcall(fn, 7)
  #define late_initcall_sync(fn)        __define_initcall(fn, 7s)

#define __define_initcall(fn, id) 
    static initcall_t __initcall_name(fn, id) __used 
    __attribute__((__section__(".initcall" #id ".init"))) = fn;

//其中initcall_是一个函数指针类型:typedef int (*initcall_t)(void);
//#define __initcall_name(fn, id)     __initcall_##fn##id   (其中##表示替换连接)
//而属性__attribute__((__section__(".initcall" #id ".init"))) 则表示把对象放在一个这个由括号中的名称所指代的section中,表示编译时将目标符号放置在括号指定的段中
// 而#在宏定义中的作用是将目标字符串化,##在宏定义中的作用是符号连接,将多个符号连接成一个符号,并不将其字符串化。

1) 声明一个名称为__initcall_##fn##id的函数指针(其中##表示替换连接,);
2) 将这个函数指针初始化为fn;
3) 编译的时候需要把这个函数指针变量放置到名称为 ".initcall" #id ".init" 的section中(比如id=rootfs,代表这个section的名称是 ".initcallrootfs.init")
4) #define rootfs_initcall(fn)        __define_initcall(fn, rootfs)
   .声明一个函数指针__initcall_fn_rootfs = fn;
5) rootfs_initcall(populate_rootfs)
2.do_initcalls()的执行

//定义一个静态的initcall_levels数组,这是一个指针数组,数组的每个元素都是一个指针。

//do_initcalls()循环调用do_initcall_level(level),level就是initcall的优先级数字,由for循环的终止条件ARRAY_SIZE(initcall_levels) - 1可知,总共会调用do_initcall_level(0)~do_initcall_level(7),一共七次。

//do_initcall_level(level)中则会遍历initcall_levels[level]中的每个函数指针,initcall_levels[level]实际上是对应的__initcall##level##_start指针变量,然后依次取出__initcall##level##_start指向地址存储的每个函数指针,并调用do_one_initcall(*fn),实际上就是执行当前函数。
//__initcall##level##start所存储的函数指针就是开发者用xxx_initcall()宏添加的函数,对应".initcall##level##.init"段

//do_one_initcall(*fn)的执行:判断initcall_debug的值,如果为真,则调用do_one_initcall_debug(fn);如果为假,则直接调用fn。事实上,调用do_one_initcall_debug(fn)只是在调用fn的基础上添加一些额外的打印信息,可以直接看成是调用fn

extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];

//定义一个静态的initcall_levels数组,这是一个指针数组,数组的每个元素都是一个指针。
static initcall_t *initcall_levels[] __initdata = {
    __initcall0_start,
    __initcall1_start,
    __initcall2_start,
    __initcall3_start,
    __initcall4_start,
    __initcall5_start,
    __initcall6_start,
    __initcall7_start,
    __initcall_end,
};

/* Keep these in sync with initcalls in include/linux/init.h */
static char *initcall_level_names[] __initdata = {
    "early",
    "core",
    "postcore",
    "arch",
    "subsys",
    "fs",
    "device",
    "late",
};
do_initcall_level
static void __init do_initcall_level(int level)
{
    initcall_t *fn;

    strcpy(initcall_command_line, saved_command_line);
    //bootloader 通过setup_initrd_tag函数把initrd_start设置到内核 tag中,内核通过parse_tag解析
    parse_args(initcall_level_names[level],
           initcall_command_line, __start___param,
           __stop___param - __start___param,
           level, level,
           NULL, &repair_env_string);
//fn为函数指针,fn++相当于函数指针+1,相当于:内存地址+sizeof(fn),sizeof(fn)根据平台不同而不同,一般来说,32位机上是4字节,64位机则是8字节
    for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
        do_one_initcall(*fn);
}
do_initcalls
// 内核模块的初始化函数都在这里执行
// 根据调用级别,从level0-leveln分别执行内核模块的初始化函数
static void __init do_initcalls(void)
{
    int level;

    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
        do_initcall_level(level);
}

3.vmlinux.lds.h的调用

kernel/kernel4.14/include/asm-generic/vmlinux.lds.h

//在这里首先定义了__initcall_start,将其关联到".initcallearly.init"段,
//然后对每个level定义了INIT_CALLS_LEVEL(level),将INIT_CALLS_LEVEL(level)展开之后的结果是定义__initcall##level##_start,并将__initcall##level##_start关联到".initcall##level##.init"段和".initcall##level##s.init"段。
//__initcall##level##_start和".initcall##level##.init"段的对应就比较清晰了,所以,从initcall_levels[level]部分一个个取出函数指针并执行函数就是执行xxx_init_call()定义的函数

 #define INIT_CALLS_LEVEL(level)                        
          VMLINUX_SYMBOL(__initcall##level##_start) = .;        
          KEEP(*(.initcall##level##.init))            
          KEEP(*(.initcall##level##s.init))            
  
  #define INIT_CALLS                            
          VMLINUX_SYMBOL(__initcall_start) = .;            
          KEEP(*(.initcallearly.init))                
          INIT_CALLS_LEVEL(0)                    
          INIT_CALLS_LEVEL(1)                    
          INIT_CALLS_LEVEL(2)                    
          INIT_CALLS_LEVEL(3)                    
          INIT_CALLS_LEVEL(4)                    
          INIT_CALLS_LEVEL(5)                    
          INIT_CALLS_LEVEL(rootfs)                
          INIT_CALLS_LEVEL(6)                    
          INIT_CALLS_LEVEL(7)                    
          VMLINUX_SYMBOL(__initcall_end) = .;


4.vmlinux.lds.S 之 INIT_CALLS

kernel/kernel4.14/arch/arm64/kernel/vmlinux.lds.S

SECTIONS{
.init.data : {
          INIT_DATA
          INIT_SETUP(16)
          INIT_CALLS
          CON_INITCALL
          SECURITY_INITCALL
          INIT_RAM_FS
          *(.init.rodata.* .init.bss)    /* from the EFI stub */
          KUNIT_TEST_MODULES
     }
}
用户使用不同优先级的initcall宏可以很方便的在linux代码中注册函数指针;将这些函数指针存储在相应的initcall段中;最终,由do_initcalls()按照优先级依次执行段中的函数.
5.initcall机制举例

#define core_initcall(fn)        __define_initcall(fn, 1)

// core_initcall(beagle_init)
//core_initcall(beagle_init)宏展开为__define_initcall(beagle_init, 1),所以beagle_init()这个函数被放置在".initcall1.init"段处
//在内核启动时,系统会调用到do_initcall()函数。 根据指针数组initcall_levels[1]找到__initcall1_start指针,在vmlinux.lds.h可以查到:__initcall1_start对应".initcall1.init"段的起始地址,依次取出段中的每个函数指针,并执行函数

//而initcall_levels[level]指向当前".initcall##level##s.init"段,initcall_levels[level+1]指向".initcall##(level+1)##s.init"段,两个段之间的内存就是存放所有添加的函数指针.
1) __define_initcall(level,fn)的作用就是指示编译器把一些初始化函数的指针(即:函数起始地址)按照顺序放置一个名为 .initcall.init 的section中,这个section又被分成了若干个子section,它们按顺序排列。在内核初始化阶段,这些放置到这个section中的函数指针将供do_initcalls() 按顺序依次调用,来完成相应初始化。
     
2) 函数指针放置到的子section由宏定义的level确定,对应level较小的子section位于较前面。而位于同一个子section内的函数指针顺序不定,由编译器按照编译的顺序随机指定。
     
3) 因此,如果你希望某个初始化函数在内核初始化阶段就被调用,那么你 就应该使用宏__define_initcall(level,fn) 或 其几个衍生宏 把这个函数fn的对应的指针放置到按照初始化的顺序放置到相关的 section 中。如果某个初始化函数fn_B需要依赖于另外一个初始化函数fn_A的完成,那么你应该把fn_B放在比fn_A对应的level值较大的子section中,    这样,do_initcalls()将在fn_A之后调用fn_B

4) 如果你希望某个初始化函数在内核初始化阶段就被调用,那么你就应该使用宏__define_initcall(level,fn) 或 其7个衍生宏来把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的section 中。 内核初始化时的do_initcalls()将从这个section中按顺序找到这些函数来执行。

populate_rootfs
rootfs_initcall(populate_rootfs)
populate_rootfs主要完成Initrd的检测工作,检查出是CPIO Initrd还是Initramfs还是Image-Initrd

一种是跟kernel融为一体的initramfs.在编译kernel的时候,通过链接脚本将其存放在__initramfs_start至__initramfs_end的区域,直接调用unpack_to_rootfs将其释放到根目录。如果不是属于这种形式的,也就是__initramfs_start和__initramfs_end的值相等,长度为零。不会做任何处理
a. 检测是否存在initram格式的文件系统,存在则直接释放到原始的rootfs “/“目录。
b. 检测是cpio-initrd还是image-initrd,并分别处理。
c. 若是cpio-init则直接释放到”/ ”目录,反之将image-initrd保存在initrd.image。
d. 完成c步骤并释放Initrd所占用的内存空间。

static int __init populate_rootfs(void)
{
    char *err;
    
    //判断是否加载default_rootfs
    if (do_skip_initramfs) {
        if (initrd_start)
            free_initrd();
        return default_rootfs();
    }

    //第一检测是否initramfs文件系统 
    //若是的,将位于__initramfs_end - __initramfs_start的initramfs段释放到“/”目录下
    err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
    if (err)
        panic("%s", err); /* Failed to decompress INTERNAL initramfs */
    
    //第二检测是cpio-initrd还是image-initrd无论这两种格式,uboot都会把它加载到内存里的initrd_start地址处
    //IS_ENABLED(CONFIG_INITRAMFS_FORCE) :存在参数配置y或m,则返回1,反之返回0
    if (initrd_start && !IS_ENABLED(CONFIG_INITRAMFS_FORCE)) {
    
//必须要配制CONFIG_BLK_DEV_RAM才会支持image-initrd
#ifdef CONFIG_BLK_DEV_RAM
        int fd;
        printk(KERN_INFO "Trying to unpack rootfs image as initramfs...
");
        
        //判断加载的是不是initramfs CPIO文件,若是直接将解压到“/”目录下,解压成功返回0。
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start);
        if (!err) {
            //解压成功,释放image中initrd地址处的对应内存
            free_initrd();
            goto done;//然后直接跳转到done标号:
        } else {
            clean_rootfs();
            unpack_to_rootfs(__initramfs_start, __initramfs_size);
        }
    
        //如果执行到这里,就说明是image-initrd,将其内容保存到文件/initrd.image中。
        printk(KERN_INFO "rootfs image is not initramfs (%s)"
                "; looks like an initrd
", err);
             
        //在根文件系统中创建文件/initrd.image
        fd = sys_open("/initrd.image",
                  O_WRONLY|O_CREAT, 0700);
                  
        if (fd >= 0) {
              //将intird_start到initrd_end内容保存到/initrd.image文件中。
            ssize_t written = xwrite(fd, (char *)initrd_start,
                        initrd_end - initrd_start);
            if (written != initrd_end - initrd_start)
                pr_err("/initrd.image: incomplete write (%zd != %ld)
",written, initrd_end - initrd_start);
                
              //关闭文件并释放image中initrd对应内存
            sys_close(fd);
            free_initrd();
        }
    done:
        /* empty statement */;
#else  //不存在image-initrd 说明此时是cpio-initrd文件
        printk(KERN_INFO "Unpacking initramfs...
");
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start);
        if (err)
            printk(KERN_EMERG "Initramfs unpacking failed: %s
", err);
        free_initrd();
#endif
    }
    flush_delayed_fput();
    /*
     * Try loading default modules from initramfs.  This gives
     * us a chance to load before device_initcalls.
     */
    load_default_modules();

    return 0;
}
rootfs_initcall(populate_rootfs);
unpack_to_rootfs(char *buf, unsigned long len)
如果initramfs不存在,__initramfs_start和__initramfs_end的值相等即参数 len=0,unpack_to_rootfs不会做任何处理.

它通过__initramfs_start指针和__initramfs_end指针访问XXX.cpio.gz文件,
调用函数unpack_to_rootfs函数把源文件解压到rootfs中
static char * __init unpack_to_rootfs(char *buf, unsigned long len)
{
    long written;
    decompress_fn decompress;
    const char *compress_name;
    static __initdata char msg_buf[64];

    header_buf = kmalloc(110, GFP_KERNEL);
    symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
    name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);

    if (!header_buf || !symlink_buf || !name_buf)
        panic("can't allocate buffers");

    state = Start;
    this_header = 0;
    message = NULL;
    while (!message && len) {
        loff_t saved_offset = this_header;
        if (*buf == '0' && !(this_header & 3)) {
            state = Start;
            written = write_buffer(buf, len);
            buf += written;
            len -= written;
            continue;
        }
        if (!*buf) {
            buf++;
            len--;
            this_header++;
            continue;
        }
        this_header = 0;
    //根据buf的第1、2个字节的magic来判断decompress类型。比如这里对应gzip,所以返回值decompress及对应gunzip()
        decompress = decompress_method(buf, len, &compress_name);
        pr_debug("Detected %s compressed data
", compress_name);
        if (decompress) {
            int res = decompress(buf, len, NULL, flush_buffer, NULL,
                   &my_inptr, error);
            if (res)
                error("decompressor failed");
        } else if (compress_name) {
            if (!message) {
                snprintf(msg_buf, sizeof msg_buf,
                     "compression method %s not configured",
                     compress_name);
                message = msg_buf;
            }
        } else
            error("junk in compressed archive");
        if (state != Reset)
            error("junk in compressed archive");
        this_header = saved_offset + my_inptr;
        buf += my_inptr;
        len -= my_inptr;
    }
    dir_utime();
    kfree(name_buf);
    kfree(symlink_buf);
    kfree(header_buf);
    return message;
}
root_dev_setup
root用于制定根文件系统所在的存储设备,并把设备名称保存到静态变量saved_root_name中
__setup(“root=”, root_dev_setup);机制

static char __initdata saved_root_name[64];
static int __init root_dev_setup(char *line)
{
strlcpy(saved_root_name, line, sizeof(saved_root_name));
return 1;
}
__setup(“root=”, root_dev_setup);

fs_names_setup
rootfstype指定跟文件系统的类型,把跟文件系统的类型保存在静态变量root_fs_names。
static char * __initdata root_fs_names;
static int __init fs_names_setup(char *str)
{
root_fs_names = str;
return 1;
}
__setup(“rootfstype=”, fs_names_setup);

//由于之前已经切换到新的跟文件系统的根目录中去,所以out标签下这几步主要是将新的跟文件系统的根目录
替换rootfs,使其成为linux的vfs的根目录。
//不使用initrd情况下的流程,跟文件系统设备已经挂载上,且替代了rootfs成为vfs的根,
剩下的任务就是留给根文件系统中的/sbin/init程序。

prepare_namespace();
如果没有使用到 cpio 类型的 initrd,内核会执行 prepare_namespace()
函数路径:kernel/kernel4.14/init/do_mounts.c

** 挂载实际根文件系统**
a. 对于image-initrd,有两中挂载设备,一种是root=/dev/mtdblockxx 一种是root=/dev/ram设备

首先用户可以用root=来指定根文件系统。它的值保存在saved_root_name中。如果用户指定了以mtd开始的字串做为它的根文件系统。就会直接去挂载。这个文件是mtdblock的设备文件,否则将设备结点文件转换为ROOT_DEV即设备节点号。然后,转向initrd_load()执行initrd预处理后,再将具体的根文件系统挂载。函数末尾,会调用sys_mount()来移动当前文件系统挂载点到”/”目录下,然后将根目录切换到当前目录,根文件系统的挂载点就成为了我们在用户空间所看到的”/”.

initrd_load创建Root_RAM0类型的/dev/ram0设备节点(就是ramdisk设备节点)
调用rd_load_image将文件/initrd.image加载进/dev/ram0, 之后删除文件initrd.image。
随后调用handle_initrd将ramdisk节点/dev/ram0 mount到/root目录, 进入/root目录,将当前目录mount为根目录,
最后切换当前目录为程序执行所参考的根目录位置。
待分析知识

_setup 提取 save_root_name

//prepare_namespace() 在 do_mounts.c 中定义,它主要负责挂载根文件系统和 ramdisk类型的initrd.
> Root_RAM0定义在kernel4.14/include/linux/root_dev.h
void __init prepare_namespace(void)
{
    int is_floppy;
    
    //对于将根文件系统存放到USB或者SCSI设备上的情况,Kernel需要等待这些耗费时间比较久的设备驱动加载完毕,所以这里存在一个Delay。
    if (root_delay) {
        printk(KERN_INFO "Waiting %d sec before mounting root device...
",
               root_delay);
        ssleep(root_delay);
    }

/********************************/
    //首先会轮训检测块设备,若检测到则创建一个设备节点,然后分配一个设备号
    //等待根文件系统所在的设备检测函数的完成
    wait_for_device_probe();
    //挂接md设备,安装md设备驱动
    md_run_setup();
    dm_run_setup();
/********************************/
    //_setup提取save_root_name待分析
    //save_root_name存放root=指定设备文件,作为当前系统的跟文件系统设备名。如:“root=/dev/ram0”使用initrd作为跟文件系统,由_setup宏提取。
        if (saved_root_name[0]) {  //此时已经存在参数值
            root_device_name = saved_root_name;
/**************************************************************/
        //如果存储设备为闪存分区(设备名称以“mtd”开头,)或是在闪存分区基础上封装的ubi设备,那么调用mount_block_root就会直接把根文件系统挂载到rootfs文件系统的目录/root下。
                if (!strncmp(root_device_name, "mtd", 3) ||
            !strncmp(root_device_name, "ubi", 3)) {

            //这里相当于将saved_root_nam指定的设备/dev/mtdblock2进行加载。如下面传递给内核的command line:   CONFIG_CMDLINE="console=ttyS0,115200 mem=108M rdinit=/linuxrc root=/dev/mtdblock2" 
            mount_block_root(root_device_name, root_mountflags);
            goto out;
        }
/***********************************************************************************/
        
        //ROOT_DEV实现待分析
        //否则将/dev/ram转换为根文件系统设备号保存全局变量ROOT_DEV中。
        ROOT_DEV = name_to_dev_t(root_device_name);
        
        //如果跟文件系统设备名的前5个字符是‘/dev/‘
        if (strncmp(root_device_name, "/dev/", 5) == 0)
            //将跟文件系统设备名指向/dev/之后的设备名字
            root_device_name += 5;
    }

    //经initrd_load()执行initrd预处理后,再将具体的根文件系统挂载。
    //若指定了内核参数root=/dev/ram,则return false,继续执行进行挂载,目录切换等。
    //若没指定root=/dev/ram则在initrd_load()内部转进handinit进行处理,然后返回true,随后直接到out.
    //加载initrd 并初始化内存盘文件系统
    if (initrd_load())
        goto out;

/*********************************************************************************/
    //root_wait待分析    
    /* wait for any asynchronous scanning to complete */
    if ((ROOT_DEV ==0 ) && root_wait) {
        printk(KERN_INFO "Waiting for root device %s...
",
            saved_root_name);
        while (driver_probe_done() != 0 ||
            (ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
            msleep(5);
        async_synchronize_full();
    }
/***********************************************************************************/

    //如果跟文件系统设备号的主设备号等于软盘的主设备号FLOPPY_MAJOR(=2),那么is_floppy=1
    is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

    //如果跟设备是软盘,并且rd_doload不等于0,并且rd_load_disk操作成功,rd_doload初始设置为0,
    可用load_ramdisk=修改,ra_load_disk函数检查/dev/root文件系统类型,如果是romfs或cramfs
    或ext2或minix文件系统,将initrd.image直接读取到/dev/ram设备中。如果/dev/root是压缩文件,则将压缩文件解压缩后写到/dev/ram设备中,如果不是上述四种文件系统之一,则rd_load_image返回0,成功返回1.
    if (is_floppy && rd_doload && rd_load_disk(0))
        ROOT_DEV = Root_RAM0;    //将Root_RAM0即/dev/ram0设置成跟文件系统设备

    //将实际的Root_RAM0根文件系统挂载到/root目录,进入该目录并将/root设置为当前目录。
    mount_root();

//之前切换到新的根文件系统的根目录中去,下面是用新根文件系统的根目录替换rootfs,使其成为LinuxVFS的根目录。
out:
    //在driver_init处初始化,这里挂载devtmpfs文件系统
    devtmpfs_mount("dev");

    //将挂载点从当前目录(实际当前的目录在mount_root中或者在mount_block_root中指定)移到根目录。对于上面的command line的话,当前的目录就是/dev/mtdblock2,或是/dev/ram0。
    //将挂载点从当前目录/dev/ram0,移动到”/”目录下
    sys_mount(".", "/", NULL, MS_MOVE, NULL);

    //将当前目录 ’.‘作为系统的根目录,这样就将/dev/ram0实际挂接到“/”目录下,并且把“/”目录作为系统的根目录,它的虚拟文件系统挂接结构(struct vfsmount)指针mnt作为所有进程描述结构的tsk->fs->rootmnt,它的目录条目结构(struct dentry)指针dentry作为进程描述结构的tsk->fs->root
    //移动当前文件系统挂载点到”/”目录下,然后将根目录切换到当前目录。这样,根文件系统的挂载点就成为了我们在用户空间所看到的”/
    //将当前目录当作系统的根目录,至此虚拟系统根目录文件系统切换到了实际的根目录文件系统
    sys_chroot("."); 
}
//切换到了新的根文件系统的根目录中去,所以这两步的作用是用新的根文件系统的根目替换rootfs,使其成为linuxVFS的根目录。

initrd_load
a. 调用create_dev创建一个/dev/ram设备节点
b. 调用rd_load_image将initrd.image释放到/dev/ram0,并判断用户有没有指定最终的根文件设备名。
c. 如果没有指定的不是Root_ram0,则转入handle_initrd函数进行处理。

详细代码分析如下:

bool __init initrd_load(void)
{
//通过Kernel的参数“noinitrd“来配置mount_initrd的值,默认为1,是否要加载 initrd 的标志,当内核启动参数中包含 noinitrd 字符串时,mount_initrd 会被设为0。
    if (mount_initrd) {
        
        //为把initrd释放到内存盘中,需要创建ROOT_RAM设备节点,并将/initrd/.image释放到这个节点中。
        create_dev("/dev/ram", Root_RAM0);

        //1.调用rd_load_image将initrd.image释放到/dev/ram0,
        //2.将initrd.image释放到节点/dev/ram0,如果根文件设备号不是ROOT_RAM0(用户指定不是/dev/ram0,转入handle_initrd), 
        //2.1换句话说,就是给内核指定的参数不是/dev/ram,例如上面指定的/dev/mtdblock2设备节点肯定就不是Root_RAM0。
        if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {
            sys_unlink("/initrd.image");
    
            //函数handle_initrd主要执行Initrd中的linuxrc文件,并且将真实的根目录设置为当前目录.
            handle_initrd();
            return true;
        }
    }
    sys_unlink("/initrd.image");
    return false;
}
rd_load_image
a. 分别打开(initrd_load在rootfs中的创建的/dev/ram0设备节点) 和 (ramdisk镜像(“initrd.image”))。
b. nblocks = identify_ramdisk_image(in_fd, rd_image_start, &decompressor);?
c. 调用crd_load将initrd.image内容加载到/dev/ram0设备节点中。

int __init rd_load_image(char *from)
{
    int res = 0;
    int in_fd, out_fd;
    unsigned long rd_blocks, devblocks;
    int nblocks, i, disk;
    char *buf = NULL;
    unsigned short rotate = 0;
    decompress_fn decompressor = NULL;
#if !defined(CONFIG_S390)
    char rotator[4] = { '|' , '/' , '-' , '\' };
#endif
    //打开rootfs /dev/ram0设备节点
    out_fd = sys_open("/dev/ram", O_RDWR, 0);
    if (out_fd < 0)
        goto out;

    //打开rootfs中的ramdisk镜像(“initrd.image”)
    in_fd = sys_open(from, O_RDONLY, 0);
    if (in_fd < 0)
        goto noclose_input;

    nblocks = identify_ramdisk_image(in_fd, rd_image_start, &decompressor);
    if (nblocks < 0)
        goto done;

    if (nblocks == 0) {
        //加载initrd.image 到 /dev/ram0
        if (crd_load(in_fd, out_fd, decompressor) == 0)
            goto successful_load;
        goto done;
    }

    /*
     * NOTE NOTE: nblocks is not actually blocks but
     * the number of kibibytes of data to load into a ramdisk.
     */
    if (sys_ioctl(out_fd, BLKGETSIZE, (unsigned long)&rd_blocks) < 0)
        rd_blocks = 0;
    else
        rd_blocks >>= 1;

    if (nblocks > rd_blocks) {
        printk("RAMDISK: image too big! (%dKiB/%ldKiB)
",
               nblocks, rd_blocks);
        goto done;
    }

    /*
     * OK, time to copy in the data
     */
    if (sys_ioctl(in_fd, BLKGETSIZE, (unsigned long)&devblocks) < 0)
        devblocks = 0;
    else
        devblocks >>= 1;

    if (strcmp(from, "/initrd.image") == 0)
        devblocks = nblocks;

    if (devblocks == 0) {
        printk(KERN_ERR "RAMDISK: could not determine device size
");
        goto done;
    }

    buf = kmalloc(BLOCK_SIZE, GFP_KERNEL);
    if (!buf) {
        printk(KERN_ERR "RAMDISK: could not allocate buffer
");
        goto done;
    }

    printk(KERN_NOTICE "RAMDISK: Loading %dKiB [%ld disk%s] into ram disk... ",
        nblocks, ((nblocks-1)/devblocks)+1, nblocks>devblocks ? "s" : "");
    for (i = 0, disk = 1; i < nblocks; i++) {
        if (i && (i % devblocks == 0)) {
            printk("done disk #%d.
", disk++);
            rotate = 0;
            if (sys_close(in_fd)) {
                printk("Error closing the disk.
");
                goto noclose_input;
            }
            change_floppy("disk #%d", disk);
            in_fd = sys_open(from, O_RDONLY, 0);
            if (in_fd < 0)  {
                printk("Error opening disk.
");
                goto noclose_input;
            }
            printk("Loading disk #%d... ", disk);
        }
        sys_read(in_fd, buf, BLOCK_SIZE);
        sys_write(out_fd, buf, BLOCK_SIZE);
#if !defined(CONFIG_S390)
        if (!(i % 16)) {
            pr_cont("%c", rotator[rotate & 0x3]);
            rotate++;
        }
#endif
    }
    printk("done.
");

successful_load:
    res = 1;
done:
    sys_close(in_fd);
noclose_input:
    sys_close(out_fd);
out:
    kfree(buf);
    sys_unlink("/dev/ram");
    return res;
}
handle_initrd()
如果ROOT_DEV != Root_RAM0,则调用handle_init通过linuxrc启动用户态。(即initrd=0Xxxx,但是root=/dev/metdblock2矛盾导致)

static void __init handle_initrd(void)
{
    struct subprocess_info *info;
    static char *argv[] = { "linuxrc", NULL, };
    extern char *envp_init[];
    int error;
    
    //real_root_dev为一个全局变量,用来保存真实文件系统的设备号
    real_root_dev = new_encode_dev(ROOT_DEV);
    
    //在 rootfs 根目录创建真正的根文件系设备节点:/dev/root 是,其设备号是 ROOT_DEV 的值,因此这个节点对应是/dev/ram的INITRD
    create_dev("/dev/root.old", Root_RAM0);
    
    //调用mount_block_root将此设备真实文件系统加载到rootfs的/root下
    mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);、

    //在根目录'/'下创建’/old‘设备节点
    sys_mkdir("/old", 0700);
    //切换到/old目录下
    sys_chdir("/old");
    /* try loading default modules from initrd */
    load_default_modules();

    /*
     * In case that a resume from disk is carried out by linuxrc or one of
     * its children, we need to tell the freezer not to wait for us.
     */
    current->flags |= PF_FREEZER_SKIP;

    info = call_usermodehelper_setup("/linuxrc", argv, envp_init,
                     GFP_KERNEL, init_linuxrc, NULL, NULL);
    if (!info)
        return;
    call_usermodehelper_exec(info, UMH_WAIT_PROC);

    current->flags &= ~PF_FREEZER_SKIP;

    /* move initrd to rootfs' /old */
    sys_mount("..", ".", NULL, MS_MOVE, NULL);
    /* switch root and cwd back to / of rootfs */
    sys_chroot("..");

    //如果real_root_dev直接配置为Root_RAM0,也即直接使用直接使用initrd作为realfs,改变当前目录到initrd中,并直接返回
    //如果real_root_dev在 linuxrc中重新设成Root_RAM0,则initrd就是最终真实的文件系统,改变当前目录到initrd中,不作后续处理直接返回。
    if (new_decode_dev(real_root_dev) == Root_RAM0) {
        sys_chdir("/old");
        return;
    }

    //切换到根目录’/‘
    sys_chdir("/");
    //如果执行完Linuxrc后,没有设置成则Root_RAM0,则需要重新挂载上面linuxrc文件执行时指定的跟文件系统。
    ROOT_DEV = new_decode_dev(real_root_dev);
    //调用mount_root将lfs挂载到VFS的/root目录下,并将当前的目录配置为VFS的/root
    mount_root();

    printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
    error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);
    if (!error)
        printk("okay
");
    else {
        int fd = sys_open("/dev/root.old", O_RDWR, 0);
        if (error == -ENOENT)
            printk("/initrd does not exist. Ignored.
");
        else
            printk("failed
");
        printk(KERN_NOTICE "Unmounting old root
");
        sys_umount("/old", MNT_DETACH);
        printk(KERN_NOTICE "Trying to free ramdisk memory ... ");
        if (fd < 0) {
            error = fd;
        } else {
            error = sys_ioctl(fd, BLKFLSBUF, 0);
            sys_close(fd);
        }
        printk(!error ? "okay
" : "failed
");
    }
}
mount_root()
void __init mount_root(void)
{
#ifdef CONFIG_ROOT_NFS
    if (ROOT_DEV == Root_NFS) {
        //如果网络文件系统挂载成功,则nfs作为根文件系统
        if (mount_nfs_root())  
            return;

        printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.
");
        ROOT_DEV = Root_FD0;
    }
#endif
#ifdef CONFIG_BLK_DEV_FD
    if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {
        /* rd_doload is 2 for a dual initrd/ramload setup */
        if (rd_doload==2) {
            if (rd_load_disk(1)) {
                ROOT_DEV = Root_RAM1;
                root_device_name = NULL;
            }
        } else
            change_floppy("root floppy");
    }
#endif
#ifdef CONFIG_BLOCK
    {
        //挂载磁盘文件系统为根文件系统
          //在rootfs中创建/dev/root设备文件,一般就是指内核启动参数指定的包含根文件系统的设备,在rootfs 中,这个设备文件被命名为 /dev/root
        int err = create_dev("/dev/root", ROOT_DEV);

        if (err < 0)
            pr_emerg("Failed to create /dev/root: %d
", err);
        //把跟文件系统挂载到目录/root
        mount_block_root("/dev/root", root_mountflags);
    }
#endif
}
mount_block_root
do_mount_root() 来挂载根文件系统

void __init mount_block_root(char *name, int flags)
{
    struct page *page = alloc_page(GFP_KERNEL);
    char *fs_names = page_address(page);
    char *p;
#ifdef CONFIG_BLOCK
    char b[BDEVNAME_SIZE];
#else
    const char *b = name;
#endif
//
    get_fs_names(fs_names);
retry:
//针对指定文件系统类型,调用do_mount_root尝试挂载,如果指定的文件系统类型和存储设备上的文件系统类型一致,那么挂载成功。
    for (p = fs_names; *p; p += strlen(p)+1) {
        //
        int err = do_mount_root(name, p, flags, root_mount_data);
        switch (err) {
            case 0:
                goto out;
            case -EACCES:
            case -EINVAL:
                continue;
        }
            /*
         * Allow the user to distinguish between failed sys_open
         * and bad superblock on root device.
         * and give them a list of the available devices
         */
#ifdef CONFIG_BLOCK
        __bdevname(ROOT_DEV, b);
#endif
        printk("VFS: Cannot open root device "%s" or %s: error %d
",
                root_device_name, b, err);
        printk("Please append a correct "root=" boot option; here are the available partitions:
");

        printk_all_partitions();
#ifdef CONFIG_DEBUG_BLOCK_EXT_DEVT
        printk("DEBUG_BLOCK_EXT_DEVT is enabled, you need to specify "
               "explicit textual name for "root=" boot option.
");
#endif
        panic("VFS: Unable to mount root fs on %s", b);
    }
    if (!(flags & SB_RDONLY)) {
        flags |= SB_RDONLY;
        goto retry;
    }

    printk("List of all partitions:
");
    printk_all_partitions();
    printk("No filesystem could mount root, tried: ");
    for (p = fs_names; *p; p += strlen(p)+1)
        printk(" %s", p);
    printk("
");
#ifdef CONFIG_BLOCK
    __bdevname(ROOT_DEV, b);
#endif
    panic("VFS: Unable to mount root fs on %s", b);
out:
    put_page(page);
}
do_mount_root
主要为挂载根文件系统
把包含根文件系统的设备挂载到rootfs中的/root目录上。

static int __init do_mount_root(char *name, char *fs, int flags, void *data)
{
    struct super_block *s;
    //尝试把参数name指定的设备文件挂载到/root下,并cd到新的根文件系统的根目录中去。
    int err = sys_mount(name, "/root", fs, flags, data); 
    if (err)
        return err;

    //设置系统current->fs->pwd为当前目录/root,即把挂载的当前目录设置为/root。
    sys_chdir("/root");

    s = current->fs->pwd.dentry->d_sb;

    ROOT_DEV = s->s_dev;

    printk(KERN_INFO
           "VFS: Mounted root (%s filesystem)%s on device %u:%u.
",
           s->s_type->name,
           sb_rdonly(s) ? " readonly" : "",
           MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
    return 0;
}
sys_mount
sys_chdir
devtmpfs_mount
【作者】张昺华
【大饼教你学系列】https://edu.csdn.net/course/detail/10393
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【微信公众号】 张昺华
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
原文地址:https://www.cnblogs.com/sky-heaven/p/13742139.html