Ubuntu Weekly Recipe

第835回Ubuntuのカーネルヴァリアントの違いを見てみよう

Ubuntuにはいくつかのカーネルヴァリアントが用意されています。今回はこのカーネルヴァリアントがどういうものかを解説し、具体的なカーネルヴァリアントを取り上げてカーネルコンフィグ上の違いを確認する方法を紹介します。また、応用的な内容としてWSL2用のカーネルについてもカーネルコンフィグを見てみます。

カーネルヴァリアント

Ubuntuでは特定の環境やユースケースなどに合わせてチューニング・用意されたカーネルがいくつかあります。これを「カーネルヴァリアント(kernel variants⁠⁠」といいます。

本連載やUbuntu Weekly Topicsの読者の中には「カーネルフレーバー」という言葉を目にしているかもしれません。⁠カーネルヴァリアント」はこのカーネルフレーバーに加え、ベースとなるカーネルバージョンの違いも含む、より広範な概念として定義されています。

なぜカーネルヴァリアントが必要なのか

ISOイメージ等を使いお手元のPCにUbuntuをインストールする場合、デフォルトではバージョンにgenericと入っているカーネルがインストールされるはずです。これもカーネルヴァリアントの1つですがgenericとある通り、⁠一般的な環境向けのカーネルであり、これを使っておけばだいたいの環境で動作する」というイメージであり、一番オーソドックスなものです。こういうと聞こえはいいのですが、裏を返せば「一部の環境では使うかもしれないが、一部の環境では使わない設定」も有効になっているカーネルであるともいえます。

ここにカーネルヴァリアントの存在意義があります。例えば、パブリッククラウドやRaspberry Piといった環境ではハードウェア構成が一定の範囲に収まるため、使われないであろうカーネルの設定を無効にしたり、反対にハードウェア構成に合わせて設定をチューニングしたりすることで最適化できます。

また、特定のユースケース向け、具体的にはレイテンシーセンシティブなユースケースではリアルタイムカーネルlowlatencyカーネル)を利用できます。これもカーネルヴァリアントの1つです。

バージョン違いのカーネルヴァリアントにはHardware Enablement = HWEカーネルがあります。まず、UbuntuのLTSがリリースされる際に同梱されているカーネルはGeneral Availability = GAカーネルと呼ばれます。それと対比されるのがこのHWEカーネルで、簡単に言えば「そのLTSリリース後に登場した新しいハードウェアをそのLTSで使えるよう、後続のリリースで提供されているカーネルをそのLTSにも提供する」という仕組みです。Linuxにおいてデバイスドライバはカーネルの一部のため、⁠新しいカーネルであれば、新しいハードウェアにも対応している」といえるのです。[1]

カーネルコンフィグ

カーネルの実際の設定についてはコンフィグファイルを見ることで確認できます。システムにインストールされているカーネルのコンフィグファイルは/boot/config-6.8.0-45-generic(Ubuntu 24.04LTSの例)のように、バージョンごとに配置されます。確認すると、このコンフィグファイルを配置しているのはlinux-modules-6.8.0-45-genericというパッケージであることがわかります。

$ dpkg -S /boot/config-6.8.0-45-generic
linux-modules-6.8.0-45-generic: /boot/config-6.8.0-45-generic

このファイルを開くと(設定項目)=(設定値)といった形式でカーネルのコンフィグが記述されています。デバイスドライバーの状況もここから読み取ることができ、設定値がyのドライバーはカーネル本体に組み込まれており、そのカーネルを使ってシステムを起動すると問答無用でドライバーも読み込まれます。mとなっているものは必要に応じて脱着できるようにカーネルモジュールとして提供されます。nもしくは設定がないものについてはそのどちらでもなく、カーネルのコンフィグとしてはドライバーを用意しない状態となります。

設定値がy, mのものについて、それぞれ実際に確認してみましょう。

