linux文件系統初始化過程(4)---加載initrd(中)

1、目的

    上文詳細介紹了CPIO格式的initrd文件,本文從源代碼角度分析加載並解析initrd文件的過程。node

    initrd文件linux內核通常存儲在磁盤空間中,在系統啓動階段由bootload負責把磁盤上的內核和initrd加載到指定的內存空間中;而後,再由內核讀取和解析initrd文件,在VFS(目前只有rootfs的根目錄)中新建目錄、常規文件、符號連接文件以及特殊文件;這樣VFS就從根目錄"/"成長爲一棵枝繁葉茂的大樹了linux

 

2、函數調用過程

 

    initrd詳細的加載過程在init/initramfs.c中實現的,爲了更好的理解加載過程,咱們給出了關鍵函數的調用關係圖1。這裏須要注意下,因爲使用roofs_initcall()宏在initcallroofs段中註冊了populate_rootfs()函數,所以在執行do_initcalls()函數時會隱示調用populate_rootfs()。緩存


                                圖1數據結構

3、initcall簡介

 

    linux在代碼段中定義了一個特殊的段initcall,該段中存放的都是函數指針;linux初始化階段調用do_initcalls()依次執行該段的函數。關於該段的詳細信息能夠參見vmlinux.lds.S連接腳本。函數

    用戶能夠調用如下一組宏在initcall段中註冊函數指針;initcall段分爲initcall0-initcall78個等級,initcall0段的優先級最高,initcall7段的優先級最低,優先級高的段最早被執行;initcallrootfs段優先級介於56之間。post

#define __define_initcall(fn, id) \
179     static initcall_t __initcall_##fn##id __used \
180     __attribute__((__section__(".initcall" #id ".init"))) = fn

 

187 #define early_initcall(fn)          __define_initcall(fn, early)
      
196 #define pure_initcall(fn)           __define_initcall(fn, 0)
        
198 #define core_initcall(fn)           __define_initcall(fn, 1)
199 #define core_initcall_sync(fn)      __define_initcall(fn, 1s)
200 #define postcore_initcall(fn)       __define_initcall(fn, 2)
201 #define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)
202 #define arch_initcall(fn)           __define_initcall(fn, 3)
203 #define arch_initcall_sync(fn)      __define_initcall(fn, 3s)
204 #define subsys_initcall(fn)         __define_initcall(fn, 4)
205 #define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
206 #define fs_initcall(fn)             __define_initcall(fn, 5)
207 #define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
208 #define rootfs_initcall(fn)         __define_initcall(fn, rootfs)
209 #define device_initcall(fn)         __define_initcall(fn, 6)
210 #define device_initcall_sync(fn)    __define_initcall(fn, 6s)
211 #define late_initcall(fn)           __define_initcall(fn, 7)
212 #define late_initcall_sync(fn)      __define_initcall(fn, 7s)

  用戶使用不一樣優先級的initcall宏能夠很方便的在linux代碼中註冊函數指針;將這些函數指針存儲在相應的initcall段中;最終,由do_initcalls()按照優先級依次執行段中的函數,具體的代碼實現以下:spa

715 static initcall_t *initcall_levels[] __initdata = {
716     __initcall0_start,
717     __initcall1_start,
718     __initcall2_start,
719     __initcall3_start,
720     __initcall4_start,
721     __initcall5_start,
722     __initcall6_start,
723     __initcall7_start,
724     __initcall_end,
725 };

678 int __init_or_module do_one_initcall(initcall_t fn)
679 {
681     int ret;
686     ret = fn();
    }

739 static void __init do_initcall_level(int level)
740 {
742     initcall_t *fn;
            ... 
751     for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
752         do_one_initcall(*fn);
753 }
754 
755 static void __init do_initcalls(void)
756 {
757     int level;
758 
759     for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
760         do_initcall_level(level);
761 }

回到加載initrd這個話題中,在init/initram.c的最後使用rootfs_initcall宏註冊了populate_rootfs()函數;基於以上分析,咱們知道這裏就是加載initrd文件的入口,下面就開始分析該函數的功能。指針

627 rootfs_initcall(populate_rootfs);

4、加載initrd文件

 

    系統啓動階段,bootloadinitrd加載到內存起始地址爲initrd_start,結束地址爲initrd_end的內存中。code

    populate_rootfs()調用unpack_to_rootfs()從內存中讀取並解析initrd文件;根據CPIO的格式咱們知道initrd文件是由不少個段組成,且段中又是由文件頭、文件名和文件體組成,所以該解析程序可使用了狀態機原理處理initrd文件。blog

    解析程序定義瞭如下8種狀態:Start(初始狀態)Collect(獲取符號連接文件信息狀態)GotHeader(獲取文件頭信息狀態)SkipIt(跳過該段狀態)GotName(獲取文件名並新建文件狀態)CopyFile(寫文件狀態)GotSymlink(新建符號連接文件狀態)Reset(終止狀態)。

376 static __initdata int (*actions[])(void) = {
377     [Start]     = do_start,
378     [Collect]   = do_collect,
379     [GotHeader] = do_header,
380     [SkipIt]    = do_skip,
381     [GotName]   = do_name,
382     [CopyFile]  = do_copy,
383     [GotSymlink]    = do_symlink,
384     [Reset]     = do_reset,
385 };

 

    爲了直觀理解initrd文件的解析過程,下面給出狀態機跳轉圖2。

    從圖中能夠看出將文件分爲符號連接和非符號連接兩種狀況處理,這是由於符號連接文件是一種特殊的文件,只有第一個符號連接文件的inode存儲的是真實數據,而其餘符號連接文件inode中存儲的是第一個符號連接文件的路徑名,所以須要把第一個符號連接文件的路徑名緩存起來,緩存的數據結構是hash表,因此在處理符號連接文件時多了一些hash表的操做,所以分爲了符號連接文件和非符號連接文件這兩種狀況來處理。

    initrd文件的詳細解析過程以下:

    1S0:初始狀態,初始化一些全局變量;

    2S1:獲取符號連接文件的文件頭和文件體;

    3S2:根據CPIO格式的定義,獲取文件頭信息;

    4S3:跳過當前CPIO格式的段,繼續處理下一個段;

    5S4:獲取文件名,並在VFS中新建文件;

    6S5:將文件內容寫入到新建文件中;

    7S6:新建符號連接文件;

    8S7:處理完當前CPIO格式的段,繼續一個段的處理。

 

    從圖中還能夠看出,因爲目錄文件和特殊文件沒有文件內容,所以跳過了S5狀態,直接進入S3狀態。

                                                                     圖2

5、總結

 

    經過以上分析,程序就能夠成功解析initrd文件,並使用sys_dir()、sys_open()、sys_mknod()、sys_symlink()等系統調用新建目錄、常規文件、特殊文件和符號連接文件了。此時,VFS從只有根目錄"/"成長爲了一棵內容豐富的大樹。

相關文章
相關標籤/搜索