はじめてのFreeBSDカーネルモジュール(2) Hello Worldにこぎつこう

前回（kldloadするとこまで）： http://qiita.com/illness072/items/48fd71b20f6cd372320c

前回はロード、アンロードまで上手くいってたっぽいので、今回は予告通り open/close/read をサポートしてみる。

open とか close とか read をサポートするには、まずロードされたときにモジュールの実体を作ってやらないといけない。`DEV_MODULE()` で教えてやったハンドラ関数 `hello_load()` のなかで、`make_dev()` とか `destroy_dev()` を呼んでやるわけだ。なんだかすごくオブジェクト指向でびっくり、もっと泥臭い(?)感じかと思ってた、カーネルランド。

ということで `sys/sys/conf.h` にある `struct cdevsw` をつかって、`make_dev()` に渡すための値を作ってやる。

```c
/* cdevポインタの置き場 */
static struct cdev *hello_dev;

/* 関数定義 */
static d_open_t    hello_open;
static d_close_t   hello_close;
static d_read_t    hello_read;

/**
 * エントリポイントの定義。
 * see: sys/sys/conf.h
 * 空のものは何もしない扱いっぽい？
 */
static struct cdevsw hello_cdevsw = {
  .d_version = D_VERSION, /* これが何なのかわかってない */
  .d_open = hello_open,
  .d_close = hello_close,
  .d_read = hello_read,
  .d_write = (d_write_t *)nullop, /* 何もしないよって事の表現っぽい？ */
  .d_name = "hello",
};
```

\# コメントがぽいぽいうるさいけど、勉強しがてら書いてるので勘弁してください。

ようはこれから書く hello_open とかそういう関数のポインタをまとめて突っ込んでやるみたい。
そして実際にこの hello_dev とか hello_cdevsw とかを `make_dev()` に渡すようハンドラを書き換える。

```c
static int
hello_load(module_t mod __unused, int /* modeventtype_t */ what,
           void* args __unused)
{
  /**
   * see: man 9 module
   */
  int err = 0;

  switch (what) {
  case MOD_LOAD:
    printf("hello: Load\n");
    hello_dev = make_dev_credf(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK,
                              &hello_cdevsw, 0,
                              NULL, UID_ROOT, GID_WHEEL, 0666, "hello");
    break;
  case MOD_UNLOAD:
    destroy_dev(hello_dev);
    printf("hello: Unload\n");
    break;
  case MOD_SHUTDOWN:
    printf("hello: Shutdown\n");
    break;
  case MOD_QUIESCE:
    printf("hello: Quisce\n");
    break;
  default:
    err = EOPNOTSUPP; /* Operation not supported */
    break;
  }
  return err;
}
```

MOD_LOAD のときに `make_dev_credf()` ってので hello_cdevsw を渡し、返り値として hello_dev を受けとる。この hello_dev ってのが hello モジュールのインスタンスみたいなものみたい。
で、MOD_UNLOAD のなかでその hello_dev を渡して `destroy_dev()` を呼ぶことでモジュールの実体を削除する、ということらしい。

これでモジュール自体はできあがったので、 open/close/read に対する実装を書いてやる。
まずは open と close の実装。

```c
/**
 * open に対する処理。何もしない。
 */
static int
hello_open(struct cdev *dev __unused, int oflags __unused, int devtype __unused,
          struct thread *td __unused)
{
  printf("hello: Open\n");
  return (0);
}

/**
 * close に対する処理。何もしない。
 */
static int
hello_close(struct cdev *dev __unused, int fflag __unused, int devtype __unused,
           struct thread *td __unused)
{
  printf("hello: Close\n");
  return (0);
}
```

デバッグプリントだけして、実際はなにもしない。
最後に read に対する実装。

