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

/dev/keikyuで "ダァ!!シエリイェッス!!"

linux driver

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

終わりに

デバイスドライバの練習で書いたサンプルを示しました。実際にイーサネット
ドライバとかを書いてみたいものですが、最近のやつだと機能が複雑だし、
誰かが速攻で書いているのが普通ですのでね。すごいシンプルなハードで、
自分でドライバ書いて動く楽しみを味わいたいとは思いますが、まだまだその腕が
ないので、簡単そうなのを読んで学ぶというところですかね。


気が向いたら、*BSDでも書いてみようかと思います。