まず、目的
CPIO形式のinitrdファイルについては、上記で詳しく説明しました。この記事では、ソースコードの観点から、initrdファイルのロードと解析のプロセスを分析します。
initrdファイルとLinuxカーネルは通常、ディスクスペースに保存されます。システムの起動フェーズでは、bootloadがカーネルとディスク上のinitrdを指定されたメモリスペースにロードします。次に、initrdファイルが読み取られて解析されます。カーネルは、VFS(現在はrootfsのルートディレクトリのみ)で、新しいディレクトリ、通常のファイル、シンボリックリンクファイル、および特殊ファイルを作成します。このようにして、VFSはルートディレクトリ「/」から大きくて緑豊かなツリーに成長します。
次に、関数呼び出しプロセス
initrdの詳細なロードプロセスは、init / initramfs.cに実装されています。ロードプロセスをよりよく理解するために、主要な関数の呼び出し関係図1を示します。ここで、roofs_initcall()マクロはpopulate_rootfs()関数をinitcallroofsセクションに登録するために使用されるため、do_initcalls()関数が実行されるときにpopulate_rootfs()が暗黙的に呼び出されることに注意してください。
図1
3、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) 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 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 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()は、優先度に従ってセクション内の関数を実行します。コードは次のように実装されます。次のとおりです。
* initcall_levels [initcall_t 715静的] __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 { 757int レベル; 758 759 for(level = 0; level <ARRAY_SIZE(initcall_levels)-1; level ++) 760 do_initcall_level(level); 761}
initrdのロードのトピックに戻ると、init / initram.cの最後で、populate_rootfs()関数がrootfs_initcallマクロに登録されています。上記の分析に基づいて、これがinitrdファイルをロードするためのエントリポイントであることがわかります。この関数の機能を分析してみましょう。
627 rootfs_initcall(populate_rootfs);
第四に、initrdファイルをロードします
システムの起動フェーズでは、ブートロードは、開始アドレスがinitrd_startで終了アドレスがinitrd_endであるメモリにinitrdをロードします。
Populate_rootfs()はunpack_to_rootfs()を呼び出して、メモリからinitrdファイルを読み取って解析します。CPIO形式によれば、initrdファイルは多くのセグメントで構成され、セグメントはファイルヘッダー、ファイル名、ファイル本体で構成されていることがわかります。 、したがって分析プログラムは、ステートマシンの原理を使用してinitrdファイルを処理できます。
パーサーは、次の8つの状態を定義します:開始(初期状態)、収集(シンボリックリンクファイル情報の状態を取得)、GotHeader(ファイルヘッダー情報の状態を取得)、SkipIt(このセクションの状態をスキップ)、GotName(ファイル名を取得して新しい状態を作成)ファイルステータス)、CopyFile(書き込みファイルステータス)、GotSymlink(新しいシンボリックリンクファイルステータス)、リセット(終了ステータス)。
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に格納されるため、最初のシンボリックリンクファイルのパス名をキャッシュする必要があります。キャッシュされるデータ構造はハッシュテーブルであるため、シンボリックを処理するときによく使用されます。リンクファイル。一部のハッシュテーブルの操作は、シンボリックリンクファイルと非シンボリックリンクファイルの2つのケースに分けられます。
initrdファイルの詳細な分析プロセスは次のとおりです。
1. S0:初期状態、いくつかのグローバル変数を初期化します。
2. S1:シンボリックリンクファイルのファイルヘッダーとファイル本文を取得します。
3. S2:CPIO形式の定義に従ってファイルヘッダー情報を取得します。
4. S3:現在のCPIO形式のセグメントをスキップして、次のセグメントの処理を続行します。
5. S4:ファイル名を取得し、VFSで新しいファイルを作成します。
6. S5:ファイルの内容を新しく作成されたファイルに書き込みます。
7. S6:新しいシンボリックリンクファイルを作成します。
8. S7:現在のCPIO形式のセグメントを処理した後、セグメントの処理を続行します。
この図から、ディレクトリファイルと特殊ファイルにはファイルの内容がないため、S5状態がスキップされ、S3状態に直接入ることがわかります。
図2
五数要約
上記の分析により、プログラムはinitrdファイルを正常に解析し、sys_dir()、sys_open()、sys_mknod()、sys_symlink()、およびその他のシステムコールを使用して、新しいディレクトリ、通常のファイル、特殊ファイル、およびシンボリックリンクファイルを作成できます。この時点で、VFSはルートディレクトリ「/」のみからリッチコンテンツを含む大きなツリーに成長しました。