カーネルとか触れるとハッカーっぽくてカッコいいよね!くらいのモチベーションで、とりあえず簡単そうだしキャラクタデバイスでも書いてみますかと思い立った。
ちなみに FreeBSD も C もロクに触ったことないので何かおかしいところあったら指摘いただけると嬉しいです。
/usr/src/sys/dev/null/null.c とか /usr/share/examples/kld/cdev とか man 9 module とか FreeBSD Architecture Handbook (https://www.freebsd.org/doc/en_US.ISO8859-1/books/arch-handbook/index.html) の特に2章とか、読むべきドキュメントはまとまってるのでわりと簡単だった。
まずはファイルの構成。hello という名前のモジュールを作るとき。
/usr/src
に置けば他のモジュールと並んでそれっぽく見えるけど、その下で作業とかしたくないし、実際どこに置いてもこの構成さえ守っておけばちゃんとビルドが通るので俺はホームディレクトリの下でやってる。
.
`-- sys
|-- dev
| `-- hello
| `-- hello.c
`-- modules
`-- hello
`-- Makefile
Makefile は他のモジュールからパクる。SRCS にソースコードのファイル名を置けばいいっぽい。
.PATH: ${.CURDIR}/../../dev/hello
KMOD = hello
SRCS = hello.c
.include <bsd.kmod.mk>
.PATH を ${.CURDIR}
にして modules/ のなかに hello.c を置いてしまっても良さそうなものだけど、他のモジュールがみんなそうしてる感じなのでそれに従っとく。
そして最後にメインとなるソースコード。
/**
* キャラクタデバイス。
* printf してる部分は dmesg に書かれる。
*/
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/systm.h>
/**
* 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_QUIESCE を失敗させる
* - アンロードが不可能 ... MOD_UNLOAD を失敗させる
*
* - what の値を認識できない場合は EOPNOTSUPP を返すべき
*/
static int
hello_load(module_t mod __unused, int /* modeventtype_t */ what,
void* args __unused)
{
int err = 0;
switch (what) {
case MOD_LOAD:
printf("hello: Load\n");
break;
case MOD_UNLOAD:
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);
一番下のほうで呼んでる DEV_MODULE
マクロと MODULE_VERSION
マクロでそれぞれモジュールとして登録したり、モジュールのバージョンを定義したりする。くわしくは man を見れば一発だけど、ざっくりしすぎた解説はコメントに書いといた。
で、DEV_MODULE
の第2引数で指定した、hello_load って関数でロード、アンロード時の挙動を書く。
1つめと3つめは今回使わないので __unused
をつけてる。2つめの int what
に入る値はコメントの通りで、
- ロードされたときは MOD_LOAD が呼ばれる
- アンロードされたときは MOD_QUIESCE がまず呼ばれて、問題なければ MOD_UNLOAD が呼ばれる
といった感じみたい。今回は printf
で dmesg に今後 open, close, read, write, ioctl なんかができるように拡張していくと、このなかで make_dev
とか destroy_dev
を呼ぶことになる。
こいつを動かすには、
# cd sys/modules/hello
# make
# kldload ./hello.ko
# kldunload ./hello.ko
これでビルドしてロードしてアンロードされる。kldload は ./
を付けとかないとデフォルトの検索パス内で探しちゃうので、変なとこで作業してる人は特に必須。
これをやったあと dmesg
すると、以下3行が表示されてるはず。
hello: Load
hello: Quisce
hello: Unload
ということで次回は open, close, read をサポートしてみる。