いまさら聞けない カーネルモジュール
FreeBSD Handbook には、カーネルモジュールに関する記載がわずか数行しかありません。現状ではカーネルモジュールについて学ぶには、man の kld(4) やkldload(8) などを読むしかありません。
そこで今回、カーネルモジュールについて情報をまとめて記事にしてみました。
1 カーネルモジュールとは
FreeBSD のカーネルは起動時にメモリに読み込まれ、ハードウェアの制御やシステムの基本機能を提供します。しかしすべてのドライバや機能をカーネル本体に組み込むと、使わない機能までメモリに常駐することになります。
この問題を解決するのがカーネルモジュール(KLD: Kernel Loadable Module)です。`.ko` という拡張子を持つファイルで、必要なときに動的にカーネルへ読み込むことができます。読み込まれたモジュールはカーネル空間で直接動作するため、カーネル本体に組み込んだ場合と性能上の差はほぼありません。
ルートファイルシステムのマウントに必要なストレージドライバ(ahci、nvme など)を除き、ほとんどのドライバはカーネルモジュールとして動的に追加される形になっています。
2 base system と ports のモジュール
FreeBSD には「base system」と「ports」という二層構造があります。この区別はモジュールの置き場所に直接対応しています。
/boot/kernel/ には base system のモジュールが置かれます。FreeBSD 本体と同時にリリースされ、freebsd-update でカーネル本体と一緒に更新されます。
/boot/modules/ には ports 由来のモジュールが置かれます。pkg install や ports のビルドによってインストールされます。GPU ドライバの drm-kmod など、base system に含まれない主にサードパーティのドライバがこちらに属します。他には virtualbox-ose-kmod などがあります。
この二層構造で重要なのが ABI(Application Binary Interface)との関係です。カーネルモジュールはビルドされた時点のカーネルバージョンに対応したバイナリになっています。/boot/kernel/ の base system モジュールは freebsd-update がカーネル本体と同時に更新するため ABI のミスマッチは起きません。
問題になるのは /boot/modules/ の ports 由来モジュールです。pkg install でインストールしたバイナリパッケージは、pkg のリポジトリがカーネルの新バージョンに対応したビルドを提供していない場合、古いカーネル向けのバイナリがインストールされることがあります。この状態でモジュールをロードしようとするとカーネルパニックや version mismatch エラーが発生します。
pkg のリポジトリはビルドのタイミングとカーネルのリリースタイミングが一致しないため、新しい点リリースの直後はリポジトリの更新が間に合わない場合があります。確実な対処は ports で make install することです。ports からビルドすれば現在動いているカーネルのソースに対してコンパイルされるため、ABI のミスマッチは基本的に解消されます。ただし ports からビルドするには /usr/src にカーネルソースが必要です。
カーネル更新の前には bectl でBE:Boot Environment を作成しておくことを勧めます。ABI ミスマッチでパニックが起きた場合でも、以前のBEから起動して復旧できます。
3 ややこしい _bin.ko
/boot/modules/ の中身を見てみると、大量の *_bin.ko というファイルが入っていると思います。これは純粋なカーネルモジュールではなく、firmware を kmod 形式で包んだものです。
なぜこのような形式になっているかというと、起動時のメカニズムに合わせるためです。起動時のルートファイルシステムがマウントされる以前には、通常のファイルシステム上にあるファイルをカーネルが読むことはできません。しかしストレージドライバ(SAS/SATA や NVMe)や、場合によってはネットワークブート(PXE)のための NIC ドライバは、ルートファイルシステムがマウントされる前の段階で firmware を必要とします。
そこでカーネルモジュールの仕組みを使って loader(8) で firmware をロードするという、ちょっとトリッキーなやり方をしていたのです。wifi-firmware-mt76-kmod のような ports でこの形式の *_bin.ko ファイルがインストールされます。
FreeBSD 15.0 からは /boot/firmware/ に firmware のバイナリをそのまま格納しておけば loader(8) がロードしてくれるという、素直な動作になりました。ただし 15.0 移行後も *_bin.ko 形式のまま残っている ports がありますが、移行は段階的に進んでいます。
firmware については別記事にまとめてあります。
4 モジュールの操作
設定ファイルに書いて再起動する前に、まず手動でロードして動作を確認するのが基本的な手順です。
ロード
kldload でモジュールを手動でロードします。
# kldload if_mt76
拡張子 .ko は省略できます。/boot/kernel/ と /boot/modules/ の両方が検索されます。検索パスは sysctl の kern.module_path で管理されており、デフォルトは以下の通りです。
$ sysctl kern.module_path
kern.module_path: /boot/kernel;/boot/modules
モジュールが他のモジュールに依存している場合、依存モジュールも自動的にロードされます。
ロードに成功すればプロンプトが返ります。以下のエラーが出た場合は既にロード済みかカーネルに内蔵されているという意味で、問題のある状態ではありません。
kldload: can't load if_mt76: module already loaded or in kernel
ロード後は dmesg で認識状況を確認します。
# dmesg | tail
状態確認
kldstat で現在ロードされているモジュールの一覧を表示します。
# kldstat
Id Refs Address Size Name
1 46 0xffffffff80200000 1dcf9a8 kernel
2 1 0xffffffff82066000 57440 zfs.ko
3 2 0xffffffff820be000 3b00 if_mt76.ko
Id はモジュールの識別番号です。Refs は参照カウントです。1 はそのモジュール自身の参照のみを示し、アンロードできます。2 以上の場合は他のモジュールから参照されているためアンロードできません。Name はモジュールのファイル名です。
特定のモジュールだけ確認したい場合は -n オプションを使います。
# kldstat -n zfs.ko
アンロード
kldunload でモジュールをアンロードします。
# kldunload if_mt76
Refs が 1 より大きい場合、つまり他のモジュールから参照されている場合はアンロードできません。
kldunload: can't unload file: Device busy
この場合は依存しているモジュールを先にアンロードする必要があります。使用中のデバイスがある場合も同様にアンロードできません。
5 起動時の自動ロード
手動ロードで動作を確認したら、次は起動時の自動ロードを設定します。方法は二つあり、それぞれ役割が異なります。
loader.conf
/boot/loader.conf はカーネルが起動する前、ブートローダーが処理します。この段階ではまだルートファイルシステムがマウントされておらず、ネットワークも動いていません。カーネル本体を読み込んだ直後の最小限の環境でモジュールがロードされます。
モジュール名_load="YES"
rc.conf の kld_list
/etc/rc.conf の kld_list はその後、マルチユーザーモードへの移行直前に rc.d/kld スクリプトが処理します。この時点ではルートファイルシステムは既にマウントされており /boot/modules/ も読める状態になっています。
kld_list="モジュール名"
複数の場合はスペース区切りで並べます。
kld_list="i915kms if_mt76"
使い分けの基準
判断の基準はシンプルで、ルートファイルシステムのマウントに必要かという一点です。ZFS をルートファイルシステムに使う場合の zfs.ko のように、マウント前に必要なものは loader.conf に書きます。GPU ドライバや無線 LAN ドライバはルートマウント後で十分なため kld_list に書きます。
ports 由来のモジュールは /boot/modules/ に置かれます。loader.conf が処理される段階では /boot/modules/ がまだ読める保証がないため、ports 由来のモジュールは原則として kld_list に書くのが安全です。
同じモジュールを両方に書いた場合、rc.d/kld が kldload を実行した時点で already loaded エラーが返りますが、スクリプトはこれを無視して続行するため実害はありません。ただし設定の意図が曖昧になるため、どちらか一方に記述することをお勧めします。
6 devmatch による自動ロード
FreeBSD 12.0 で導入された devmatch は、ハードウェアを自動検出してドライバを自動ロードするユーティリティです。起動時に rc.d/devmatch が実行され以下の順で処理されます。
バスをスキャン(PCI、USB など)
→ ドライバが割り当たっていないデバイスを検出
→ /boot/kernel/ と /boot/modules/ の .ko を検索
→ MODULE_PNP_INFO で宣言された PNP テーブルと照合
→ マッチしたドライバを kldload
既に attach 済みのデバイスはスキャン対象から除外されます。対象バスは PCI / PCIe、USB、ACPI で、ISA など古いバスは対象外です。
ロードの三段階の中では loader.conf の後、kld_list の前に位置します。
[1] loader 段階 loader.conf の *_load="YES"
[2] rc.d/devmatch ハードウェアスキャンによる自動ロード
[3] rc.d/kld rc.conf の kld_list による明示的なロードdevmatch でロードされないモジュール
MODULE_PNP_INFO を持たない、またはハードウェア検出と無関係なモジュールは devmatch の対象外です。
MODULE_PNP_INFO とはカーネルモジュールが持っている「自分はどのデバイスに対応しているか」を宣言するマクロです。devmatch はこの情報を読んでドライバとデバイスのマッチングを行います。
対象外となるモジュールの例です。
pf.ko / ipfw.ko ファイアウォール
zfs.ko ZFS
linux.ko Linux 互換レイヤ
i915kms.ko Intel GPU ドライバ
amdgpu.ko AMD GPU ドライバ
GPU ドライバが対象外なのは初期化のタイミングの問題があるためで、kld_list への明示的な記述が必要です。
7 ロード抑制の設定
デフォルトやdevmatch で自動的にロードされるモジュールをロードさせたくない場合のやり方です。
ロード抑制の仕組みは段階ごとに異なります。
段階 設定場所 設定内容
------------------------------------------------------------------------------------------------
loader 段階 /boot/loader.conf module_blacklist
devmatch 段階 /etc/rc.conf devmatch_blocklist
rc.d 段階 kld_list から外す 記述しないだけでよい
module_blacklist に指定されたモジュールは loader.conf の *_load="YES"による自動ロードができなくなります。ただしローダープロンプトから直接ロードすることは可能で、他のモジュールの依存関係として間接的にロードされることは防げません。またカーネル内蔵のモジュールには効きません。
特定のモジュールを完全に抑制したい場合は該当する段階すべてで設定が必要です。
8 ports 由来モジュールの典型例
drm-kmod
デスクトップ・ラップトップで GPU を使う場合に必要なドライバです。
# pkg install drm-kmod
drm-kmod はメタポートで、実体は GPU の世代に対応した drm-61-kmod などのドライバ本体と gpu-firmware-kmod という firmware メタパッケージで構成されています。pkg install drm-kmod 一発でドライバと firmware の両方がインストールされます。
kld_list への記述は GPU の種類によって異なります。
# Intel GPU
kld_list="i915kms"
# AMD GPU
kld_list="amdgpu"
FreeBSD カーネルモジュールとライセンス
FreeBSD プロジェクトのポリシーとして、FreeBSD プロジェクトのポリシーとして、ライセンス上の問題が生じる恐れのあるGPL only のようなコードはカーネルに含めない方針です。
最近、デバイスの開発元によって書かれたデバイスドライバは、GPLと、BSDやMITのようなpermissive なライセンスのデュアルライセンスのものが多くなってきていますので、Linux用のドライバもカーネルモジュールとして base system に入る例が増えています。
ドライバのライセンスと配置の関係
ライセンス 配置
---------------------------------------------------
MIT / BSD base system に入れられる
GPL BSD デュアル base system に入れられる
GPL only base system 不可、ports にも入らない
具体例
Linux 用のドライバである iwlwifi(Intel WiFi)は `GPL-2.0 OR BSD-3-Clause` のデュアルライセンスであるため base system に取り込むことができました。Intel i915 GPU ドライバは MIT ライセンスです。
drm-kmod が ports に置かれているのは ABI の問題だけでなく、一部のコンポーネントのライセンスが厳密にはbase system に組み込めないという事情もあります。
GPL only ドライバの問題
GPL only のドライバは Linux カーネルのコードをそのまま移植できません。FreeBSD が独自に書き直すか、デュアルライセンス化を働きかけるか、あるいはサポートを断念するかという選択になります。
Raspberry Pi 5 の RP1 チップ(USB、Ethernet、WiFi がぶら下がるサウスブリッジ)のドライバは Linux では GPL で実装されており、FreeBSD への移植が進んでいません。OpenBSD 7.8(2025年10月)では独自に書き起こした RP1 ドライバが追加されましたが、FreeBSD では 2025年時点でまだ対応中です。
FreeBSD で使えるドライバの範囲はライセンスによって制約されており、「Linux では動くのに FreeBSD では動かない」ハードウェアの多くはこのライセンス問題が背景にあります。
終わりに
まとまったドキュメントがなかったため、断片的な情報を集めてまとめてみました。いかがだったでしょうか?間違いなどございましたら、是非コメントでご指摘ください。
