世界を動かす技術を、日本語で。

Linuxのブートプロセス:電源ボタンからカーネルまで

概要

  • 電源ボタンを押してからLinuxカーネルが動き出すまでの一連の流れを解説
  • CPUのリセットからBIOS/UEFI、ブートローダ、セットアッププログラムの役割
  • 32ビット保護モードから64ビットロングモードへの移行手順
  • カーネルの展開とアドレス修正、kASLRによるランダム化
  • 低レベルの仕組みと用語の簡単な説明付き

電源投入からカーネルの最初の命令まで

  • 電源ボタン を押すと、CPUは自動的にリセットされ、 real mode (8086時代のモード)で起動

  • メモリアドレス はセグメントとオフセットで構成され、hex(16進数)表記が多用される

  • リセットベクタ(0xFFFFFFF0) にジャンプし、ごく短いジャンプ命令で マザーボードのファームウェア (BIOSまたはUEFI)へ制御を渡す

    • レジスタ はCPU内の高速な記憶スロット、CSはコードセグメント、IPは命令ポインタ
  • BIOS はPOST(電源投入時自己診断)を実行し、ブート順に従いデバイスを調査

    • ブート可能なデバイスを見つけると、最初の512バイトセクタ(0x55, 0xAAで終了)を0x7C00にコピーし、そこへジャンプ
    • セクタは小さいため、次の大きなプログラムを読み込むだけの役目が多い
  • UEFI はファイルシステムを直接理解し、より大きなブートプログラムを読み込むことが可能

    • より多くの情報をOSに渡す

ブートローダとセットアッププログラム

  • ブートローダ (例:GRUB)はOSをメモリに配置する案内役
    • 設定を読み込み、メニュー表示やLinuxカーネルのロードを実行
    • カーネルファイルには小さな setupプログラム (real mode用)と圧縮済みカーネル本体が含まれる
    • setup headerにカーネル配置場所やコマンドライン、initrdの位置などの情報を格納
  • setupプログラム は作業空間の初期化を担当
    • セグメントレジスタ(CS, DS, SS)を整え、 direction flag をクリアし、スタックを作成

    • BSS領域 (グローバル変数の初期化用)をゼロクリア

    • earlyprintk が指定されていればシリアルポートを初期化

    • ファームウェアから利用可能なRAM領域情報(e820)を取得

    • 最初のC関数mainを呼び出し、real modeからの脱出準備

    • 割り込み はCPUの「割り込み」機能で、maskable(一時的に無効化可能)とnon-maskable(NMI、常に割り込む)が存在

32ビット保護モードから64ビットロングモードへの移行

  • protected mode は1980年代の制限を超える32ビット世界

    • GDT (グローバルディスクリプタテーブル)でセグメント定義
    • Linuxは flat model (全空間を単純に扱う)を採用
    • IDT (割り込みディスクリプタテーブル)は割り込み時のジャンプ先管理
  • 移行手順

    • 割り込みを無効化し、 A20ライン を開放(1MB超アドレスのための歴史的対応)

    • FPU (浮動小数点演算ユニット)をリセット

    • 必要最小限のGDTとIDTをロード

    • CR0 のPEビットをセットし、far jumpで保護モードへ

    • データ・スタックセグメントを再設定し、32ビット保護モードに移行

    • 制御レジスタ (CR0, CR3, CR4)はモード切替やページテーブルのアドレス管理に使用

  • 64ビットロングモード (Linuxが本当に動作するモード)に移行するには

    • ページング (仮想アドレス→物理アドレス変換)が必須
    • PAE (CR4で有効化)と LME (EFERレジスタで有効化)をセット
    • 低メモリを2MB単位でカバーする最小限のページテーブルを構築し、CR3にセット
    • LMEを有効化し、64ビットコードへfar returnでジャンプ
  • モード切替時は割り込みや状態管理を慎重に扱い、異常状態を防止

カーネル展開、アドレス修正、kASLR

  • 64ビットスタブ が自身の実行位置を確認し、必要なら安全な場所へ移動

  • BSS領域 をクリアし、最小限のIDT(ページフォールト、NMI用)をセット

    • ページフォールト発生時は、その場でマッピング追加
  • カーネルやブートパラメータ、コマンドラインバッファのためにアイデンティティマッピングを用意

  • extract_kernel 関数がカーネル展開を担当

    • 一時ヒープを確保し、圧縮アルゴリズム(gzip, xz, zstd, lzo等)でカーネルを解凍
    • ELFヘッダ を読み、必要なアドレスに各セクションを配置
    • 配置先がビルド時と異なる場合は リロケーション 処理でアドレス修正
    • 展開完了後、カーネルのエントリーポイントにジャンプし、ブートパラメータを渡す
    • 最初に呼ばれる関数は start_kernel で、本格的な初期化が始まる
  • kASLR (Kernel Address Space Layout Randomization)は、カーネルの物理・仮想ベースアドレスをランダム化し、セキュリティ向上を図る技術

    • 攻撃者にカーネル位置を特定させにくくする工夫

