第2章 カーネルのビルドとカスタマイズ

LPIC-2 201.1〜201.2 相当

本章は、LPIC-2 201試験の201.1(カーネルの構成要素、weight 2)と201.2(カーネルのコンパイル、weight 3)、合計weight 5に対応します。カーネルのバージョン体系からソースの入手、設定(.config)、ビルド、インストール、initramfsの生成、ブートローダーへの登録、DKMSによるサードパーティモジュール管理までを、実際のコマンドと出力例を交えて扱います。検証環境として、Debian/Ubuntu系(apt・dpkg系)とRHEL/CentOS系(dnf・rpm系)の両方を想定し、手順やパスが異なる箇所はその都度両方を併記します(「RHEL/CentOS系」は、RHELとコマンド体系を共有するディストリビューション全般を指す表記です。CentOS Linuxは2024年にサポートが終了しており、現在はRocky LinuxやAlmaLinuxが実質的な後継として広く使われています)。

カーネルのバージョン体系

Linuxカーネルの公式サイトであるkernel.orgでは、複数系列のバージョンが並行して公開されています。

系列特徴
Mainline最新の開発版。新機能が最初に取り込まれるが、安定性は保証されない
StableMainlineから分岐し、重大なバグ修正のみを取り込みながら短期間保守される版
Longterm(LTS)数年単位で長期に保守される安定版。多くのディストリビューションが採用
EOL(End of Life)保守期間が終了し、以後セキュリティ修正も提供されなくなったバージョン

kernel.orgのリリース一覧では、バージョン番号の横に[mainline]・[stable]・[longterm]・[EOL]のようなタグが表示され、そのバージョンが現在どの扱いかが一目で分かります。バージョン番号は「メジャー.マイナー.パッチ」の形式(例:6.6.32)で表され、パッチ番号が上がるほど、そのマイナーバージョンに対する保守修正が積み重ねられていることを意味します。

実際に運用しているディストリビューション(Ubuntu、RHELなど)が搭載するカーネルは、kernel.orgのバージョンそのままではなく、ディストリビューション独自のバックポート修正・追加パッチを当てた「ディストリビューションカーネル」であることがほとんどです。そのためuname -rで表示されるバージョン文字列には、5.15.0-1057-awsのようにディストリビューション独自のビルド番号やターゲット環境名が付加されているのが一般的です。カスタムカーネルをビルドする際は、特別な理由がない限りLongterm系列をベースにするのが実務では無難な選択です。

実務での考え方:なぜ自前でカーネルをビルドするのか: カーネルを自前でビルドする主な理由には、(1)ディストリビューション標準カーネルが対応していない新しいハードウェアへの対応、(2)不要な機能を削除してイメージサイズや攻撃対象領域を削減する、(3)カーネル自体のデバッグ、(4)学習目的、が挙げられます。一方で実務では、自前ビルドは滅多に行われません。理由は、セキュリティパッチの追跡・再ビルド・再テストを自分たちで継続的に担う運用コストが非常に高く、ディストリビューションベンダーが提供する検証済みカーネルパッケージを使う方が、大多数の環境にとって合理的だからです。自前ビルドが選ばれるのは、組み込み機器や特殊なハードウェアを扱う限られた場面が中心です。

カーネルソースの入手と展開

カーネルソースはkernel.orgからtar.xz形式(xz圧縮)で配布されています。ダウンロード後、慣習的に/usr/src/以下に展開します。

$ cd /usr/src
$ sudo wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.32.tar.xz
--2026-08-21 09:00:12--  https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.32.tar.xz
Resolving cdn.kernel.org (cdn.kernel.org)... 145.40.68.75
Connecting to cdn.kernel.org (cdn.kernel.org)|145.40.68.75|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 138543210 (132M) [application/x-xz]
Saving to: 'linux-6.6.32.tar.xz'

linux-6.6.32.tar.xz       100%[=================================>] 132.14M  22.4MB/s    in 6.1s

2026-08-21 09:00:18 (21.7 MB/s) - 'linux-6.6.32.tar.xz' saved [138543210/138543210]
$ sudo tar xJf linux-6.6.32.tar.xz     # xz圧縮アーカイブを展開(xJfのJがxz圧縮を意味する)(正常時は無出力)
$ cd linux-6.6.32

この行のこの値に注目: wgetの出力にあるLength:行はダウンロードするファイルの総サイズ、進捗バーの末尾の22.4MB/sは実際の転送速度を示します。最終行にsavedと表示されれば、ダウンロードが完全に完了したことが分かります。途中で切断された場合はwget -cで続きから再開できます。

