shimada-kの日記

ソフトウェア・エンジニアのブログです

Linuxの組み込みカーネルモジュールを作成する方法

OSの研究をする上で、カーネルのパラメータの値を知りたい時や独自の機能を追加したい時などにカーネルモジュールとして実装することが多々あります。

そして往々にして知りたいパラメータはファイルの外にEXPORTされていないので、LKM(ローダブル・カーネル・モジュール)では実装できなくて、カーネルに静的に組み込まれた(ようはカーネルのビルド時にコンパイルされて静的にリンクされる形の)モジュールで実装する必要があります。

いつもはパラメータにアクセスできる既存のファイルに直接'#include "作ったファイル.c"'を書き込んでいましたが、カーネルコンフィグで組み込むことにしました。

環境:Linux kernel#2.6.32 amd64

今回は例としてキャラクタデバイスを使ったモジュールを組み込むことにします。
modsample.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h> // alloc_chrdev_region
#include <linux/cdev.h> // cdev_init
#include <asm/uaccess.h> // copy_from_user, copy_to_user

#define MODNAME "modsample"

#define MINOR_COUNT 1 // 接続するマイナー番号数

static dev_t dev_id;  // デバイス番号
static struct cdev c_dev; // キャラクタデバイス構造体

// 内部バッファ
#define MAX_BUFLEN 64
unsigned char cdev_buf[MAX_BUFLEN];
static int cdev_buflen = 0;


// スペシャルファイルをオープンした
static int cdev_open(struct inode *inode, struct file *filp) 
{
	printk(KERN_INFO "cdev_open\n");
	return 0;
}


// スペシャルファイルをクローズした
static int cdev_release(struct inode* inode, struct file* filp)
{
	printk(KERN_INFO "cdev_release\n");
	return 0;
}


// スペシャルファイルからの読み込み
static ssize_t cdev_read(struct file* filp, char* buf,
                        size_t count, loff_t* offset)
{
	int len = cdev_buflen;
	if(!len)
		return 0;

	printk(KERN_INFO "cdev_read count = %d\n", count);

	if(copy_to_user(buf, cdev_buf, len)){
		printk(KERN_WARNING "copy_to_user failed\n");
		return -EFAULT;
	}

	*offset += len;
	cdev_buflen = 0;

	return len;
}


// スペシャルファイルへの書き込み
static ssize_t cdev_write(struct file* filp, const char* buf,
                        size_t count, loff_t* offset)
{
	if(count >= MAX_BUFLEN)
		return -EFAULT;

	printk(KERN_INFO "cdev_write count = %d\n", count);

	if(copy_from_user(cdev_buf, buf, count)){
		printk(KERN_WARNING "copy_from_user failed\n");
		return -EFAULT;
	}

	cdev_buf[count] = '\0';
	printk(KERN_INFO "cdev_buf = %s\n", cdev_buf);

	*offset += count;
	cdev_buflen = count;

	return count;
}


// ファイルオペレーション構造体
// スペシャルファイルに対して読み書きなどを行ったときに呼び出す関数を登録する
static struct file_operations modsample_fops ={
	.owner   = THIS_MODULE,
	.open    = cdev_open,
	.release = cdev_release,
	.read    = cdev_read,
	.write   = cdev_write,
	.ioctl   = NULL,
};


// モジュール初期化
static int __init modsample_module_init(void)
{
	int ret;

	// キャラクタデバイス番号の動的取得
	ret = alloc_chrdev_region(&dev_id, // 最初のデバイス番号が入る
					0,  // マイナー番号の開始番号
					MINOR_COUNT, // 取得するマイナー番号数
					MODNAME // モジュール名
					);
	if(ret < 0){
		printk(KERN_WARNING "alloc_chrdev_region failed\n");
		return ret;
	}

	// キャラクタデバイス初期化
	// ファイルオペレーション構造体の指定もする
	cdev_init(&c_dev, &modsample_fops);
	c_dev.owner = THIS_MODULE;

	// キャラクタデバイスの登録
	// MINOR_COUNT が 
	ret = cdev_add(&c_dev, dev_id, MINOR_COUNT);
	if(ret < 0){
		printk(KERN_WARNING "cdev_add failed\n");
		return ret;
	}

	printk(KERN_INFO "modsample is loaded\n");
	printk(KERN_INFO "major = %d\n", MAJOR(dev_id));
	printk(KERN_INFO "minor = %d\n", MINOR(dev_id));

	return 0;
}


//  モジュール解放
static void __exit modsample_module_exit(void)
{
	// キャラクタデバイス削除
	cdev_del(&c_dev);

	// デバイス番号の返却
	unregister_chrdev_region(dev_id, MINOR_COUNT);

	printk(KERN_INFO "modsample is removed\n");
}


module_init(modsample_module_init);
module_exit(modsample_module_exit);

MODULE_DESCRIPTION(MODNAME);
MODULE_LICENSE("GPL2");

サンプルのソースコードこちらで公開されているものを若干改造したものになっています。

modsampleはキャラクタデバイスなので'カーネルツリー'/drivers/char以下にコピーします。

# cp modsample.c /usr/src/linux-source-2.6.32/drivers/char

そしてdrivers/char/Kconfigを編集します

#
# Character device configuration
#

menu "Character devices"

-追記ここから-
config MODSAMPLE
        tristate "sample charctor device"
        ---help---
                This is a sample device of static kernel-module.
-追記ここまで-

config VT
        bool "Virtual terminal" if EMBEDDED
        depends on !S390
        select INPUT

さらにdrivers/char/Makefileも編集します。

#
# Makefile for the kernel character device drivers.
#

#
# This file contains the font map for the default (hardware) font
#
FONTMAPFILE = cp437.uni

obj-y    += mem.o random.o tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o tty_buffer.$

obj-$(CONFIG_MODSAMPLE)         += modsample.o  ←追記
obj-$(CONFIG_LEGACY_PTYS)       += pty.o
obj-$(CONFIG_UNIX98_PTYS)       += pty.o

そしてビルドしてみます。

#cd /usr/src/linux-source-2.6.32
#fakeroot make-kpkg --initrd --revision=20110527 kernel_image

するとコンソールに「新しいコンフィグだけど、どうする?」みたいなメッセージがでるのでyキーを押しましょう。

f:id:shimada-k:20110527142400p:image:w360

そのままカーネルのビルドが完了したらmodsampleモジュールが組み込みモジュールとしてリンクされています。試しに遊んでみましょう。

デバイス番号を取得して/dev以下にデバイスファイルを作って、read/writeしてみます。

f:id:shimada-k:20110527213338p:image:w360

いいですね。うまくいっているようです。