LoginSignup
4
7

More than 5 years have passed since last update.

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

Posted at

前回(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() に渡すための値を作ってやる。

/* 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() に渡すようハンドラを書き換える。

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 の実装。

/**
 * 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 に対する実装。

/**
 * 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 ができる。ということでやってみる。

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

よし、できた。

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

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);
4
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
7