天天看點

linux檔案系統初始化過程(4)---加載initrd(中)一、目的二、函數調用過程三、initcall簡介四、加載initrd檔案五、總結版權聲明:

一、目的

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

    initrd檔案和linux核心一般存儲在磁盤空間中,在系統啟動階段由bootload負責把磁盤上的核心和initrd加載到指定的記憶體空間中;然後,再由核心讀取和解析initrd檔案,在VFS(目前隻有rootfs的根目錄)中建立目錄、正常檔案、符号連結檔案以及特殊檔案;這樣VFS就從根目錄"/"成長為一棵枝繁葉茂的大樹了。

二、函數調用過程

    initrd詳細的加載過程在init/initramfs.c中實作的,為了更好的了解加載過程,我們給出了關鍵函數的調用關系圖1。這裡需要注意下,由于使用roofs_initcall()宏在initcallroofs段中注冊了populate_rootfs()函數,是以在執行do_initcalls()函數時會隐示調用populate_rootfs()。

linux檔案系統初始化過程(4)---加載initrd(中)一、目的二、函數調用過程三、initcall簡介四、加載initrd檔案五、總結版權聲明:

                                圖1

三、initcall簡介

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

    使用者可以調用以下一組宏在initcall段中注冊函數指針;initcall段分為initcall0-initcall7這8個等級,initcall0段的優先級最高,initcall7段的優先級最低,優先級高的段最先被執行;initcallrootfs段優先級介于5和6之間。

#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()按照優先級依次執行段中的函數,具體的代碼實作如下:

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);
           

四、加載initrd檔案

    系統啟動階段,bootload将initrd加載到記憶體起始位址為initrd_start,結束位址為initrd_end的記憶體中。

    populate_rootfs()調用unpack_to_rootfs()從記憶體中讀取并解析initrd檔案;根據CPIO的格式我們知道initrd檔案是由很多個段組成,且段中又是由檔案頭、檔案名和檔案體組成,是以該解析程式可以使用了狀态機原理處理initrd檔案。

    解析程式定義了以下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檔案的詳細解析過程如下:

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

    2、S1:擷取符号連結檔案的檔案頭和檔案體;

    3、S2:根據CPIO格式的定義,擷取檔案頭資訊;

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

    5、S4:擷取檔案名,并在VFS中建立檔案;

    6、S5:将檔案内容寫入到建立檔案中;

    7、S6:建立符号連結檔案;

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

    從圖中還可以看出,由于目錄檔案和特殊檔案沒有檔案内容,是以跳過了S5狀态,直接進入S3狀态。

linux檔案系統初始化過程(4)---加載initrd(中)一、目的二、函數調用過程三、initcall簡介四、加載initrd檔案五、總結版權聲明:

                                                                     圖2

五、總結

    通過以上分析,程式就可以成功解析initrd檔案,并使用sys_dir()、sys_open()、sys_mknod()、sys_symlink()等系統調用建立目錄、正常檔案、特殊檔案和符号連結檔案了。此時,VFS從隻有根目錄"/"成長為了一棵内容豐富的大樹。

版權聲明:

    原創作品,如非商業性轉載,請注明出處;如商業性轉載出版,請與作者聯系。

繼續閱讀