Linuxのブート過程: 電源ボタンからカーネルまで
(0xkato.xyz)- コンピュータの電源ボタンを押した瞬間からLinuxカーネルが実行されるまでの過程を、段階ごとに説明した技術解説
- CPUがリアルモード(real mode) で起動し、プロテクトモード(protected mode) と ロングモード(long mode) に移行する過程を具体的に扱う
- BIOS/UEFIファームウェア、ブートローダー(GRUB)、カーネルの展開とアドレス再配置など、各段階の役割と動作原理を詳細に記述
- メモリマッピング、割り込み、ページテーブル、kASLR など、カーネル初期化に必要な中核概念を簡潔な例とともに説明
- Linuxブートの内部メカニズムを理解することで、システムアーキテクチャ、セキュリティ、性能最適化に関する洞察を提供
Part 1 — 電源ボタンからカーネルの最初の実行まで
-
電源ボタンを押すと、CPUはリアルモード(real mode) にリセットされ、最初の命令を実行する
- リアルモードは8086時代から存在する単純なアドレス体系で、セグメント(segment) と オフセット(offset) を組み合わせて物理アドレスを計算する
- 例:
physical_address = (segment << 4) + offset - CPUはリセット後、
0xFFFFFFF0アドレス(リセットベクタ)へジャンプし、ファームウェアに制御を渡す
-
レジスタ(register) はCPU内部の超高速ストレージスロットで、CS(コードセグメント)、IP(命令ポインタ)などがある
- CSは現在のコード位置、IPは次に実行する命令を指す
BIOSとUEFI
- BIOS は旧来のファームウェアで、POST(電源投入時自己診断)の後にブート順序を確認し、起動可能なディスクを探索する
- 起動可能なディスクは、先頭512バイトセクタの末尾が
0x55AAで示される - BIOSはこのセクタを
0x7C00アドレスへコピーしてからジャンプし、実行する
- 起動可能なディスクは、先頭512バイトセクタの末尾が
- UEFI は現代的な代替技術で、ファイルシステムを直接理解し、より大きなブートプログラムを読み込める
- BIOSと異なり「最初のセクタ」という制約がなく、OSへより豊富なシステム情報を渡せる
ブートローダー(bootloader)
- ブートローダー はカーネルをメモリに読み込み、実行準備を整えるプログラム
- 代表的には GRUB が使われ、設定ファイルを読み、カーネルと初期RAMディスク(initrd)をメモリへロードする
- カーネルファイルは、リアルモード用の小さなセットアッププログラム と 圧縮されたカーネル本体 で構成される
- GRUBはカーネル位置、コマンドライン、initrd位置などの情報を setup header 構造体に記録した後、カーネルのセットアップコードへジャンプする
セットアッププログラム(setup code)
- カーネル実行前に予測可能な作業空間を作る役割を果たす
- セグメントレジスタ(CS, DS, SS)を揃え、direction flag をクリアしてメモリコピーが一定に動作するよう設定する
- スタック(stack) を作成し、関数呼び出し時の一時データを保存する
- BSS領域(初期値0で始まるべきグローバル変数領域)を0で初期化する
earlyprintkオプションがあれば、シリアルポートを設定して初期デバッグメッセージを出力できる- ファームウェアにRAMマップ(e820) を要求し、使用可能なメモリ領域と予約領域を把握する
- すべての準備が終わると、最初のC関数である
mainを呼び出し、その後モード移行段階へ入る
割り込み(interrupt)
- 割り込み はCPUが現在の作業を一時停止し、緊急処理を行うメカニズム
- キー入力、タイマーなどのハードウェアイベントが代表例
- マスク可能割り込み は一時的に遮断でき、NMI(Non-Maskable Interrupt) は常に処理される
- モード移行中は予期しない割り込みを防ぐため、一時的に遮断する
Part 2 — リアルモードから32ビット、そして64ビットへ
- 現代のLinuxはx86_64アーキテクチャのロングモード(long mode) で動作する
- リアルモード → プロテクトモード → ロングモードの順に段階的な移行が必要
プロテクトモード(protected mode)
- 1980年代の限界を超えるために導入された32ビットモードで、2つの中核構造を持つ
- GDT(Global Descriptor Table) : セグメントの開始アドレス、サイズ、権限を定義する
- Linuxはフラットモデル(flat model) を使い、32ビット空間全体を1つの連続領域として単純化する
- IDT(Interrupt Descriptor Table) : 割り込み発生時に呼び出すハンドラのアドレスを保存する
- ブート中は最小限のIDTだけをロードし、カーネル初期化後に完全なIDTを設置する
- GDT(Global Descriptor Table) : セグメントの開始アドレス、サイズ、権限を定義する
モード移行過程
- セットアップコードはまず 割り込み無効化、PICチップ停止、A20ライン有効化、数値演算コプロセッサ初期化 を実行する
- A20ラインは1MBアドレスのラップアラウンド問題を解決するための歴史的な仕組み
- 最小限のGDTとIDTをロードした後、CR0レジスタのPEビット を設定して far jump を行う
- これによりプロテクトモードへ入り、セグメントとスタックポインタを新しいアドレス体系に合わせて再設定する
制御レジスタ(control registers)
- CR0: プロテクトモードを有効化
- CR3: ページテーブルの最上位アドレスを保存
- CR4: PAEなどの拡張機能を有効化
ロングモード(long mode)移行の準備
- 64ビットモードへ移行するには、2つの条件が必要
- ページング(paging) の有効化: 仮想アドレスと物理アドレスの間のマッピングを行う
- EFERレジスタのLME(Long Mode Enable) ビットの設定
- ページテーブルは4KB単位のページをマッピングするが、初期ブート時には2MB単位の identity map で単純に構成する
ページング有効化手順
- PAE 機能をCR4で有効にし、低位アドレス領域を2MB単位でカバーする最小限のページテーブルを作成する
- 最上位テーブルのアドレスをCR3に記録した後、ページングを有効化する
- EFERのLMEビットをセットし、64ビットコードへジャンプして ロングモードへ移行 する
- アドレスとレジスタが64ビット幅へ拡張された状態で、カーネル実行の準備が整う
Part 3 — カーネルの展開、アドレス修正、そして自己移動
- ここでCPUは64ビットモードにあり、メモリ上には圧縮されたカーネルイメージ が存在する
- 小さな64ビットスタブ(stub)がカーネルを展開し、アドレスを調整する役割を担う
初期整理と安全装置の設定
- スタブは自分の実際の実行位置を計算し、カーネルが重なる場合は 自己コピー(self-relocation) によって安全な位置へ移動する
- 自身の BSS領域を初期化 し、簡易IDT をロードする(ページフォルトとNMIハンドラを含む)
- ページフォルトが発生した場合は、欠けているマッピングをただちに追加して復旧する
- カーネル、ブートパラメータ、コマンドラインバッファなど、必要な領域に対する identityマッピング を作成する
カーネルの展開
extract_kernel関数が実行され、カーネルの圧縮を展開する- gzip、xz、zstd、lzo など、さまざまな圧縮アルゴリズムをサポート
- 展開後は ELFヘッダ を読み、コード/データセクションを正しいアドレスへコピーする
- カーネルがビルドされたアドレスと実際のロードアドレスが異なる場合は 再配置(relocation) を行う
- アドレスを含む命令やポインタを修正し、実際のメモリ位置に合わせる
- すべての準備が終わると
start_kernel関数 へジャンプし、本格的なカーネル初期化が始まる
カーネルの自己移動(kASLR)
- kASLR (Kernel Address Space Layout Randomization) は、カーネルの物理・仮想アドレスをランダム化して攻撃の難易度を高める
- ブート時に2つのベース(base)をランダムに選ぶ
- 物理ベース: カーネルが実際に配置されるRAMアドレス
- 仮想ベース: カーネルが使用する仮想アドレスの開始点
- ブート時に2つのベース(base)をランダムに選ぶ
- 選択過程
- ブートローダー、initrd、コマンドラインバッファなど、保護すべき領域の一覧 を作成する
- ファームウェアのメモリマップを走査し、十分に大きい空き領域を探す
- ハードウェア乱数命令などから得た エントロピー でランダムなスロットを選ぶ
- 適切な領域がなければデフォルトアドレスに戻り、
nokaslrオプションを指定するとランダム化を無効化する
用語まとめ
- Hexadecimal(16進数) :
0x接頭辞で表し、ハードウェアのビット構造やアラインメントに向いている - Register: CPU内部の一時記憶領域 (CS, DS, SS, IP, SP など)
- Segment/Offset: リアルモードのアドレス計算方式
(segment * 16 + offset) - BIOS/UEFI: システム初期化とブートプログラムのロードを担うファームウェア
- Bootloader(GRUB) : カーネルのロードとシステム情報の受け渡しを行う
- Stack/BSS: 関数の一時保存領域と、0で初期化されたグローバル変数領域
- Interrupt/NMI: ハードウェア・ソフトウェアイベントの処理メカニズム
- GDT/IDT: セグメントおよび割り込み定義テーブル
- A20 Line: 1MBアドレスのラップアラウンドを防ぐスイッチ
- Protected Mode/Long Mode: 32ビットおよび64ビットの実行モード
- Paging/Page Tables: 仮想アドレスと物理アドレス
まだコメントはありません。