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

Linux kernelをビルドする

連載ナビ

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

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

—

カーネルビルドとは「設定 → コンパイル → 配置」の3工程

Linuxカーネルのビルドと聞くと特殊な作業に思えますが、結論から言うと、やることは次の3工程だけです。

1. 設定: make menuconfig などで「どの機能を組み込むか」を選び、.config ファイルを作る

2. コンパイル: make でソースコードから vmlinuz(カーネル本体)とカーネルモジュール群を生成する

3. 配置: できあがったカーネルを /boot に置き、モジュールを /lib/modules/ にインストールし、ブートローダ(GRUB)に登録する

普段Ubuntuで見かける /boot/vmlinuz-6.8.0-45-generic のようなファイルは、Ubuntuの開発チームがこの3工程を実行した成果物です。LFSでは、これを自分の手で行います。カーネルビルドは「OSの核を自分で決めて作る」作業であり、この連載の大きな山場です。

ディストリビューションのカーネルは「全部入り」だから

なぜ自分でビルドする価値があるのか。理由は、普段使っているカーネルが「どんなハードウェアでも動く全部入り構成」であり、その構成の中身を選ぶ機会が一度もないからです。

Ubuntuのカーネルは、無数のマシンで動く必要があるため、数千個のデバイスドライバやファイルシステムをモジュールとして同梱しています。一方、カーネルの各機能は設定時に次の3択から選べます。

  • y — カーネル本体に組み込む(起動直後から使える。その分カーネルが大きくなる)
  • m — モジュールとして別ファイルにする(必要になったときにロードされる)
  • n — 無効にする(そもそもコンパイルしない)

この選択が起動の成否に直結します。たとえば、ルートファイルシステムに使っているext4や、ディスクコントローラのドライバを m(モジュール)にすると、「モジュールはディスクの中にあるのに、そのディスクを読むためにモジュールが必要」という循環に陥り、カーネルは起動に失敗します。自分で選んでみて初めて、「Ubuntuはこの問題をどう回避しているのか」(答えは次回のinitramfsです)という問いが実感を持って理解できるようになります。

ビルドの全工程を追う

実際の流れを、コマンドと生成物で追ってみます。ソースは kernel.org から入手し、展開したディレクトリで作業します。


# 1. 設定画面を開く(.config が生成される)
make menuconfig

# 2. CPUコア数を使って並列コンパイル(マシン性能により数十分〜数時間)
make -j$(nproc)

# 3. モジュールを /lib/modules/<バージョン>/ にインストール
sudo make modules_install

# 4. カーネル本体を /boot に配置
#    x86_64 の場合(M1 Mac上のARM64 VMなら arch/arm64/boot/Image)
sudo cp arch/x86_64/boot/bzImage /boot/vmlinuz-lfs
sudo cp System.map /boot/System.map-lfs
sudo cp .config /boot/config-lfs

make menuconfig は、ncursesライブラリを使ったテキストベースの設定画面です(未導入なら ncurses の開発パッケージが必要です)。設定結果はソースディレクトリ直下の .config という1つのテキストファイルに、CONFIG_EXT4_FS=y のような行として保存されます。ゼロから選ぶのは大変なので、実務では動作中のシステムの設定(多くのディストリビューションでは /boot/config-$(uname -r) に置かれています)をコピーして出発点にするのが定石です。

make が完了すると、x86_64なら arch/x86_64/boot/bzImage、ARM64なら arch/arm64/boot/Image という圧縮済みカーネルイメージができます。これを /boot にコピーしてGRUBのメニューに登録すれば、次回起動時に自分のカーネルが選べるようになります。

設定を1つ間違えるとどうなるか

機能選択の重みを体感できる典型的な失敗が、ルートファイルシステムのドライバを組み込み忘れた場合です。起動すると、カーネルは途中まで正常に進んだあと、こんなメッセージで停止します。


VFS: Cannot open root device "sda2" or unknown-block(8,2): error -6
Please append a correct "root=" boot option; here are the available partitions:
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(8,2)

これは「カーネル自体は起動したが、ルートファイルシステムをマウントできなかった」という状態です。原因の多くは次のどれかです。

  • ルートに使っているファイルシステム(ext4など)を y にしていない
  • ディスクコントローラのドライバ(仮想環境なら virtio_blk など)を y にしていない
  • GRUBに渡す root= パラメータの指定ミス

逆に言えば、このpanicメッセージを読んで「ドライバか、ファイルシステムか、root= 指定か」と切り分けられるようになることが、カーネルビルドを経験する最大の収穫です。普段のUbuntuでこのエラーに出会うことはまずありませんが、クラウドでカスタムイメージを作るときや、コンテナホストのカーネルを調整するときに、この理解がそのまま生きてきます。

カーネルは「与えられるもの」から「選んで作るもの」へ

カーネルビルドは、設定(.config)・コンパイル(make)・配置(/boot とGRUB)の3工程であり、機能ごとの y/m/n の選択が起動の成否に直結する——これが今回のポイントです。

そして「ext4ドライバをモジュールにしたら起動できない」という循環問題には、実はディストリビューションが使っている標準的な解決策があります。それが次回のテーマ、initramfsです。Ubuntuの /boot に必ず initrd.img が置いてある理由が、次回はっきり分かります。

図解テキスト


kernel source
  -> make menuconfig   (.config を作る。各機能を y/m/n で選択)
  -> make              (コンパイル)
  -> bzImage/Image     (カーネル本体 → /boot へ)
     + modules         (→ /lib/modules/<version>/ へ)
  -> GRUB に登録して再起動

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

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

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

(menuconfig 以降はカーネルソースを展開したディレクトリで実行します)


# いま動いているカーネルのバージョンと設定ファイルを確認
uname -r
ls /boot/

# カーネルソースのディレクトリで: 設定画面を開く
make menuconfig

# 並列ビルド(nproc が使えない環境向けのフォールバック付き)
make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu)

ハンズオン手順

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

2. make menuconfig で「File systems」の項目を開き、ext4 が y/m のどちらかをメモする

3. 生成された .config を grep EXT4 .config で確認し、画面での選択と一致するか見る

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

チェックポイント

  • コマンドが最後まで実行できた
  • y / m / n の違いを、起動への影響とあわせて説明できる
  • 「Unable to mount root fs」の原因の候補を2つ挙げられる
  • 次の回に進む前に、分からない単語を1つ調べた

今回理解できたこと

  • kernelは自分で構成できる(設定は .config に集約される)
  • 機能選択が起動結果に直結する(ルートFSのドライバは特に重要)
  • ビルドの成果物は /boot のカーネル本体と /lib/modules/ のモジュール群