Linuxカーネルモジュール開発入門|システムプログラミング実践ガイド【2026年版】
Linux 6.19/7.0時代のカーネルモジュール開発を基礎から解説。実装可能なコード例とビルド手順、デバッグ方法まで網羅した実践ガイド。
約6分で読めますはじめに:なぜカーネルモジュール開発なのか
Linuxカーネルモジュールは、システムを再起動せずにカーネルの機能を動的に拡張できる強力な仕組みです。デバイスドライバの開発、ファイルシステムの拡張、セキュリティ機能の追加など、低レイヤの技術を実践的に学ぶ上で欠かせない知識です。
2026年2月にリリースされたLinux 6.19では、名前空間の一覧を取得する新システムコールlistns(2)やライブアップデート機能「Live Update Orchestrator」が搭載され、次期メインラインはLinux 7.0への移行が発表されています。本記事では、この最新カーネル環境でのモジュール開発を、実装可能なコード例とともに解説します。
カーネルモジュールとは何か
基本概念
カーネルモジュールは、実行中のカーネルに動的にロード・アンロードできるコード片です。通常のユーザー空間プログラムとは異なり、カーネル空間で動作するため、ハードウェアへの直接アクセスやカーネル内部のデータ構造を操作できます。
主な用途:
- デバイスドライバ(USBデバイス、ネットワークカード等)
- ファイルシステムの拡張(ext4、btrfs等)
- ネットワークプロトコルスタック
- セキュリティモジュール(SELinux、AppArmor等)
従来の静的リンクとの違い
カーネル6.8以降、モジュールローダー機能はすべてカーネル内部に統合され、リンカ機能まで組み込まれています。これにより、モジュールの依存関係解決やシンボル解決が効率化されました。
開発環境のセットアップ
必要なパッケージのインストール
Ubuntu/Debian系:
sudo apt update
sudo apt install build-essential linux-headers-$(uname -r) kmod
RHEL/CentOS系:
sudo dnf groupinstall "Development Tools"
sudo dnf install kernel-devel kernel-headers
カーネル設定の確認
モジュール機能が有効になっているか確認:
cat /boot/config-$(uname -r) | grep CONFIG_MODULES
# CONFIG_MODULES=y が出力されればOK
Hello Worldモジュールの実装
ソースコード(hello.c)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World kernel module");
MODULE_VERSION("1.0");
static int __init hello_init(void)
{
pr_info("Hello, Kernel World!\n");
return 0;
}
static void __exit hello_exit(void)
{
pr_info("Goodbye, Kernel World!\n");
}
module_init(hello_init);
module_exit(hello_exit);
重要な変更点(Linux 6.15以降):
x86システムでIBT(Indirect Branch Tracking)が有効な場合、古いinit_module()とcleanup_module()は非推奨となり、ビルドエラーの原因となります。必ずmodule_init()/module_exit()マクロを使用してください。
Makefile
obj-m += hello.o
PWD := $(CURDIR)
KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
Linux 6.13以降の改善:
-Cオプションの代わりに-fオプションを使うことで、不要なディレクトリ移動を回避できます:
all:
make -f $(KDIR)/Makefile M=$(PWD) modules
ビルドと実行
# ビルド
make
# モジュールのロード
sudo insmod hello.ko
# カーネルログの確認
dmesg | tail
# モジュールの一覧表示
lsmod | grep hello
# モジュールのアンロード
sudo rmmod hello
# 再度ログ確認
dmesg | tail
実践:文字デバイスドライバの作成
基本構造
カーネルモジュールの実践として、簡単な文字デバイスドライバを実装します。このドライバは、/dev/mychardevとしてアクセス可能なデバイスファイルを作成し、read/write操作をサポートします。
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#define DEVICE_NAME "mychardev"
#define BUF_SIZE 1024
static int major_number;
static struct class *chardev_class = NULL;
static struct cdev chardev_cdev;
static char kernel_buffer[BUF_SIZE];
static int chardev_open(struct inode *inode, struct file *file)
{
pr_info("mychardev: Device opened\n");
return 0;
}
static int chardev_release(struct inode *inode, struct file *file)
{
pr_info("mychardev: Device closed\n");
return 0;
}
static ssize_t chardev_read(struct file *file, char __user *buf,
size_t len, loff_t *offset)
{
size_t to_read = min(len, (size_t)(BUF_SIZE - *offset));
if (to_read == 0)
return 0;
if (copy_to_user(buf, kernel_buffer + *offset, to_read))
return -EFAULT;
*offset += to_read;
return to_read;
}
static ssize_t chardev_write(struct file *file, const char __user *buf,
size_t len, loff_t *offset)
{
size_t to_write = min(len, (size_t)(BUF_SIZE - *offset));
if (to_write == 0)
return -ENOSPC;
if (copy_from_user(kernel_buffer + *offset, buf, to_write))
return -EFAULT;
*offset += to_write;
return to_write;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = chardev_open,
.release = chardev_release,
.read = chardev_read,
.write = chardev_write,
};
static int __init chardev_init(void)
{
dev_t dev;
// デバイス番号の動的割り当て
if (alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME) < 0) {
pr_err("Failed to allocate device number\n");
return -1;
}
major_number = MAJOR(dev);
// デバイスクラスの作成
chardev_class = class_create(DEVICE_NAME);
if (IS_ERR(chardev_class)) {
unregister_chrdev_region(dev, 1);
return PTR_ERR(chardev_class);
}
// デバイスファイルの作成
if (IS_ERR(device_create(chardev_class, NULL, dev, NULL, DEVICE_NAME))) {
class_destroy(chardev_class);
unregister_chrdev_region(dev, 1);
return -1;
}
// 文字デバイスの初期化と登録
cdev_init(&chardev_cdev, &fops);
if (cdev_add(&chardev_cdev, dev, 1) < 0) {
device_destroy(chardev_class, dev);
class_destroy(chardev_class);
unregister_chrdev_region(dev, 1);
return -1;
}
pr_info("mychardev: Device registered with major number %d\n", major_number);
return 0;
}
static void __exit chardev_exit(void)
{
dev_t dev = MKDEV(major_number, 0);
cdev_del(&chardev_cdev);
device_destroy(chardev_class, dev);
class_destroy(chardev_class);
unregister_chrdev_region(dev, 1);
pr_info("mychardev: Device unregistered\n");
}
module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple character device driver");
テスト手順
# モジュールのロード
sudo insmod mychardev.ko
# デバイスファイルの確認
ls -l /dev/mychardev
# 書き込みテスト
echo "Hello from userspace" | sudo tee /dev/mychardev
# 読み込みテスト
sudo cat /dev/mychardev
# モジュールのアンロード
sudo rmmod mychardev
デバッグとトラブルシューティング
printk vs pr_info/pr_err
現代のカーネル開発では、printk()よりもpr_info(), pr_err(), pr_warn()などのマクロを使用することが推奨されます。
pr_info("情報メッセージ\n");
pr_warn("警告メッセージ\n");
pr_err("エラーメッセージ\n");
dmesgでのログレベル設定
# すべてのカーネルメッセージを表示
sudo dmesg -w
# エラーメッセージのみ表示
sudo dmesg --level=err
カーネルクラッシュ時の対処
モジュールのバグがカーネルパニックを引き起こす可能性があります。開発時は仮想マシン環境の使用を強く推奨します:
# QEMU/KVM環境でのテスト
qemu-system-x86_64 -kernel /boot/vmlinuz-$(uname -r) \
-initrd /boot/initrd.img-$(uname -r) \
-m 2048 -enable-kvm
モジュールパラメータの活用
実行時にモジュールの動作を変更できるパラメータ機能:
static int debug_level = 0;
module_param(debug_level, int, 0644);
MODULE_PARM_DESC(debug_level, "Debug level (0=off, 1=info, 2=verbose)");
static int __init mymodule_init(void)
{
if (debug_level > 0)
pr_info("Debug mode enabled (level=%d)\n", debug_level);
return 0;
}
使用例:
sudo insmod mymodule.ko debug_level=2
まとめ
本記事で解説した内容:
- カーネルモジュールの基礎概念:動的ロード・アンロードの仕組みと用途
- Linux 6.19/7.0対応の開発環境構築:必要なパッケージと設定確認
- Hello Worldモジュール:
module_init()/module_exit()の正しい使い方 - 文字デバイスドライバの実装:
file_operations構造体とデバイスファイル管理 - デバッグ手法:
pr_*()マクロ、dmesg、QEMU環境でのテスト - モジュールパラメータ:実行時の動作カスタマイズ
カーネルモジュール開発は、Linuxシステムの深い理解とC言語の実践力を養う最良の方法の一つです。次のステップとして、procfs/sysfs経由のユーザーインターフェース実装や、割り込みハンドラの作成に挑戦してみてください。
Sources: