1. 程式人生 > 實用技巧 >檔案子系統-(rootfs)根檔案系統掛載流程03-markdown【轉】

檔案子系統-(rootfs)根檔案系統掛載流程03-markdown【轉】

轉自:http://news.migage.com/articles/檔案子系統(rootfs)根檔案系統掛載流程03_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)掛載流程
檔案系統的製作…

1. rootfs掛載流程分析

預設根目錄已經掛上去了,此處為掛載具體的檔案系統。
linux/init

1.1 不同型別掛載情況梳理:

1.1.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

1.1.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")

1.1.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\n");
//所以如果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)\n",
           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.\n");
    
    (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)匯出.

do_initcalls();

initcalls機制
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[] 

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...\n");
        

​    //判斷載入的是不是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\n", 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)\n",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...\n");
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start);
        if (err)
            printk(KERN_EMERG "Initramfs unpacking failed: %s\n", 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\n", 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);機制

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...\n",
           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...\n",
            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)\n",
           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\n");
    goto done;
}

buf = kmalloc(BLOCK_SIZE, GFP_KERNEL);
if (!buf) {
    printk(KERN_ERR "RAMDISK: could not allocate buffer\n");
    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.\n", disk++);
        rotate = 0;
        if (sys_close(in_fd)) {
            printk("Error closing the disk.\n");
            goto noclose_input;
        }
        change_floppy("disk #%d", disk);
        in_fd = sys_open(from, O_RDONLY, 0);
        if (in_fd < 0)  {
            printk("Error opening disk.\n");
            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\b", rotator[rotate & 0x3]);
            rotate++;
        }
#endif
    }
    printk("done.\n");

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\n");
else {
    int fd = sys_open("/dev/root.old", O_RDWR, 0);
    if (error == -ENOENT)
        printk("/initrd does not exist. Ignored.\n");
    else
        printk("failed\n");
    printk(KERN_NOTICE "Unmounting old root\n");
    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\n" : "failed\n");
}

}

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.\n");
​    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\n", 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
      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\n",
          root_device_name, b, err);
                  printk("Please append a correct \"root=\" boot option; here are the available partitions:\n");

        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.\n");
#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:\n");
    printk_all_partitions();
    printk("No filesystem could mount root, tried: ");
    for (p = fs_names; *p; p += strlen(p)+1)
        printk(" %s", p);
    printk("\n");

#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.\n",
       s->s_type->name,
       sb_rdonly(s) ? " readonly" : "",
       MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
return 0;

}

sys_mount
sys_chdir
devtmpfs_mount