メインコンテンツへスキップ
Tech Playground
低レイヤ・言語

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:

#Linux #カーネル #システムプログラミング #C言語
シェア: