/dev/keikyuで "ダァ!!シエリイェッス!!"
Linuxデバイスドライバを書く練習。/dev/fizzbuzzにしようかと思ったけど、
なんかあれだったので。まあ中身は fizzbuzzなんですが・・・。
ソース
C言語をスクラッチから書くのは慣れない・・・.
#include <linux/init.h> #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <asm/uaccess.h> #include <asm/string.h> MODULE_LICENSE("Dual BSD/GPL"); static int keikyu_open(struct inode *inode, struct file *file) { file->private_data = (void*)1L; printk("[%s] init keikyu device\n", __func__); return 0; } #define KQ_CLOSE_DOOR "ダァ!!シエリイェッス!!" #define KQ_CLOSE "シエリイェッス!!" #define KQ_DOOR "ダァ!!" static ssize_t keikyu_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops) { long num; char *write_buf; ssize_t write_len; char tmp[16]; int ret = 0; num = (long)file->private_data; if ((num % 3 == 0) && (num % 5 == 0)) { write_buf = KQ_CLOSE_DOOR; write_len = strlen(KQ_CLOSE_DOOR); } else if (num % 5 == 0) { write_buf = KQ_CLOSE; write_len = strlen(KQ_CLOSE); } else if (num % 3 == 0) { write_buf = KQ_DOOR; write_len = strlen(KQ_DOOR); } else { ret = sprintf(tmp, "%ld", num); if (ret < 0) { return -1; } write_buf = tmp; write_len = ret; } if (copy_to_user(buf, write_buf, write_len)) { return -EFAULT; } num++; file->private_data = (void*)num; return write_len; } static int keikyu_close(struct inode *inode, struct file *file) { /* do nothing */ printk("[%s] rmmod\n", __func__); return 0; } static struct file_operations keikyu_fops = { .open = keikyu_open, .read = keikyu_read, .release = keikyu_close, }; static int keikyu_dev_limit = 1; static struct cdev keikyu_cdev; static struct class *keikyu_class = NULL; static int keikyu_major; static dev_t keikyu_dev; static int keikyu_init(void) { dev_t dev = MKDEV(0, 0); int ret; ret = alloc_chrdev_region(&dev, 0, keikyu_dev_limit, "keikyu"); if (ret != 0) goto error; keikyu_major = MAJOR(dev); cdev_init(&keikyu_cdev, &keikyu_fops); keikyu_cdev.owner = THIS_MODULE; keikyu_cdev.ops = &keikyu_fops; ret = cdev_add(&keikyu_cdev, dev, 1); if (ret != 0) goto error1; keikyu_dev = MKDEV(keikyu_major, 0); keikyu_class = class_create(THIS_MODULE, "keikyu"); if (IS_ERR(keikyu_class)) goto error2; device_create(keikyu_class, NULL, dev, NULL, "keikyu0"); return 0; error2: printk("[%s] error2\n", __func__); cdev_del(&keikyu_cdev); error1: printk("[%s] error1\n", __func__); unregister_chrdev_region(dev, keikyu_dev_limit); error: return -1; } static void keikyu_exit(void) { dev_t dev = MKDEV(keikyu_major, 0); device_destroy(keikyu_class, keikyu_dev); class_destroy(keikyu_class); cdev_del(&keikyu_cdev); unregister_chrdev_region(dev, keikyu_dev_limit); } module_init(keikyu_init); module_exit(keikyu_exit);
ビルド & ロード
Makefileを初めに書きましょう。
EXTRA_CFLAGS += -Wall CFILES = keikyu.c obj-m += keikyu.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
パーミッションを調整するため /etc/udev/rules.d/51-keikyu.rulesというファイルを
作成し、以下の内容を書きます。
KERNEL=="keikyu0", GROUP="root", MODE="0644"
ローダブルモジュールをロードしましょう。そしてちゃんとデバイスファイルが
できているか確認しましょう。
% make % sudo insmod ./keikyu.ko % ls -l /dev/keikyu0 crw-r--r-- 1 root root 250, 0 2011-10-30 19:31 /dev/keikyu0
サンプルプログラム
readを n回行ったことに対して, fizzbuzzをするドライバですので、
以下のようなサンプルを書きました。
#!perl use strict; use warnings; my $file = "/dev/keikyu0"; open my $fh, "<", $file or die "Can't open file: $!"; for my $i (1..30) { my $ret = sysread $fh, my $buf, 256; die "Can't read: $!" unless defined $ret; $ret = syswrite STDOUT, $buf, $ret; die "Can't write: $!" unless defined $ret; syswrite STDOUT, "\n"; } close $fh;
結果は以下の通り
1 2 ダァ!! 4 シエリイェッス!! ダァ!! 7 8 ダァ!! シエリイェッス!! 11 ダァ!! 13 14 ダァ!!シエリイェッス!! 16 17 ダァ!! 19 シエリイェッス!! ダァ!! 22 23 ダァ!! シエリイェッス!! 26 ダァ!! 28 29 ダァ!!シエリイェッス!!
最後にはアンロードしましょう。
% sudo rmmod keikyu % ls -l /dev/keikyu0 # ちゃんと終了処理がされていることを確認 ls: cannot access /dev/keikyu0: No such file or directory