多くの環境では、展開したディレクトリに対して/usr/src/linuxというシンボリックリンクを張る慣習があります。複数バージョンのソースを並行して置きつつ、ビルドスクリプトなどからは常に/usr/src/linuxという固定パスで参照できるようにするためです。

$ sudo ln -sfn /usr/src/linux-6.6.32 /usr/src/linux   # 正常時は無出力
$ ls -ld /usr/src/linux
lrwxrwxrwx 1 root root 20 Aug 20 10:00 /usr/src/linux -> linux-6.6.32

この出力の->の右側が実際の展開先ディレクトリです。シンボリックリンクの向き先が意図したバージョンになっているかは、複数バージョンを扱う際に見落としやすいので確認しておきましょう。

ソースツリー内のDocumentation/ディレクトリには、各サブシステムの詳細な公式ドキュメントが同梱されています。Documentation/admin-guide/には管理者向けの一般的な手順、Documentation/process/には開発プロセスの説明、個々のサブシステム(例:Documentation/filesystems/やDocumentation/networking/)にはそのサブシステム固有の設定項目の説明があります。設定項目の意味がmake menuconfigのヘルプ文だけでは分からないとき、このディレクトリを一次情報として参照するのが実務での基本的な調べ方です。

カーネルイメージとブート関連ファイルの種類

用語意味
vmlinuxリンク直後の非圧縮カーネル本体(デバッグシンボルを含む生の実行ファイル)
zImage圧縮されたカーネルイメージの古い形式。ロード先アドレスに制限があった
bzImage"big zImage"の略。zImageのサイズ制限を解消した現在標準のカーネルイメージ形式
vmlinuzディストリビューションが/bootに配置する、圧縮済みブート可能カーネルイメージのファイル名慣習(実体はbzImage)
initrd / initramfsルートファイルシステムをマウントするために必要なドライバをまとめた、起動時にのみ使う一時的なイメージ
豆知識:zImageとbzImageの「640KBの壁」: x86の実モードには、伝統的に扱えるメモリ領域が640KBまでという制約があり、初期のzImage形式のカーネルはこの制約の影響でロード可能なイメージサイズに上限がありました。カーネルの機能が増えるにつれてイメージサイズも大きくなり、この上限に収まらなくなったため、より大きなイメージを異なる方式でロードできる"big zImage"(bzImage)形式が導入されました。現在のx86/x86_64環境では、実質的にすべてのディストリビューションがbzImage形式を使っています。

.configの作り方

カーネルの機能ごとのオン/オフは.configファイルに保存されます。ゼロから設定するのは現実的ではないため、既存の設定を出発点にするのが実務での基本です。

$ cp /boot/config-$(uname -r) .config   # 現在稼働中のカーネルの設定をコピーして出発点にする(最も実務的な方法)(正常時は無出力)
$ make olddefconfig    # 新バージョンで増えた設定項目を、すべて既定値で自動的に埋める(対話なし)
HOSTCC  scripts/basic/fixdep
...(省略、依存関係の再構築ログが続く)
scripts/kconfig/conf --olddefconfig Kconfig
#
# using defaults found in /boot/config-5.15.0-1057-aws
#
.config:1234:warning: symbol value 'm' invalid for CONFIG_NEW_FEATURE_X
#
# configuration written to .config
#

この行のこの値に注目: warning: symbol value ... invalidのような警告が出ることがありますが、これは新バージョンで依存関係が変わった設定項目が既定値へ自動調整されたことを示すもので、多くの場合は無視して問題ありません。最終行にconfiguration written to .configと表示されれば、.configファイルへの書き込みが完了しています。

makeターゲット用途
oldconfig既存の.configを引き継ぎ、新項目のみ1つずつ対話的に確認する
olddefconfig既存の.configを引き継ぎ、新項目の差分をすべて既定値で自動的に埋める(対話なし)
menuconfigテキストベースのメニュー形式で設定項目を選択(最も一般的なGUIレスの方法)
xconfigQtベースのグラフィカルな設定画面
gconfigGTKベースのグラフィカルな設定画面
nconfigmenuconfigの拡張版。検索機能など操作性が改善されたテキストUI
defconfigアーキテクチャごとに用意された既定の設定セットを採用する
localmodconfig現在ロードされているモジュール(lsmodの結果)だけを有効にし、それ以外を無効化する。ビルド時間を大幅に短縮できる

