本記事では、カーネルモジュールの仕組みや扱い方について、サンプルコードを用いて分かりやすく解説します。
カーネルモジュールとは
カーネルモジュールとは、OS(オペレーティングシステム)の中核部分であるカーネルに対して動的に追加や削除を行うことができるソフトウェアモジュールのことを指します。一般的にLinuxにおいて用いられる概念ですが、WindowsやmacOSにおいても名称は異なりますが同等の概念は存在します。
以下の図のようなイメージです。(以下の図は、OSの構成を示しています。)
[カーネル空間とユーザー空間] OSはカーネル空間とユーザー空間という2つの主要な実行環境に分かれています。 カーネル空間は、OSの核心部分が実行されるメモリ空間です。カーネル空間では、ハードウェアの直接的な管理(メモリ管理やプロセススケジューリング、入出力制御など)を行うプログラムが実行されます。カーネルモジュールもカーネル空間で実行されるプログラムです。 ユーザー空間は、カーネル以外のプログラムが実行されるメモリ空間です。ユーザー空間では、ユーザーが直接利用するアプリケーション(ブラウザやオフィスソフトなど)やシステムが利用する様々なサービスプログラムが実行されます。
カーネルモジュールには以下のような特徴があります。
- 動的な追加・削除が可能
- 個別にビルドできるため、開発が容易 (機能追加のためにカーネル全体を再構築する必要がない)
- カーネルバージョンに影響を受ける (カーネルバージョンが変わるとそれに合わせてカーネルモジュールの更新が必要になる場合がある)
カーネルモジュールとして提供されるソフトウェアには、デバイスドライバやファイルシステム、ネットワークプロトコルなどがあります。特に、デバイスドライバとして使用されることが多いです。
カーネルモジュールの扱い方
Linuxにおけるカーネルモジュールの扱い方について簡単に説明します。
モジュールのロード・アンロード
カーネルモジュールをカーネルに追加することをロード、カーネルからロードされたカーネルモジュールを削除することをアンロードと呼びます。
insmod
insmodコマンドは、モジュールのファイル名(*.ko)を指定し、カーネルにロードします。
sudo insmod sample.ko
rmmod
rmmodコマンドは、モジュール名を指定し、モジュールをアンロードします。
sudo rmmod sample
モジュール名はモジュールのファイル名(*.ko)から拡張子(.ko)を除いたものとなります。
モジュールの情報確認
lsmod
lsmodコマンドは、ロードされているカーネルモジュールのリストを表示します。
$ lsmod
Module Size Used by
rfcomm 98304 4
cmac 12288 3
algif_hash 12288 1
...
「Module」列はモジュール名、「Size」列はモジュールのサイズ[KB]、「Used by」列はそのモジュールを使用している他のモジュールの数をそれぞれ表しています。
modinfo
modinfoコマンドは、カーネルモジュールの詳細情報を表示するために使用されます。このコマンドは、特定のカーネルモジュールに関するメタデータを提供し、モジュールのバージョン、説明、作者、ライセンス、依存関係、設定オプションなどの情報を含みます。
$ modinfo rfcomm
filename: /lib/modules/6.5.0-17-generic/kernel/net/bluetooth/rfcomm/rfcomm.ko
alias: bt-proto-3
license: GPL
version: 1.11
...
カーネルログ
カーネルログは、Linuxカーネルやカーネルモジュールからのメッセージや情報を記録したログファイルです。これにはシステムの起動情報、ハードウェア関連のメッセージ、ドライバの読み込み状況、システムエラー、警告、その他の診断情報などが含まれます。
カーネルログは、dmesgコマンドで確認することができます。
$ sudo dmesg | tail
[ 11.355490] NET: Registered PF_ALG protocol family
[ 22.616315] rfkill: input handler enabled
[ 23.955164] Bluetooth: RFCOMM TTY layer initialized
[ 23.955175] Bluetooth: RFCOMM socket layer initialized
[ 23.955182] Bluetooth: RFCOMM ver 1.11
[ 25.178507] rfkill: input handler disabled
[ 26.390179] kauditd_printk_skb: 48 callbacks suppressed
[ 26.390183] audit: type=1400 audit(1707993228.030:60): apparmor="DENIED" operation="capable" class="cap" profile="/snap/snapd/20671/usr/lib/snapd/snap-confine" pid=1898 comm="snap-confine" capability=12 capname="net_admin"
[ 26.390193] audit: type=1400 audit(1707993228.030:61): apparmor="DENIED" operation="capable" class="cap" profile="/snap/snapd/20671/usr/lib/snapd/snap-confine" pid=1898 comm="snap-confine" capability=38 capname="perfmon"
[ 324.409232] loop22: detected capacity change from 0 to 130880
※dmesgコマンドをそのまま実行すると、ログメッセージが全て出力されてしまいます。「| tail」を付けることで、最新ログの10行のみを出力することが可能です。
カーネルモジュールのサンプルコード解説
Linuxで簡単なカーネルモジュールのサンプルコード(sample.c)を用いて、Linuxにおけるカーネルモジュールの作成方法や扱い方について解説していきます。
環境は Ubuntu 22.04 LTS でカーネルバージョンは 6.5.0-17-generic です。
以下にサンプルコードを示します。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("furu");
MODULE_DESCRIPTION("hello module.");
MODULE_VERSION("0.01");
static int __init hello_init(void) {
printk(KERN_INFO "Hello, world!\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, world!\n");
}
module_init(hello_init);
module_exit(hello_exit);
サンプルコードの内容
このサンプルコードは、モジュールをロードしたタイミングでカーネルログに “Hello, world!” と出力し、アンロードしたタイミングで “Goodbye, world!” と出力します。
「ヘッダファイルの定義」、「メタデータの定義」、「ロード時の実行関数」、「アンロード時の実行関数」の順でそれぞれ説明していきます。
ヘッダファイルの定義
サンプルコードでは、以下のヘッダファイルをインクルードしています。
- linux/init.h
- linux/module.h
- linux/kernel.h
「linux/*.h」という表現は、Linuxカーネルのヘッダファイルのことを表します。
linux/init.h は、カーネルモジュールの初期化とクリーンアップのためのマクロや関数を定義しています。module_init() や module_exit() はこれに含まれるマクロです。
linux/module.h は、カーネルモジュールの定義、登録、およびカーネルとのインターフェースに必要な構造体、マクロ、関数を定義しています。MODULE_LICENSE()、MODULE_AUTHOR()、MODULE_DESCRIPTION()、MODULE_VERSION()などのメタデータを定義するマクロはこれに含まれます。
linux/kernel.h は、カーネルの中心的な機能やヘルパー関数を定義しています。カーネルログを出力する printk() はこれに含まれます。
メタデータの定義
サンプルコードでは、以下のメタデータを定義しています。
MODULE_LICENSE("GPL");
MODULE_AUTHOR("furu");
MODULE_DESCRIPTION("hello module.");
MODULE_VERSION("0.01");
「MODULE_LICENSE(“GPL”)」は、モジュールのライセンスを指定します。
「MODULE_AUTHOR(“furu”)」, 「MODULE_DESCRIPTION(“hello module.”)」 , 「MODULE_VERSION(“0.01”) 」は、それぞれモジュールの作者、説明、バージョン情報を定義しています。
これらのメタデータは、modinfoコマンドでカーネルモジュールを指定して実行することで確認することができます。
ロード時の実行関数
insmodコマンドでモジュールのロードを行ったとき、module_init() の引数に指定された関数が実行されます。
サンプルコードでは、hello_init() が指定されているので、以下の処理が実行されます。
printk(KERN_INFO "Hello, world!\n");
printk() はカーネルログを出力する関数であり、サンプルコードでは “Hello, world!” が出力されます。
先頭の KERNINFO はログレベルであり、「情報メッセージ」を表しています。
カーネルログのログレベルには、以下のような定義されています。
- KERN_EMERG:緊急
- KERN_ALERT:警報
- KERN_CRIT:重大
- KERN_ERR:エラー
- KERN_WARNING:警告
- KERN_NOTICE:通知
- KERN_INFO:情報
- KERN_DEBUG:デバッグ
アンロード時の実行関数
rmmodコマンドでモジュールのアンロードを行ったとき、module_exit() の引数に指定された関数が実行されます。
サンプルコードでは、hello_exit() が指定されているので、以下の処理が実行されます。
printk(KERN_INFO "Goodbye, world!\n");
サンプルコードのビルド, モジュールのロード・アンロード
それでは、以下の手順に従ってサンプルコードのビルドし、生成されたモジュールのロード・アンロードを行っていきます。
環境構築
サンプルコードをビルドするための環境構築を行います。
現在実行中のカーネルバージョンに合致するカーネルヘッダとC/C++のビルドに必要なパッケージをインストールします。
sudo apt-get install linux-headers-$(uname -r) build-essential
ビルド
サンプルコードをビルドするために、以下のMakefileを使用します。
obj-m += sample.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
サンプルコードと同じディレクトリ階層にMakefileを置き、makeコマンドを実行します。
$ ls
Makefile sample.c
$ make
make実行後、複数のファイルが生成されます。この中で、「sample.ko」がカーネルモジュールとなります。
$ ls
Makefile Module.symvers modules.order sample.c sample.ko sample.mod sample.mod.c sample.mod.o sample.o
モジュールのロード
insmodコマンドで sample.ko をロードします。
sudo insmod sample.ko
カーネルログに “Hello, world!” が出力されていればロード成功です。
[ 2271.659647] Hello, world!
モジュールのアンロード
rmmodコマンドでモジュールをアンロードします。
$ sudo rmmod sample
カーネルログに “Goodbye, world!” が出力されていればアンロード成功です。
[ 3122.581834] Goodbye, world!
まとめ
カーネルモジュールに関する内容を簡単にまとめます。
- カーネルモジュールとは、カーネルに対して動的に追加や削除を行うことができるソフトウェアモジュールのこと。
- カーネルモジュールをカーネルに追加することをロード、カーネルからロードされたカーネルモジュールを削除することをアンロードと呼ぶ。
- Linuxでは、カーネルモジュールのロードはinsmodコマンド、アンロードはrmmodコマンドを使用する。
- Linuxカーネルやカーネルモジュールからのメッセージや情報を記録したログファイルのことをカーネルログと呼ぶ。