前回(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
よし、できた。
最後にソースコード全文。そろそろヘッダファイルを分けよう。
/**
* キャラクタデバイスモジュールのサンプル。
* 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);