```c
/**
 * read(2) に対する処理。"Hello!\n" を返す
 * see: man 9 uiomove, man 2 read
 *
 * - uio に入ってくるもの（たぶん）
 *   - read(int d, void *buf, size_t nbytes); の場合
 *     - uio->uio_iovcnt = 1
 *     - uio->uio_iov[0] = *buf
 *     - uio->uio_offset = d
 *     - uio->uio_resid  = nbytes
 *   - readv(int d, const struct iovec *iov, int iovcnt); の場合
 *     - uio->uio_iovcnt = iovcnt
 *     - uio->uio_offset = d
 *     - 他は未調査
 * - uiomove の第二引数が 0 だとEOFになるっぽい
 * - 0 を返せば成功扱いっぽい
 */
static int
hello_read(struct cdev *dev __unused, struct uio *uio, int ioflag __unused)
{
  int error;
  char message[] = "Hello!";
  size_t length = sizeof(message) / sizeof(char);
  /* 返却サイズ計測。0 になるならEOF */
  length = MIN(uio->uio_resid,
               uio->uio_offset >= length ? 0 : length - uio->uio_offset);
  /* DEBUG */
  printf("hello: Read: uio: uio_iovcnt=%d, uio_offset=%d, uio_resid=%d,"
         " uio_segflg=%s, uio_rw=%s\n",
         uio->uio_iovcnt, (int)(uio->uio_offset), (int)(uio->uio_resid),
         uio->uio_segflg == 0 ? "USERSPACE"
                              : uio->uio_segflg == 1 ? "SYSSPACE"
                                                     : "NOCOPY",
         uio->uio_rw == 0 ? "READ" : "WRITE");
  /* ユーザランドに渡してやる */
  error = uiomove(message, length, uio);
  /* DEBUG */
  if (error != 0) {
    printf("hello: Read: uiomove failed! (%d)\n", error);
  } else {
    printf("hello: Read: uiomove success, message=\"%s\", length=%d\n",
           message, (int)length);
  }
  return (error);
}
```

ユーザランドになんか転送するには `uiomove()` を使うっぽい。こいつに渡す length が、ユーザランド側の read の返り値になるみたい。つまりこいつに 0 を渡せば、よくある `while ((cnt = read(fd, buf, sizeof(buf))) > 0) { ... }` みたいなループを抜ける感じになる。

さて、ここまでの実装で open/close/read ができるようになった。となれば cat ができる。ということでやってみる。

```shell-session
$ cd sys/modules/hello
$ make
$ sudo kldload ./hello.ko
$ cat /dev/hello
Hello!
$ sudo kldunload ./hello.ko
```

よし、できた。

最後にソースコード全文。そろそろヘッダファイルを分けよう。

