読者です 読者をやめる 読者になる 読者になる

Linuxデバイスドライバを書いてみる(2)

デバイスドライバ

前回は最小構成のカーネルモジュールを作ってみました。
taltalp.hatenablog.jp

今回はデバイスを/devに登録して,ユーザー空間からデバイスドライバに触れてみたいと思います。

ソースコード

前回作成したMakefileはそのままで、test.cを編集しました。

test.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/pci.h>

#define DEVICE_CLASS_NAME "testclass"
#define DEVICE_NAME "testdevice"  
#define DRIVER_NAME "testdriver"

static int majorNumber;
static struct class* testClass = NULL;
static struct device* testDevice = NULL;

static int dev_open(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "dev_open\n");
	return 0;
}

static loff_t dev_seek(struct file *file, loff_t offset, int whence)
{
	printk(KERN_INFO "dev_seek\n");
	return 0;
}

static ssize_t dev_read(struct file *file, char __user *buf,
			size_t count, loff_t *ppos)
{
	printk(KERN_INFO "dev_read\n");
	return 0;
}

static ssize_t dev_write(struct file *file, const char __user *buf,
			size_t count, loff_t *ppos)
{
	printk(KERN_INFO "dev_write\n");
	return 0;
}

static int dev_mmap(struct file *file, struct vm_area_struct *vma)
{
	printk(KERN_INFO "dev_mmap\n");
	return 0;
}

static int dev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "dev_release\n");
	return 0;
}

static const struct file_operations fops = {
	.owner  = THIS_MODULE,
	.llseek = dev_seek,
	.open   = dev_open,
	.read   = dev_read,
	.write  = dev_write,
	.mmap   = dev_mmap,
	.release = dev_release,
};

static int test_init(void){
	printk(KERN_ALERT "driver loaded\n");
	printk(KERN_ALERT "Hello World\n");

	// キャラクタ型デバイスの登録
	majorNumber = register_chrdev(0, DEVICE_NAME, &fops);
	if(majorNumber < 0){
		printk(KERN_ALERT "Driver failed to register a mahor number\n");
		return majorNumber;
	}
	printk(KERN_INFO "driver registerd correctly with major number %d\n", majorNumber);

	// デバイスクラスの作成
	testClass = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
	if(IS_ERR(testClass)){
		unregister_chrdev(majorNumber, DEVICE_NAME);
		printk(KERN_ALERT "Failed to register device class\n");
		return PTR_ERR(testClass);
	}
	printk(KERN_INFO "device class registered correctly\n"); 

	// デバイスの作成
	testDevice = device_create(testClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
	if(IS_ERR(testDevice)){
		class_destroy(testClass);
		unregister_chrdev(majorNumber, DEVICE_NAME);
		printk(KERN_ALERT "Failed to create the device\n");
		return PTR_ERR(testDevice);
	}

	return 0;
}

static int test_exit(void){
	device_destroy(testClass, MKDEV(majorNumber, 0));
	class_unregister(testClass);
	class_destroy(testClass);
	unregister_chrdev(majorNumber, DEVICE_NAME);

	printk(KERN_ALERT "driver unloaded\n");
}

MODULE_LICENSE("Dual BSD/GPL");
module_init(test_init);
module_exit(test_exit);

ソースコードの説明

前回に比べてだいぶ長くなりました.
ドライバがインストールされたらまず呼び出されるのはtest_init()で,これは前回とかわりません。その中身でデバイスの登録をしています。メジャーナンバーを取得したり,デバイスクラスを作成したりしていますが,詳しい内容については説明を省きます。(詳しい話を頑張ってしようとしても多分間違ったことを言ってしまうと思うので)

また、test_init()のすぐ上にはfile_operationsという構造体があると思います。
これはユーザーモードからシステムコールが来た時に呼び出される関数の名前を書いておきます。
具体的には,ユーザーモードシステムコールopen()が実行されると.open = dev_openと書いてあるので,dev_open()が実行されます.渡される引数についてはそれぞれのシステムコールごとにいろいろあるんですが、詳しい内容はldd3やリファレンスを参考にしてください。

実際に動かしてみる

test.cをmakeして生成されたtest.koをインストールします。

$sudo insmod test.ko

ここで一度printkの内容を見てみます

$dmesg

すると

driver loaded
Hello World
driver registerd correctly with major number xxx
device class registerd correctly

と表示され,デバイスドライバが正しく登録されたことがわかります。
次に,

$ls /dev

testdevice

がいることを確認します。
ここで

$sudo cat /dev/testdevice
$dmesg

とするとシステムコールされて

dev_open
dev_read
dev_release

が表示されました。