設定値がyとなっているドライバーの例
$ grep CONFIG_EXT4_FS= /boot/config-6.8.0-45-generic
CONFIG_EXT4_FS=y

$ modinfo ext4  # filenameに(builtin)とある通り、カーネルに組み込まれている
name:           ext4
filename:       (builtin)
softdep:        pre: crc32c
license:        GPL
file:           fs/ext4/ext4
description:    Fourth Extended Filesystem
author:         Remy Card, Stephen Tweedie, Andrew Morton, Andreas Dilger, Theodore Ts'o and others
alias:          fs-ext4
alias:          ext3
alias:          fs-ext3
alias:          ext2
alias:          fs-ext2

$ lsmod | grep ext4  # カーネルと一体になっているため、カーネルモジュールとしては出てこない
設定値がmとなっているドライバーの例
$ grep CONFIG_E1000E= /boot/config-6.8.0-45-generic
CONFIG_E1000E=m

$ modinfo e1000e    # filenameのパスにカーネルモジュールが存在する
filename:       /lib/modules/6.8.0-45-generic/kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko.zst
license:        GPL v2
description:    Intel(R) PRO/1000 Network Driver
author:         Intel Corporation, <linux.nics@intel.com>
srcversion:     F0A9F9FEAE79DF7F1E24FF7
[後略]

$ lsmod | grep e1000e    # ドライバーはロードされていない
$ sudo modprobe e1000e  # ドライバーをロードする
$ lsmod | grep e1000e    # ドライバーがロードされた
e1000e                356352  0
$ sudo modprobe -r e1000e   # ドライバーをアンロード
$ lsmod | grep e1000e    # 再びe1000eドライバーはロードされていない状態となった

カーネルコンフィグを見比べてみる

それではカーネルヴァリアント間でカーネルコンフィグを見比べてみます。今回はgenericカーネルとlowlatencyカーネルを見比べます。具体的には、前述の通り、カーネルコンフィグは/boot/config-から始まるファイルに記載されているため、これを入手しdiffを取ってしまうことにします。なお、ここではUbuntu24.04.1 LTSを使用し、バージョン6.8.0-45-genericのx86_64アーキテクチャ向けのカーネルはシステムにインストール済みであることを前提とします。お手元の環境によって適宜読み替えてください。

/boot/config-から始まるファイルはlinux-modulesパッケージで提供されているため、lowlatencyカーネルに対応するlinux-modulesパッケージを入手します。

$ apt download linux-modules-6.8.0-45-lowlatency

ダウンロードが完了するとlinux-modules-6.8.0-45-lowlatency_6.8.0-45.45.1_amd64.debというパッケージファイルがカレントディレクトリに置かれます。これをlinux-modules-6.8.0-45-lowlatencyというディレクトリに展開します。

$ dpkg-deb -x linux-modules-6.8.0-45-lowlatency_6.8.0-45.45.1_amd64.deb linux-modules-6.8.0-45-lowlatency

これにより./linux-modules-6.8.0-45-lowlatency/boot/config-6.8.0-45-lowlatencyにカーネルコンフィグファイルが展開・配置されます。config-6.8.0-45-lowlatencyを入手できましたので、/boot/config-6.8.0-45-genericと内容を比較します。

$ diff -u /boot/config-6.8.0-45-generic ./linux-modules-6.8.0-45-lowlatency/boot/config-6.8.0-45-lowlatency
--- /boot/config-6.8.0-45-generic       2024-08-30 17:32:37.000000000 +0900
+++ ./linux-modules-6.8.0-45-lowlatency/boot/config-6.8.0-45-lowlatency 2024-09-02 22:14:44.000000000 +0900
@@ -50,7 +50,7 @@
 CONFIG_KERNEL_ZSTD=y
 CONFIG_DEFAULT_INIT=""
 CONFIG_DEFAULT_HOSTNAME="(none)"