```c:hello.c
/**
 * キャラクタデバイスモジュールのサンプル。
 * printf してる部分は dmesg に書かれる。
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/systm.h>
#include <sys/uio.h>

/* cdevポインタの置き場 */
static struct cdev *hello_dev;

/* 関数定義 */
static d_open_t    hello_open;
static d_close_t   hello_close;
static d_read_t    hello_read;

/**
 * エントリポイントの定義。
 * see: sys/sys/conf.h
 * 空のものは何もしない扱いっぽい？
 */
static struct cdevsw hello_cdevsw = {
  .d_version = D_VERSION, /* これが何なのかわかってない */
  .d_open = hello_open,
  .d_close = hello_close,
  .d_read = hello_read,
  .d_write = (d_write_t *)nullop, /* 何もしないよって事の表現っぽい？ */
  .d_name = "hello",
};


/**
 * read(2) に対する処理。"Hello!\n" を返す
 * see: man 9 uiomove, man 2 read
 *
 * - uio に入ってくるもの（たぶん）
 *   - read(int d, void *buf, size_t nbytes); の場合
 *     - uio->uio_iovcnt = 1
 *     - uio->uio_iov[0] = *buf
 *     - uio->uio_offset = d
 *     - uio->uio_resid  = nbytes
 *   - readv(int d, const struct iovec *iov, int iovcnt); の場合
 *     - uio->uio_iovcnt = iovcnt
 *     - uio->uio_offset = d
 *     - 他は未調査
 * - uiomove の第二引数が 0 だとEOFになるっぽい
 * - 0 を返せば成功扱いっぽい
 */
static int
hello_read(struct cdev *dev __unused, struct uio *uio, int ioflag __unused)
{
  int error;
  char message[] = "Hello!";
  size_t length = sizeof(message) / sizeof(char);
  /* 返却サイズ計測。0 になるならEOF */
  length = MIN(uio->uio_resid,
               uio->uio_offset >= length ? 0 : length - uio->uio_offset);
  /* DEBUG */
  printf("hello: Read: uio: uio_iovcnt=%d, uio_offset=%d, uio_resid=%d,"
         " uio_segflg=%s, uio_rw=%s\n",
         uio->uio_iovcnt, (int)(uio->uio_offset), (int)(uio->uio_resid),
         uio->uio_segflg == 0 ? "USERSPACE"
                              : uio->uio_segflg == 1 ? "SYSSPACE"
                                                     : "NOCOPY",
         uio->uio_rw == 0 ? "READ" : "WRITE");
  /* ユーザーランドに渡してやる */
  error = uiomove(message, length, uio);
  /* DEBUG */
  if (error != 0) {
    printf("hello: Read: uiomove failed! (%d)\n", error);
  } else {
    printf("hello: Read: uiomove success, message=\"%s\", length=%d\n",
           message, (int)length);
  }
  return (error);
}

/**
 * open に対する処理。何もしない。
 */
static int
hello_open(struct cdev *dev __unused, int oflags __unused, int devtype __unused,
          struct thread *td __unused)
{
  printf("hello: Open\n");
  return (0);
}

/**
 * close に対する処理。何もしない。
 */
static int
hello_close(struct cdev *dev __unused, int fflag __unused, int devtype __unused,
           struct thread *td __unused)
{
  printf("hello: Close\n");
  return (0);
}

static int
hello_load(module_t mod __unused, int /* modeventtype_t */ what,
           void* args __unused)
{
  /**
   * see: man 9 module
   *
   * - what の中身
   *   - モジュールがロードされた   ... what = MOD_LOAD
   *   - モジュールがアンロード     ... what = MOD_QUIESCE
   *   - MOD_QUIESCE が 0 を返した  ... what = MOD_UNLOAD
   *   - システムのシャットダウン時 ... what = MOD_SHUTDOWN
   *
   * - MOD_QUIESCE と MOD_UNLOAD の違い：
   *   - モジュールが使用中         ... MOD_QUESCE を失敗させる
   *   - アンロードが不可能         ... MOD_UNLOAD を失敗させる
   *
   * - what の値を認識できない場合は EOPNOTSUPP を返すべき
   */
  int err = 0;

  switch (what) {
  case MOD_LOAD:
    printf("hello: Load\n");
    hello_dev = make_dev_credf(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK,
                              &hello_cdevsw, 0,
                              NULL, UID_ROOT, GID_WHEEL, 0666, "hello");
    break;
  case MOD_UNLOAD:
    destroy_dev(hello_dev);
    printf("hello: Unload\n");
    break;
  case MOD_SHUTDOWN:
    printf("hello: Shutdown\n");
    break;
  case MOD_QUIESCE:
    printf("hello: Quisce\n");
    break;
  default:
    err = EOPNOTSUPP; /* Operation not supported */
    break;
  }
  return err;
}

/**
 * see: man 9 DEV_MODULE
 * 第1引数： モジュールの名前
 * 第2引数： モジュールのイベントハンドラ関数名
 * 第3引数： モジュールのイベントハンドラ関数に渡す引数
 *
 * - DECLARE_MODULE の 糖衣構文っぽい
 */
DEV_MODULE(hello, hello_load, NULL);

/**
 * see: man 9 MODULE_VERSION
 * 第1引数： モジュールの名前
 * 第2引数： バージョン(int)
 *
 * - MODULE_DEPEND とかで他のモジュールからバージョン依存
 *   されるときに使ったりする
 */
MODULE_VERSION(hello, 0);
```