PK
パーネルカニック研究所
今日もどこかでkernel panic。
root@pk-lab:~#

initramfsとは何か

連載ナビ

この連載は「Linuxを使えるようになる」ではなく、「Linuxがどう動いているのか」を理解するための実践シリーズです。

今回は Linuxを理解する 〜 LFSで学ぶLinux内部構造 〜 の Part 5 / 第2回として、仕組みのつながりを意識しながら学びます。

—

initramfsは「本物のルートをマウントするための、メモリ上の仮ルート」

前回、「ext4ドライバをモジュールにすると起動できない」という循環問題を残しました。その答えがinitramfsです。

結論から言うと、initramfsとは、必要最小限のコマンドとドライバを詰めたcpio形式のアーカイブで、カーネルが起動直後にメモリ上へ展開して「仮のルートファイルシステム」として使うものです。Ubuntuの /boot を見ると、カーネル本体とペアで必ず置かれています。


/boot/vmlinuz-6.8.0-45-generic      ← カーネル本体
/boot/initrd.img-6.8.0-45-generic   ← initramfs(カーネルとペアで存在する)

ブートローダ(GRUB)はカーネルと一緒にこのファイルもメモリにロードし、カーネルはまずこの仮ルート上でユーザー空間の処理を始めます。本物のルートファイルシステムのマウントは、この仮ルートの中から行われます。

「ルートを読むためのドライバが、ルートの中にある」を解決するため

なぜこんな二段構えが必要なのか。理由は、前回見た鶏と卵の問題を、カーネルを肥大化させずに解決するためです。

カーネルモジュールは /lib/modules/ 、つまりルートファイルシステムの中にあります。しかしルートファイルシステムをマウントするには、ディスクコントローラのドライバやext4のドライバが先に必要です。解決策は2つしかありません。

1. 必要なドライバを全部カーネル本体に組み込む(y にする)

2. ドライバを含む小さなファイルシステムをディスクとは別経由で先に渡しておく

方法1は、自分のマシン1台のためにビルドするLFSなら合理的です。しかしUbuntuのように「どんなディスク構成のマシンでも起動できる」必要があるディストリビューションでは、全ドライバの組み込みは非現実的です。そこで方法2、つまりinitramfsが使われます。ブートローダ経由で渡されるためディスクのマウントが不要で、その中のドライバを使って本物のルートに到達できます。

さらにinitramfsは、単純なドライバロード以上の仕事もこなします。LUKSで暗号化されたルートの復号(パスフレーズ入力画面はinitramfsの中で動いています)、LVMボリュームの認識、NFSルートのネットワーク設定などは、いずれも「ルートをマウントする前」に必要な処理であり、initramfsの担当です。

中身を実際に開けて見る

initramfsは中身を確認できます。Debian/Ubuntu系には lsinitramfs というコマンドがあります。


lsinitramfs /boot/initrd.img-$(uname -r) | head -n 40

出力にはこんなファイルが並びます。


.
init
bin
lib/modules/6.8.0-45-generic/kernel/fs/ext4/ext4.ko
lib/modules/6.8.0-45-generic/kernel/drivers/ata/ahci.ko
scripts/local
etc/fstab
...

注目すべきは2点です。

  • init: カーネルが仮ルートを展開した後、最初に実行するプログラム(多くの場合シェルスクリプト)。ここにドライバのロードとルートのマウント手順が書かれています
  • lib/modules/ 以下の .ko ファイル: ルート到達に必要なカーネルモジュールの抜粋。フルセットではなく、起動に関係するものだけが選ばれて入っています

このファイルを生成しているのが、Debian/Ubuntu系では update-initramfs(設定は /etc/initramfs-tools/)、Fedora/RHEL系では dracut です。カーネルを更新すると新しい initrd.img が自動生成されるのは、パッケージのフックがこれらのツールを呼んでいるからです。

起動シーケンスの中での位置づけ

initramfsを含めた起動の流れを整理すると、次のようになります。

1. ブートローダ(GRUB)が、カーネルとinitramfsをメモリにロードして、カーネルに制御を渡す

2. カーネルが自身を初期化し、initramfsをメモリ上のルートとして展開する

3. カーネルが仮ルートの /init を実行する(ここからユーザー空間)

4. /init がデバイスを検出し、必要なモジュール(ディスクドライバ、ファイルシステム)をロードする

5. 本物のルートファイルシステムを(読み取り専用などで)マウントする

6. switch_root で仮ルートから本物のルートへ切り替え、本物の /sbin/init(多くの環境ではsystemd)を実行する

この4〜5段目で失敗すると、Ubuntuでは次のような画面に落ちます。


BusyBox v1.36.1 (Ubuntu ...) built-in shell (ash)
Enter 'help' for a list of built-in commands.

(initramfs)

これは「initramfsまでは正常で、本物のルートのマウントに失敗した」ことを示すプロンプトです。ディスクのUUID変更やfstabのミスが典型的な原因で、この状態からでも ls /dev/ や blkid で調査ができます。「(initramfs) と出たら、仮ルートには到達している」と読めることが、起動トラブル切り分けの重要な手がかりになります。

仮ルートの役目が終わると、systemdの出番

initramfsは、本物のルートをマウントするための仮ルートであり、カーネルとペアで /boot に置かれ、init スクリプトとドライバの抜粋を含むcpioアーカイブである——これが今回のポイントです。LFSでは必要なドライバをカーネルに組み込むためinitramfsを省略できますが、ディストリビューションが汎用性のためにこれを必須としている理由も、これで説明できるようになりました。

そして起動シーケンスの最後、switch_root の先で実行されるのが、本物のルート上の最初のプロセス、PID 1です。現代の主要ディストリビューションでそのPID 1を務めるのが、次回のテーマsystemdです。

図解テキスト


bootloader -> kernel + initramfs をメモリにロード
  -> kernel が initramfs を仮ルートとして展開
  -> /init 実行(ユーザー空間の開始)
  -> 必要ドライバ読み込み(ext4, ディスクコントローラ, ...)
  -> real root mount
  -> switch_root で本物のルートへ切り替え
  -> /sbin/init(= systemd)起動

この回で実行するコマンド

上から順番に実行すれば、この回の内容を体験できます。

まずはそのままコピペして実行し、次に1行ずつ意味を確認してください。


# カーネルとinitramfsがペアで存在することを確認
ls -lh /boot/

# initramfsの中身を一覧(Debian/Ubuntu系)
lsinitramfs /boot/initrd.img-* 2>/dev/null | head -n 40 || true

# 中に入っているカーネルモジュール(.ko)だけを抽出して見る
lsinitramfs /boot/initrd.img-* 2>/dev/null | grep '\.ko' | head -n 20 || true

ハンズオン手順

1. ターミナルを開いて、上のコマンドを1行ずつ実行する

2. /boot のカーネルとinitramfsのファイルサイズを比べてメモする

3. lsinitramfs の出力から init と .ko ファイルを探し、それぞれの役割を言ってみる

4. エラーが出たら、そのエラーメッセージをそのまま残す

チェックポイント

  • コマンドが最後まで実行できた
  • 「ルートを読むドライバがルートの中にある」問題と、initramfsによる解決を説明できる
  • (initramfs) プロンプトが出たとき、起動のどこまで進んでいたか言える
  • 次の回に進む前に、分からない単語を1つ調べた

今回理解できたこと

  • initramfsは起動初期の橋渡し(メモリ上に展開される仮のルートファイルシステム)
  • root mount前の作業場所として必須(ドライバロード・暗号化解除・LVM認識など)
  • update-initramfs / dracut が生成し、カーネル更新のたびに作り直される