localmodconfigは、稼働中の環境に実際に必要なモジュールだけに絞り込めるため、検証目的でとにかく早くビルドを終えたい場合に重宝します。ただし、USBメモリで一時的にしか使わないドライバなど、実行時にしかロードされないモジュールまで無効化してしまう可能性があるため、本番向けのカーネルをこの方法だけで構成するのは避けるべきです。

makeターゲットの全体像

カーネルのビルドシステムには、設定・ビルド・後片付け・パッケージ化までの多数のmakeターゲットが用意されています。

ターゲット用途
allbzImage・modules・関連ファイルをまとめてビルドする既定のターゲット
config最も原始的な、1問1答形式のテキスト設定(実務ではほぼ使われない)
menuconfigテキストメニュー形式の設定画面を開く
oldconfig既存.configを引き継ぎ差分のみ対話確認
bzImage圧縮カーネルイメージ本体のみをビルド
modulesモジュールとして構成されている機能をビルド
modules_installビルド済みモジュールを/lib/modules/<バージョン>/以下にインストール
installカーネル本体を/boot以下にインストールする
headers_installカーネルヘッダーファイルを、外部モジュールのビルドに使える形で書き出す
cleanオブジェクトファイル等のビルド生成物を削除するが、.configは残す
mrproper.configを含め、ビルド生成物を完全にクリーンな初期状態へ戻す
distcleanmrproperの内容に加え、パッチのバックアップファイル・エディタのバックアップファイル等も削除する
rpm-pkgソース・バイナリ両方を含むRPMパッケージとしてビルド(RHEL系)
binrpm-pkgバイナリRPMパッケージのみをビルド(設定確認なしで高速、RHEL系)
deb-pkgソース・バイナリ両方を含むdebパッケージとしてビルド(Debian系)
bindeb-pkgバイナリdebパッケージのみをビルド(Debian系)
よくある間違い:clean・mrproper・distclenの混同: この3つのターゲットの違いはLPIC-2で頻出のポイントです。cleanは生成物を消しても.configは残しますが、mrproperは.configごと削除して完全に初期状態へ戻します。せっかく時間をかけて調整した設定を失いたくない場合は、mrproperやdistcleanを実行する前に、必ずcp .config .config.bakのようにバックアップを取ってください。「distcleanのつもりでcleanを打ったら設定が残っていて混乱した」逆に「cleanのつもりでmrproperを打って設定が消えた」という取り違えは、実際によくあるミスです。

カーネルのビルドから起動までの手順

ここまでの内容を踏まえ、ソースの取得から実際に新しいカーネルで起動するまでの一連の流れを、番号付きの手順として整理します。

ステップ1:ビルド環境の準備

$ sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev   # Debian/Ubuntu系
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
  bison flex libelf-dev libncurses-dev libssl-dev ...(省略)
0 upgraded, 12 newly installed, 0 to remove and 0 not upgraded.
Need to get 8,432 kB of archives.
...(省略)
Setting up libncurses-dev:amd64 (6.4-4) ...
$ sudo dnf groupinstall "Development Tools" && sudo dnf install ncurses-devel bison flex openssl-devel elfutils-libelf-devel   # RHEL/CentOS系
Dependencies resolved.
================================================================
 Package          Arch    Version         Repository      Size
================================================================
Installing group/module packages:
 gcc              x86_64  11.4.1-3.el9    appstream       31 M
...(省略)
Complete!

この行のこの値に注目: 0 upgraded, 12 newly installedのような行で、実際に何個のパッケージが新規インストールされたかが分かります。すでに開発環境が整っている場合は0 upgraded, 0 newly installedのように変化なしと表示され、余計な待ち時間なくすぐにコマンドが終了します。

ここで確認すべきこと: gcc --versionでコンパイラが利用可能なこと、make --versionでmakeが利用可能なことを確認します。libncurses-dev(Debian系)・ncurses-devel(RHEL系)が不足していると、後述のmake menuconfigが起動できずエラーになります。

ステップ2:ソースの取得と設定

前述の手順でソースを展開し、.configを用意します(cp /boot/config-$(uname -r) .config → make olddefconfig、またはmake menuconfigで個別に調整)。

ここで確認すべきこと: .configファイルが存在し、意図したベース設定(現行カーネルの設定)が反映されていることを、grep CONFIG_LOCALVERSION .configなどで抜き取り確認します。

ステップ3:ビルドの実行

$ make -j$(nproc)     # カーネル本体・組み込みモジュールをビルド。-jは並列ジョブ数、$(nproc)はCPUの論理コア数
  SYNC    include/config/auto.conf.cmd
  HOSTCC  scripts/kconfig/conf.o
  CC      init/main.o
  CC      kernel/fork.o
