NetBSD Advent Calendar 2018 3日目の記事です。
今日はNetBSDのカーネルモジュールの話をしようと思います。
カーネルモジュール
カーネルモジュールについては module(7)
の説明を見るのが手っ取り早いです。カーネルモジュールはシステム管理者が稼働中のシステムに対し、動的に追加・削除することができる機能で、開発者が再起動を必要としない形でカーネル機能の追加が可能になります。
DESCRIPTION
Kernel modules allow the system administrator to dynamically add and
remove functionality from a running system. This also helps software
developers add new parts of the kernel without constantly rebooting to
test their changes.
カーネルモジュールのサンプル
NetBSDソースツリーの sys/modules/examples
以下にカーネルモジュールのサンプルが用意されています。
今回はこのサンプルを元にカーネルモジュールを試してみましょう。
# cd /usr/src/sys/modules/examples
# ls -F
CVS/ Makefile.inc hello/ luareadhappy/ properties/
Makefile README luahello/ ping/ readhappy/
カーネルモジュールをビルドする環境を作る
カーネルモジュールのサンプルコードは、ビルドツールチェインが生成したパスにgccがあることを想定したMakefileになっています。
そのため、カーネルモジュールをビルドするには build.sh
でセルフホスト用のツールチェインを構築してしまうのが早道です。
(前提として、 /usr/src
にカーネルソースが展開されているものとします)
以下の手順で、x86_64向けのツールチェインを構築します。
$ sudo bash
# cd /usr/src
# ./build.sh -m amd64 -a x86_64 -T /usr/src/tooldir.NetBSD-8.0-amd64 -r -o -U tools
モジュールのサンプルを触ってみる
さっそくカーネルモジュールのサンプルを触ってみます。
サンプルには hello
と readhappy
という小さな2つのモジュールが用意されているので、それぞれを見てみましょう。
helloモジュール
まずは hello
モジュールです。コメントを除くと30行程度のソースコードです。
# cd hello
# ls -F
CVS/ Makefile hello.c
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hello.c,v 1.1 2015/05/13 07:07:36 pgoyette Exp $");
#include <sys/param.h>
#include <sys/module.h>
/*
* Last parameter of MODULE macro is a list of names (as string; names are
* separated by commas) of dependencies. If module has no dependencies,
* then NULL should be passed.
*/
MODULE(MODULE_CLASS_MISC, hello, NULL);
static int
hello_modcmd(modcmd_t cmd, void *arg __unused)
{
switch (cmd) {
case MODULE_CMD_INIT:
printf("Example module loaded.\n");
break;
case MODULE_CMD_FINI:
printf("Example module unloaded.\n");
break;
case MODULE_CMD_STAT:
printf("Example module status queried.\n");
break;
default:
return ENOTTY;
}
return 0;
}
make
コマンドでそのままビルドできます。
# make
# ls -F
CVS/ amd64@ hello.kmod hello.o machine@
Makefile hello.c hello.kmod.map i386@ x86@
カーネルモジュールのロード
カーネルモジュールのロードは modload
コマンドで行います。
# cd /usr/src/sys/modules/examples/hello/
#
# # まだhelloモジュールはロードされていない
# modstat | grep hello | wc -l
0
#
# # modloadコマンドでロード。
# # 「./」で相対パス指定にしないとロードエラーになるので注意。
# modload ./hello.kmod
#
# # helloモジュールがロードされていることを確認する。
# modstat | egrep 'NAME|hello'
NAME CLASS SOURCE FLAG REFS SIZE REQUIRES
hello misc filesys - 0 77 -
ソースコードと照らし合わせる形で、カーネルモジュールのロード・アンロードがどう振る舞うのかを見てみます。
static int
hello_modcmd(modcmd_t cmd, void *arg __unused)
{
switch (cmd) {
case MODULE_CMD_INIT:
printf("Example module loaded.\n");
break;
モジュールをロードすると...。
# modunload hello
対応する処理として、 hello_modcmd()
関数の MODULE_CMD_INIT
が処理されます。
static int
hello_modcmd(modcmd_t cmd, void *arg __unused)
{
switch (cmd) {
...中略...
case MODULE_CMD_INIT:
printf("Example module loaded.\n");
break;
modunload
の場合は、同関数の MODULE_CMD_FINI
が処理されます。
case MODULE_CMD_FINI:
printf("Example module unloaded.\n");
break;
readhappyモジュール
次に readhappy
モジュールを見てみます。こちらもシンプルなコードのようですね。
# cd /usr/src/sys/modules/examples/readhappy
# ls -F
CVS/ Makefile readhappy.c
こちらも make
コマンドでビルドし、 modload
コマンドでロードします。
# modload ./readhappy.kmod
# modstat | egrep '^NAME|happy'
NAME CLASS SOURCE FLAG REFS SIZE REQUIRES
happy misc filesys - 0 377 -
readhappy.c
のソースコードコメントを見ると以下の記述があるので、それに従ってmknodコマンドを実行します。
* Create a device /dev/happy from which you can read sequential
* happy numbers.
*
* To use this device you need to do:
* mknod /dev/happy c 210 0
# mknod /dev/happy c 210 0
# ls -l /dev/happy
crw-r--r-- 1 root wheel 210, 0 Dec 3 18:58 /dev/happy
# file /dev/happy
/dev/happy: character special (210/0)
ソースコードを見ると、 readhappy.c
の struct cdevsw.d_read
に read(2)
に対応する関数が紐付けられています。そこで、紐づけられている happy_read()
関数に適当な printf()
を挿入して動作を見てみましょう。
static struct cdevsw happy_cdevsw = {
...中略...
.d_read = happy_read,
...中略...
happy_read(dev_t self __unused, struct uio *uio, int flags __unused)
{
...中略...
/* Send it to User-Space */
int e;
printf("-=> (^_^)/ %s", line); // 確認用に追加した行。
if ((e = uiomove(line, len, uio)))
return e;
ユーザランド側で簡単なサンプルコードを用意し、 /dev/happy
からデータを読みだしてみます。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char buf[BUFSIZ];
int fd, i;
fd = open("/dev/happy", O_RDONLY);
if (fd == -1) {
fprintf(stderr, "open() failed.\n");
exit(-1);
}
for (i = 0; i < argc; i++) {
read(fd, buf, BUFSIZ);
printf("%s", buf);
}
close(fd);
return 0;
}
データを read()
する度に printf()
で追加した文言がカーネルメッセージとして出力されていることと、カーネル内の値(データ)が read()
でユーザランド側でも取得できることが分かります。
まとめ
NetBSDのカーネルモジュールについて、提供されているサンプルを元にビルドやロード方法を紹介しました。
特に readhappy.c
はシンプルでありながら、ユーザランドへのデータの渡し方等、ツボを押さえたサンプルになっています。
カーネルモジュールのサンプルには、他にもLuaで記述されたカーネルモジュールも含まれているので、こちらも後日紹介できればと思います。