-CONFIG_VERSION_SIGNATURE="Ubuntu 6.8.0-45.45-generic 6.8.12"
+CONFIG_VERSION_SIGNATURE="Ubuntu 6.8.0-45.45.1-lowlatency 6.8.12"
 CONFIG_SYSVIPC=y
 CONFIG_SYSVIPC_SYSCTL=y
 CONFIG_SYSVIPC_COMPAT=y
@@ -131,8 +131,8 @@

 CONFIG_PREEMPT_BUILD=y
 # CONFIG_PREEMPT_NONE is not set
-CONFIG_PREEMPT_VOLUNTARY=y
-# CONFIG_PREEMPT is not set
+# CONFIG_PREEMPT_VOLUNTARY is not set
+CONFIG_PREEMPT=y
 CONFIG_PREEMPT_COUNT=y
 CONFIG_PREEMPTION=y
 CONFIG_PREEMPT_DYNAMIC=y
@@ -170,7 +170,7 @@
 CONFIG_RCU_STALL_COMMON=y
 CONFIG_RCU_NEED_SEGCBLIST=y
 CONFIG_RCU_NOCB_CPU=y
-# CONFIG_RCU_NOCB_CPU_DEFAULT_ALL is not set
+CONFIG_RCU_NOCB_CPU_DEFAULT_ALL=y
 CONFIG_RCU_LAZY=y
 CONFIG_RCU_LAZY_DEFAULT_OFF=y
 # end of RCU Subsystem

ここから、genericカーネルに対してlowlatencyカーネルには次の差分があることがわかります。

  • CONFIG_PREEMPT_VOLUNTARYがオフis not setとなっている代わりにCONFIG_PREEMPTがオンyになっている
  • CONFIG_RCU_NOCB_CPU_DEFAULT_ALLがオフis not setからオンyになっている

これらはどういうものなのでしょうか。調べる方法の1つにLKDDbのようなツールを使う方法があります。ここから見つかればこれを使うのが簡単です。

検索をかけてみると次のように情報が見つかりました。

本稿ではこれらの差分による詳細な動作の違いには踏み込みませんが、CONFIG_PREEMPT=yについては、ブログ記事があります。この記事にある通りlowlatencyヴァリアントカーネルでは低レイテンシーを達成するために、プリエンプションに関してよりシビアな設定となっていることがわかりました。逆に言えば、これら3つ以外はgenericカーネルと設定値に違いはないこともわかります。[2]

このようにgenericカーネルと他のカーネルヴァリアントとを同じように比較してみると、ヴァリアントごとに具体的にどういうチューニングが入っているのかを確認できます。

WSL2のカーネルについて調べる

カーネルコンフィグの読み方・調べ方がわかったところで、WSL2用のカーネルについても確認してみます。

前提として、WSL2で提供されるカーネルはMicrosoft がビルド・配布しているものです。また、WSL2用のカーネルは稼働させるディストリビューションに関係なく使用されるため、⁠WSL2 上で動くUbuntu用のカーネル」でもありません。

つまり、WSL2用のカーネルは「Ubuntuのパッケージとして提供されているものではないし、なんならUbuntu専用でもない」ため、定義上はUbuntuのカーネルヴァリアントではありません。ただ、本稿では「WSL2で稼働するUbuntu環境で使われていて、WSL2用にチューニングされているカーネルである」という観点から、⁠カーネルヴァリアントみたいなもの」として取り上げます。

「では、同じように/bootの下にあるコンフィグファイルを確認して……」といきたいところですが、WSL2上の(少なくとも)Ubuntu環境では/bootの中身は空っぽです[3]。Ubuntuのレポジトリから提供されているカーネルでもないのでパッケージから抽出しようにもパッケージがありません。そのため、別のやり方でカーネルコンフィグを確認する必要があります。

WSL2用のカーネルのソースコードはGibHub上のWSL2-Linux-Kernelレポジトリで管理されています。また、このレポジトリに含まれるREADME.mdを読むとmake KCONFIG_CONFIG=Microsoft/config-wslを実行してカーネルをビルドせよ」との記載があるため、Microsoft/config-wslのコンフィグ内容でビルドされているはずであるという推測が成立します。