...(省略、数千行のコンパイルログが続く)
  LD      vmlinux.o
  MODPOST vmlinux.o
  LD      vmlinux
  SORTTAB vmlinux
  OBJCOPY arch/x86/boot/compressed/vmlinux.bin
  BUILD   arch/x86/boot/bzImage
Kernel: arch/x86/boot/bzImage is ready  (#1)

この行のこの値に注目: ビルド中はCC(コンパイル)・LD(リンク)・MODPOST(モジュールのポストプロセス)といった短い接頭辞と対象ファイル名だけが大量に流れます。最終行のKernel: ... is readyが表示されれば、arch/x86/boot/bzImageの生成まで成功したことを意味し、これが表示されずにエラーで止まった場合は、直前に表示されたerror:を含む行を遡って原因を特定します。

-j$(nproc)は、CPUのコア数に合わせてコンパイルを並列実行し、ビルド時間を大幅に短縮するオプションです。フルビルドの所要時間は、CPU性能・有効化した機能の量に左右されますが、一般的なデスクトップ相当のマシンで数十分から1時間程度が目安です。繰り返しビルドする場合はccacheを導入すると、変更していないソースファイルのコンパイル結果を再利用でき、2回目以降のビルドが大幅に高速化されます。

ここで確認すべきこと: ビルドがエラーなく完了し、最終的にarch/x86/boot/bzImage(x86_64環境の場合)が生成されていることを確認します。

ステップ4:モジュールとカーネル本体のインストール

$ sudo make modules_install   # ビルド済みモジュールを/lib/modules/<バージョン>/以下へ配置
  INSTALL drivers/net/ethernet/intel/e1000e/e1000e.ko
  INSTALL fs/ext4/ext4.ko
...(省略)
  DEPMOD  /lib/modules/6.6.32
$ sudo make install           # カーネル本体・System.map・configを/boot以下へ配置
sh ./arch/x86/boot/install.sh 6.6.32 arch/x86/boot/bzImage System.map "/boot"
run-parts: executing /etc/kernel/postinst.d/initramfs-tools 6.6.32 /boot/vmlinuz-6.6.32
update-initramfs: Generating /boot/initrd.img-6.6.32
run-parts: executing /etc/kernel/postinst.d/zz-update-grub 6.6.32 /boot/vmlinuz-6.6.32
Sourcing file `/etc/default/grub'
Found linux image: /boot/vmlinuz-6.6.32
Found initrd image: /boot/initrd.img-6.6.32
done

この行のこの値に注目: DEPMOD行は、インストールしたモジュール間の依存関係情報(modules.dep)を再生成したことを示します。make install実行時にDebian系では/etc/kernel/postinst.d/以下のフックが自動的に実行され、initramfsの生成やGRUB設定の更新まで一括で行われる点に注目してください(このため、後述のステップ5・6を手動で実行しなくても、この時点ですでに起動可能な状態になっている場合があります)。

make modules_installは、/lib/modules/6.6.32/のようなバージョン別ディレクトリ以下にモジュール(.koファイル)を配置し、内部的にdepmodを呼び出してモジュール間の依存関係情報(modules.dep)を再生成します。make installは、カーネルイメージ(vmlinuz-6.6.32)・シンボルテーブル(System.map-6.6.32)・使用した設定(config-6.6.32)を/boot以下へコピーします。

ここで確認すべきこと: ls /lib/modules/に新バージョンのディレクトリが増えていること、ls /boot/vmlinuz-*に新しいカーネルイメージが存在することを確認します。

ステップ5:initramfsの生成

新しいカーネルに対応する初期RAMディスク(initramfs)を生成します。これはルートファイルシステムをマウントするために必要なドライバ(LVM、RAID、暗号化ディスクなど)をまとめた一時的なイメージで、生成を忘れると起動に失敗します。

$ sudo mkinitramfs -o /boot/initrd.img-6.6.32 6.6.32   # Debian/Ubuntu系(正常時は無出力)
$ sudo dracut /boot/initramfs-6.6.32.img 6.6.32          # RHEL/CentOS系
dracut: Executing: /usr/bin/dracut /boot/initramfs-6.6.32.img 6.6.32
dracut: Using configuration file '/etc/dracut.conf'
dracut: *** Including module: bash ***
dracut: *** Including module: systemd ***
dracut: *** Including module: lvm ***
...(省略)
dracut: *** Creating image file '/boot/initramfs-6.6.32.img' ***
dracut: *** Creating initramfs image file '/boot/initramfs-6.6.32.img' done ***

mkinitrdはmkinitramfsの前身にあたる古いツール名で、現在の主要ディストリビューションではmkinitramfs(Debian系)またはdracut(RHEL系)が標準です。dracutは既定でかなり冗長なログを出力し、*** Including module: ... ***という行から、どのモジュール(機能単位)が組み込まれたかを確認できます。生成後は、中身に想定したドライバ(例:LVM関連モジュール)が含まれているかを確認できます。

$ lsinitramfs /boot/initrd.img-6.6.32 | grep dm-mod    # Debian系:initramfsの中身を一覧表示しdm-mod(LVM用)を検索
lib/modules/6.6.32/kernel/drivers/md/dm-mod.ko
$ lsinitrd /boot/initramfs-6.6.32.img | grep dm-mod     # RHEL系の同等コマンド
-rw-r--r--   1 root     root        90112 Aug 21 09:15 usr/lib/modules/6.6.32/kernel/drivers/md/dm-mod.ko.xz

この行のこの値に注目: どちらの出力も、dm-mod(Device Mapper、LVM/暗号化ディスクの基盤モジュール)を含むファイルパスが1行表示されれば、initramfsに必要なドライバが正しく組み込まれていることが確認できます。何も表示されない(grepが空振りする)場合は、そのシステムがLVM等を使っていないか、あるいは組み込み漏れのいずれかなので、実際にLVMを使っている環境であれば後者を疑う必要があります。

ここで確認すべきこと: lsinitramfs/lsinitrdの出力に、ルートファイルシステムの種類(LVM・RAID・暗号化等)に応じて必要なモジュールが含まれているかを確認します。含まれていない場合、起動時にルートファイルシステムをマウントできず起動に失敗します。

ステップ6:ブートローダーへの登録

$ sudo update-grub                 # Debian/Ubuntu系
Sourcing file `/etc/default/grub'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.6.32
Found initrd image: /boot/initrd.img-6.6.32
Found linux image: /boot/vmlinuz-5.15.0-1057-aws
Found initrd image: /boot/initrd.img-5.15.0-1057-aws
done
$ sudo grub2-mkconfig -o /boot/grub2/grub.cfg   # RHEL/CentOS系(BIOS環境の一例)
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.6.32
Found initrd image: /boot/initramfs-6.6.32.img
Found linux image: /boot/vmlinuz-5.14.0-427.el9.x86_64
Found initrd image: /boot/initramfs-5.14.0-427.el9.x86_64.img
done

この行のこの値に注目: Found linux image: ...の行が、検出された各カーネルバージョンごとに1組ずつ表示されます。新しくビルドした6.6.32の行が含まれていれば、GRUBメニューへの登録に成功したことが分かります。旧バージョン(この例では5.15.0-1057-awsや5.14.0-427.el9.x86_64)の行も残っていることは、後述する「切り戻し用に旧カーネルを残しておく」という運用上、むしろ正常な状態です。

update-grubは内部的にgrub-mkconfigを呼び出すDebian系のラッパースクリプトです。RHEL系ではgrub2-mkconfigを使い、出力先はBIOS環境かUEFI環境かによって/boot/grub2/grub.cfgまたは/boot/efi/EFI/<distro>/grub.cfgのように変わります。

$ grep menuentry /boot/grub/grub.cfg | grep 6.6.32
menuentry 'Debian GNU/Linux, with Linux 6.6.32' --class debian --class gnu-linux ...

ここで確認すべきこと: grub.cfgに新しいカーネルバージョンのmenuentry行が追加されていることを確認します。この行が存在しないと、ブート時に新しいカーネルを選択できません。

ステップ7:再起動と動作確認

$ sudo reboot
(再起動後)
$ uname -r
6.6.32

ここで確認すべきこと: uname -rの出力が、意図してビルドしたバージョン番号と一致していることを確認します。異なるバージョンが表示される場合、ブートローダーの既定エントリが新しいカーネルを指していない可能性があります。

パッケージ形式でのビルド

複数台のマシンに同じカスタムカーネルを配布したい場合は、make installで直接インストールするのではなく、パッケージ形式でビルドしてから配布する方法が実務的です。

$ make bindeb-pkg -j$(nproc)   # Debian/Ubuntu系:バイナリdebパッケージをビルド
  CC      init/main.o
...(省略、通常のビルドログに続けてパッケージング処理が実行される)
dpkg-deb: building package 'linux-image-6.6.32' in '../linux-image-6.6.32_6.6.32-1_amd64.deb'.
dpkg-deb: building package 'linux-headers-6.6.32' in '../linux-headers-6.6.32_6.6.32-1_amd64.deb'.
$ make binrpm-pkg -j$(nproc)   # RHEL/CentOS系:バイナリRPMパッケージをビルド
  CC      init/main.o
...(省略)
Wrote: /root/rpmbuild/RPMS/x86_64/kernel-6.6.32-1.x86_64.rpm
Wrote: /root/rpmbuild/RPMS/x86_64/kernel-headers-6.6.32-1.x86_64.rpm

この行のこの値に注目: 最終行に表示される.deb・.rpmファイルの絶対パスが、実際にできあがったパッケージファイルの場所です。bindeb-pkgは生成先が1つ上のディレクトリ(../)、binrpm-pkgは~/rpmbuild/RPMS/配下と、配置場所がディストリビューション系統ごとに異なる点に注意します。

パッケージ形式でビルドしておく利点は、(1)dpkg/rpmによるインストール・アンインストール履歴の管理下に置ける、(2)複数台への配布・展開が容易、(3)パッケージマネージャの依存関係チェックの恩恵を受けられる、という点です。deb-pkg・rpm-pkgはソースパッケージも同時に生成するため配布物が大きくなり、バイナリだけで十分な場合はbindeb-pkg・binrpm-pkgを使う方が高速です。

DKMSによるサードパーティモジュール管理

特定のハードウェア用ドライバなど、カーネル本体には同梱されていない外部モジュールは、カーネルのバージョンごとに再ビルドする必要があります。カーネルを更新するたびに手作業で再ビルドするのは手間がかかるため、DKMS(Dynamic Kernel Module Support)という仕組みが使われます。DKMSにモジュールを登録しておくと、以降カーネルが更新されるたびに、新しいカーネル向けのモジュールが自動的に再ビルド・インストールされます。

/usr/src/mymodule-1.0/dkms.conf(DKMSモジュールの設定ファイル例)
PACKAGE_NAME="mymodule"              # モジュールのパッケージ名
PACKAGE_VERSION="1.0"                # バージョン。ディレクトリ名の -1.0 部分と一致させる
BUILT_MODULE_NAME[0]="mymodule"      # ビルド後の.koファイル名(拡張子なし)
DEST_MODULE_LOCATION[0]="/kernel/drivers/mymodule"   # /lib/modules/<version>/以下でのインストール先
MAKE[0]="make -C $kernel_source_dir M=$dkms_tree/$PACKAGE_NAME/$PACKAGE_VERSION/build"   # ビルドコマンド
CLEAN="make -C $kernel_source_dir M=$dkms_tree/$PACKAGE_NAME/$PACKAGE_VERSION/build clean"   # クリーンアップコマンド
AUTOINSTALL="yes"                    # 新しいカーネルインストール時に自動で再ビルドする
$ sudo dkms add -m mymodule -v 1.0
Creating symlink /var/lib/dkms/mymodule/1.0/source -> /usr/src/mymodule-1.0
$ sudo dkms build -m mymodule -v 1.0
Building module:
cleaning build area...
make -C ...(略)...
Building initial module for 6.6.32
Done.
$ sudo dkms install -m mymodule -v 1.0
mymodule.ko:
Running module version sanity check.
DKMS: install completed.
$ dkms status
mymodule/1.0, 6.6.32, x86_64: installed

dkms statusの出力は「モジュール名/バージョン, 対応カーネルバージョン, アーキテクチャ: 状態」の形式で表示されます。installedであればそのカーネル向けに正しくビルド・インストール済みであることを意味し、builtのままであればビルドは成功したがまだインストールされていない状態を示します。モジュールを削除する場合はdkms removeを使います。

$ sudo dkms remove -m mymodule -v 1.0 --all   # --allで登録済みの全カーネルバージョン分をまとめて削除

------------------------------
Deleting module mymodule/1.0 completely from the DKMS tree.
------------------------------
Done.

この行のこの値に注目: Deleting module ... completely from the DKMS tree.と表示されれば、登録されていたすべてのカーネルバージョン向けのビルド済みモジュールと登録情報が削除されたことを意味します。削除後にdkms statusを実行しても、そのモジュールの行は表示されなくなります。

サードパーティ製のグラフィックドライバやVPNクライアントのカーネルモジュールなど、頻繁にアップデートされる外部モジュールで広く利用されている仕組みです。カーネルモジュールのランタイム管理(lsmod・modprobeなど)については「カーネルモジュールとランタイム管理」の章で扱っています。

カーネルパッチの適用

ディストリビューションが提供する既定のカーネルに、独自の修正やベンダー固有の機能を追加したい場合、ソースコードの差分である「パッチ」を適用してからビルドします。パッチはdiffコマンドで生成された差分形式のテキストファイルで、patchコマンドで既存のソースツリーに適用します。

$ cd /usr/src/linux-6.6.32
$ patch -p1 --dry-run < /path/to/fix-something.patch   # 実際には適用せず、当たるかどうかだけ事前確認
checking file drivers/net/foo.c
$ patch -p1 < /path/to/fix-something.patch              # --dry-runで問題なければ実際に適用
patching file drivers/net/foo.c

-p1は、パッチファイルの中に記録されたパス(a/drivers/net/foo.cのような形式)から、先頭のディレクトリ階層を1つ取り除いてから現在のディレクトリを基準に適用することを意味します。--dry-runを先に実行し、checking file ...という出力だけが表示されエラーが出なければ、実際の適用に進んでも安全です。

パッチがうまく当たらなかった場合、対象ファイルと同じ場所に.rej(reject)拡張子のファイルが作成されます。

$ patch -p1 < /path/to/fix-something.patch
patching file drivers/net/foo.c
Hunk #2 FAILED at 120.
1 out of 2 hunks FAILED -- saving rejects to file drivers/net/foo.c.rej
$ cat drivers/net/foo.c.rej
--- drivers/net/foo.c
+++ drivers/net/foo.c
@@ -117,7 +117,7 @@ static int foo_probe(struct pci_dev *pdev)
-	if (ret < 0)
+	if (ret != 0)
 		goto err_out;

-	foo_reset_hw(priv);
+	foo_reset_hw(priv, FOO_RESET_FULL);

この行のこの値に注目: .rejファイルの中身は、通常のdiff形式そのままで、当てられなかった変更(-が削除行、+が追加行)が記録されています。この例では、パッチが期待していたfoo_reset_hw(priv)という呼び出し自体がベースのソースにはすでに存在せず(引数の数が異なる別バージョンの関数に変わっている)、自動適用できなかったことが読み取れます。この内容を見ながら、該当箇所を手動でソースに反映するかどうかを判断します。

Hunk #2 FAILEDは、パッチの2番目の変更ブロック(hunk)が対象ファイルに適用できなかったことを意味します。原因の多くは、ベースにしているソースのバージョンがパッチ作成時のバージョンとずれていることです。.rejファイルには、当たらなかった差分がそのまま記録されているため、内容を確認しながら該当箇所を手動でマージするか、対象ソースのバージョンを合わせてから再適用します。

破壊的操作への備え:失敗したときの復旧

警告:カーネルの入れ替えは起動不能のリスクを伴います: 新しいカーネルが正しく起動しない、必要なドライバが読み込めない、といった問題は、実機での検証を怠ると容易に発生します。作業前には必ず、(1)現在稼働中のカーネルとinitramfsが/bootに残っていること、(2)現在の.configを.config.bakとしてバックアップしておくこと、(3)可能であれば仮想マシンやテスト環境で先に動作確認すること、を徹底してください。

新しいカーネルで起動に問題が起きた場合の基本的な復旧手順は次の通りです。

  1. GRUBメニューで旧カーネルを選択する: 多くのディストリビューションでは、新しいカーネルをインストールしても古いカーネルのブートエントリを自動的には削除しません。起動時にGRUBメニューが表示されたら、正常に動作していた旧バージョンのカーネルを選択して起動します。
  2. Advanced optionsサブメニューを開く: Ubuntu系などでは、既定では最新カーネルの項目しかトップメニューに表示されず、旧カーネルは「Advanced options for ...」というサブメニューの中にまとめられています。トップメニューに旧カーネルが見当たらない場合は、このサブメニューを確認します。
  3. 旧カーネルで起動できたら、原因の切り分けを行う: initramfsの生成漏れ、ドライバの設定漏れなど、本章で扱った手順のどこが抜けていたかを見直します。切り分けが難しい場合は、「システム復旧と代替ブートローダー」の章で扱うレスキューモード・chroot復旧の手順も参照してください。
旧カーネルを消してはいけない理由: ディスク容量を節約する目的で、動作確認前に旧カーネルのパッケージ・ブートエントリを削除してしまうと、切り戻し手段そのものを失います。新しいカーネルでの運用が十分な期間安定していることを確認できるまでは、少なくとも1つ前のバージョンのカーネルを削除しないのが安全な運用です。

エラーメッセージと原因の対応表

メッセージ・症状主な原因
*** No rule to make target 'menuconfig'、あるいは ncurses関連のエラーlibncurses-dev(Debian系)/ncurses-devel(RHEL系)が未インストール
Hunk #N FAILED at ...パッチ作成時とソースのバージョンがずれている。.rejファイルを確認し手動マージが必要
起動時に Kernel panic - not syncing: VFS: Unable to mount root fsinitramfsの生成漏れ、または生成したinitramfsに必要なドライバ(LVM/RAID等)が含まれていない
再起動してもuname -rが新バージョンを示さないupdate-grub/grub2-mkconfigを実行し忘れた、またはブートローダーの既定エントリが旧カーネルのまま
dkms statusで対象カーネルの行が表示されないdkms build/dkms installが該当カーネルバージョンに対して実行されていない

本章で扱ったビルド・インストールの流れは、「システム起動のカスタマイズ」の章で扱うsystemdによる起動プロセスの、さらに手前の段階に位置づけられます。カーネル自体がどう構成され、どう起動可能な形になるかを理解しておくと、起動トラブルの切り分けの視野が大きく広がります。

確認クイズ

Q1. 数年単位で長期に保守され、多くのディストリビューションが採用するカーネル系列はどれですか?

解説: Longterm(LTS)系列は数年単位で長期に保守される安定版で、多くのディストリビューションが基盤として採用しています。

Q2. 実務でカーネルを自前でビルドすることが滅多にない主な理由として適切なものはどれですか?

解説: 自前ビルドは検証済みのディストリビューションカーネルを使う場合に比べ、セキュリティ修正の追跡や再テストの継続的なコストが高く、実務では限られた場面でのみ選択されます。

Q3. カーネルソースの展開先として慣習的に使われるパスはどれですか?

解説: カーネルソースは慣習的に/usr/src/以下に展開し、/usr/src/linuxというシンボリックリンクを張って参照するのが一般的です。

Q4. zImage形式のサイズ制限を解消した、現在標準的に使われるカーネルイメージの形式はどれですか?

解説: bzImage(big zImage)は、古いzImage形式のロードアドレス制限(いわゆる640KBの壁)を解消した、現在標準のカーネルイメージ形式です。

Q5. 現在ロードされているモジュールだけを有効にし、ビルド時間を大幅に短縮できるmakeターゲットはどれですか?

解説: localmodconfigは、lsmodの結果をもとに現在ロード中のモジュールだけを有効化し、それ以外を無効化することでビルド時間を短縮します。

Q6. 次のmakeターゲットのうち、実行しても既存の.configファイルが削除されないものはどれですか?

解説: cleanはビルド生成物を削除しますが.configは残します。mrproperとdistcleanは.configごと初期状態へ戻すため、事前のバックアップが必要です。

Q7. 次のdkms statusの出力から読み取れる状態として適切なものはどれですか?
mymodule/1.0, 6.6.32, x86_64: installed

解説: dkms statusの末尾がinstalledであれば、そのモジュールが指定したカーネルバージョン向けに正しくビルド・インストールされていることを示します。

Q8. 次のpatchコマンド実行結果から読み取れる状態として適切なものはどれですか?
patching file drivers/net/foo.c
Hunk #2 FAILED at 120.
1 out of 2 hunks FAILED -- saving rejects to file drivers/net/foo.c.rej

解説: Hunk #2 FAILEDは2番目の変更ブロックが適用できなかったことを示し、当たらなかった差分は.rejファイルに保存されます。原因の多くはソースのバージョンのずれです。

Q9. 次のgrub.cfg確認コマンドの出力から読み取れることとして適切なものはどれですか?
grep menuentry /boot/grub/grub.cfg | grep 6.6.32
menuentry 'Debian GNU/Linux, with Linux 6.6.32' --class debian ...

解説: grub.cfgに該当バージョンのmenuentry行が存在することは、update-grub等によりブートローダーへの登録が完了していることを示します。

Q10. 新しいカーネルで起動に問題が起きた場合、最も基本的で優先すべき対処法はどれですか?

解説: 多くのディストリビューションでは旧カーネルのブートエントリが自動的には削除されないため、GRUBメニューから旧カーネルを選び直せば、以前の動作していた状態にすぐ戻せます。旧カーネルを削除するのは安定運用を確認してからにすべきです。