この流れで、電源投入からLinuxカーネルの本格的な起動までの詳細なプロセスを把握可能。低レベルの仕組みや用語も理解しやすい構成。

Hackerたちの意見

文字が薄くて、スマホで読むのが難しいな。

デスクトップブラウザでもスタイリングが悪いよ。FirefoxやFirefox Mobileを使うなら、こういう場合はリーダーモードがいいよ。

自虐的にダウンボートされた見た目。

トピックは面白いけど、なんかおばあちゃん向けって感じだね。

電源が安定すると、CPUは「リアルモード」と呼ばれる小さくて古いモードにリセットされる。リアルモードは元々の8086チップにさかのぼる。ルールは意図的にシンプルだ。メモリアドレスは、CPUが特別な高速ストレージ(レジスタ)に保持している2つの値から構成される。セグメントとオフセットを組み合わせるんだ。物理アドレス = (セグメント おばあちゃん、こういうのに異常に詳しそうだね。

大学で教わったことの一つが、オーディエンス分析なんだ。これについてよく考えるよ。何が既に知られていると期待されているのか? どの略語やフレーズを定義する必要があるのか? などなど。この技術はまだまだ未熟で、テックライターも同じように苦労してるみたい。

ページソースに:uwu OwO これは何?

まだ進行中だよ。

UEFIはファームウェアによって実装されたインターフェース(文字通り、統一拡張ファームウェアインターフェース)で、ファームウェアそのものではない。「マシンを起動する」って言うのはちょっと用語の使い方を間違えてるね。マシンを起動するのはファームウェアで、UEFIを通じてファームウェアとやり取りするんだ。この投稿は、現代のファームウェアのダンスの面白い部分をすっ飛ばしてるよ。特に、ExitBootServices()を呼び出すときにはもうロングモードに入ってるし、リアルモードやプロテクトモードを経る必要はないんだ。

これについてもっと読むにはどこに行けばいいの?

なんか変な記事だな。片方では面白いトピックなんだけど、もう片方では、なんで16進数が何かを説明してるの? このレベルの詳細に興味がある人が、16進数を知らないってどういうこと? もしかして考えすぎかも。でも、これが私の最大の疑問には答えてないんだよね。物理的な押しからリセットベクターにどうやって行くのか? その魔法はハードウェアや物理学、電子工学でどう機能するのか、知りたいな。

HNはITのプロだけが読むわけじゃないよ。Linuxのスタートアップについてちょっと興味があるかもしれないけど、16進数について知ってることや知らないことを思い出す必要はないんじゃないかな。

著者にとって役立つ提案がたくさんあるみたいだね。私の提案は、教育コンテンツにはインタラクティブなスタイルがもっと合うかもってこと。HNで評判の良い投稿があるよ: https://www.nan.fyi/database、これはフレームワークで作られてる: https://github.com/nandanmen/NotANumber

もっと詳細な似たような記事ってないかな? お気に入りのマイクロプロセッサのデータシートを読む気はあまりないけど、提供されてる以上の詳細が欲しいな。特にUEFI/BIOSの前に。

リセットベクターからも明らかなように、80286とその後継機は実際には非現実モードでブートするんだ。80386では、コードセグメントのベースアドレスは0xffff0000で、これは16ビットのCSレジスタを4ビットシフトしても得られない。ディスクリプタキャッシュは、リセット時に正しい値でロードされるだけなんだ。リアルモードでCSに書き込むと、キャッシュされた値がCS * 16で上書きされる。

これを読むと、x86/Linuxのブートチェーンがどれだけ化石のような儀式で、後方互換性のためにくっつけられているかが強調されるね。

すごく良い投稿だね。数ヶ月前に私もLinuxのブートについて書いたけど、もう少しIO側(ディスク上に何があって、どうやってロードされるか)に焦点を当ててたよ。ここにあるよ: https://blog.davidv.dev/posts/booting-x86-64/