続いて、PowerShellやコマンドプロンプトからwsl --versionコマンドで確認すると、本稿執筆時点で最新のWSLバージョンは2.3.24.0で、カーネルバージョンは5.15.153.1-2でした。このカーネルバージョンを手がかりにレポジトリを確認すると"linux-msft-wsl-5.15.153.1"というタグがあることに気づきます。Microsoft/config-wsl../arch/x86/configs/config-wslへのシンボリックリンクになっています。ここから、今回確認してみたいWSL2カーネルバージョン5.15.153.1-2のカーネルコンフィグはこのconfig-wslファイルではないかと推察されます。

WSL2用のカーネルは前述のとおり、本来の意味でのカーネルヴァリアントではないことから、genericカーネルなどと比較しても有意義ではないためdiffを取ってみることはしません。ただ、非常に大雑把な指標ですが、有効となっているymと指定されている)設定の数をgenericカーネルと比較してみると、WSL2用のカーネルは有効な設定がかなり絞り込まれていることがうかがえます。

$ wget https://raw.githubusercontent.com/microsoft/WSL2-Linux-Kernel/refs/tags/linux-msft-wsl-5.15.153.1/arch/x86/configs/config-wsl
$ grep -e =[ym] /boot/config-6.8.0-45-generic |wc -l
9351
$ grep -e =[ym] config-wsl |wc -l
1416

その一方で、WSL2はHyper-V のアーキテクチャを利用しているため、Hyper-VのゲストOSとして必要なモジュール(準仮想化ドライバー)類はしっかりカーネル本体に組み込まれるようになっていることがわかります。

$ grep HYPERV_ /boot/config-6.8.0-45-generic    # genericカーネルでのHyper-V関連のコンフィグ
CONFIG_HYPERV_VSOCKETS=m
CONFIG_PCI_HYPERV_INTERFACE=m
CONFIG_HYPERV_STORAGE=m
CONFIG_HYPERV_NET=m
CONFIG_HYPERV_KEYBOARD=m
CONFIG_HID_HYPERV_MOUSE=m
# CONFIG_HYPERV_VTL_MODE is not set
CONFIG_HYPERV_TIMER=y
CONFIG_HYPERV_UTILS=m
CONFIG_HYPERV_BALLOON=m
CONFIG_HYPERV_IOMMU=y
# CONFIG_HYPERV_TESTING is not set

$ grep HYPERV_ config-wsl   #WSL2用カーネルでのHyper-V関連のコンフィグ
CONFIG_HYPERV_VSOCKETS=y
CONFIG_PCI_HYPERV_INTERFACE=y
CONFIG_HYPERV_STORAGE=y
CONFIG_HYPERV_NET=y
CONFIG_HYPERV_KEYBOARD=y
# CONFIG_HID_HYPERV_MOUSE is not set
CONFIG_HYPERV_TIMER=y
CONFIG_HYPERV_UTILS=y
CONFIG_HYPERV_BALLOON=y
# CONFIG_HYPERV_IOMMU is not set
# CONFIG_HYPERV_TESTING is not set

なお、GitHub上のリリース情報を確認すると、WSLバージョン2.3.11で一度カーネルバージョンが6.6.36.3になっていたようです。しかし、何らかの問題があり、WSLバージョン2.3.17でバージョン5.15.153.1-2にrevert(差し戻し)されたことがうかがえます。とはいえ、将来的に問題が解消されることになれば、再び6.6.x系のカーネルにアップグレードされることになるのではないかと見ています。

WSLバージョン2.3.11の記述を見ると、カーネルが6.6.36.3に上げたのに伴い"Hundreds of new kernel modules added."という記載もあるので、今後、カーネルバージョンが上がるタイミングでWSL2のカーネル同士のコンフィグを比較してみても面白いかもしれません。

おすすめ記事

記事・ニュース一覧