カーネルモジュールのサンプルからcallout(9)の機能の使い方を見てみる

[NetBSD Advent Calendar 2023](https://qiita.com/advent-calendar/2023/netbsd) 11日目の記事です。今日はNetBSDカーネルモジュールのサンプルからカーネルの機能を見てみようと思います。

## カーネルモジュールのサンプル

NetBSDカーネルモジュールのサンプルは、 `/usr/src/sys/modules/examples` に置かれています。いくつかサンプルがありますが、今回は `executor` を見てみます。

## executorカーネルモジュール

まずはソースコードを見てみます。カーネルモジュールなので、ロード時の処理から追いかけて行くのが良さそうです。

```c
 90 static int
 91 executor_modcmd(modcmd_t cmd, void *arg)
 92 {
 93     switch(cmd) {
 94     case MODULE_CMD_INIT:
 95         printf("executor module inserted\n");
 96         callout_init(&sc, 0);
 97         callout_reset(&sc, mstohz(1000), callout_example, NULL);
 98         break;
 99     case MODULE_CMD_FINI:
100         printf("executor module unloaded\n");
101         callout_stop(&sc);
102         callout_destroy(&sc);
103         break;
104     default:
105         return ENOTTY;
106     }
107     return 0;
108 }
```

カーネルモジュールがロードされると、94行目の `MODULE_CMD_INIT` の処理に入ってきます。 `callout_init()` で(おそらく)"callout"という機能の初期化を行っているようです。

[callout_init(9)](https://man.netbsd.org/callout.9)を見ると、"The callout facility provides a mechanism to execute a function at a given time."と説明されており、calloutという仕組み(facility)で指定した時間に関数を実行する機能が提供されているようです。

次に `callout_reset()` を見てみます。以下のような形で関数が呼ばれており、指定した時間(ミリ秒をhzに変換している)の経過後に `callout_example()` を呼び出させるような指定になっています。この使い方を見ると、JavaScriptの `setTimeout()` に似ているように思えます。

```c
callout_reset(&sc, mstohz(1000), callout_example, NULL);
```

さらにコードを読み勧めて `callout_example()` を見てみます。 `printf()` でメッセージを表示して `callout_schedule()` を呼んでいます。[callout(9)](https://man.netbsd.org/callout.9)の説明を見ると、"callout_schedule() is slightly more efficient than using callout_reset()."とあるので、 `callout_exeample()` の呼び出しを再度スケジューリングしているようです。

```c
 75 static void                                                                 76 callout_example(void *arg) {                                                77     RUN_ONCE(&ctl, runonce_example);                                        78     executor_count++;
 79     printf("Callout %d\n", executor_count);
 80     callout_schedule(&sc, mstohz(1000));
 81 }
```

## 実際に挙動を確認してみる

ソースコードから動作内容がなんとなく見えてきたので実際に動かして確認してみます。まずはカーネルモジュールのビルドです(単に `make` を走らせるだけです)。

```sh
# make
#   compile  executor/executor.o
/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-gcc -O2 -g   -std=gnu99    -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wno-sign-compare  -Wsystem-headers   -Wno-traditional   -Wa,--fatal-warnings  -Wreturn-type -Wswitch -Wshadow -Wcast-qual -Wwrite-strings -Wextra -Wno-unused-parameter -Wno-sign-compare -Wold-style-definition -Wsign-compare -Wformat=2  -Wno-format-zero-length  -Werror   -ffreestanding  -fno-strict-aliasing -Wno-pointer-sign -mno-red-zone -mno-mmx -mno-sse -mno-avx -msoft-float -mcmodel=kernel -fno-omit-frame-pointer   -I/usr/src/common/include --sysroot=/ -I/usr/src/common/include  -nostdinc -I. -I/usr/src/sys/modules/examples/executor -isystem /usr/src/sys -isystem /usr/src/sys/arch -isystem /usr/src/sys/../common/include -D_KERNEL -D_LKM -D_MODULE -DSYSCTL_INCLUDE_DESCR -c    executor.c
/usr/src/tooldir.NetBSD-9.3-amd64/bin/nbctfconvert -L VERSION executor.o
#      link  executor/executor.kmod
/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-gcc  --sysroot=/ -Wl,--warn-shared-textrel -Wl,-z,relro -nostdlib -r -Wl,-T,/usr/src/sys/../sys/modules/xldscripts/kmodule,-d  -Wl,-Map=executor.kmod.map  -o executor.kmod executor.o
/usr/src/tooldir.NetBSD-9.3-amd64/bin/nbctfmerge -t -L VERSION -o executor.kmod executor.o
#
# file executor.kmod
executor.kmod: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
```

カーネルモジュールをロードします。

```sh
# modload ./executor.kmod
```

![img.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61137/c2ccdf99-9434-7aed-b9f9-01a4b1d76fce.png)

ソースコードの字面から予想していた通り、 `callout_example()` が一定時間毎に呼び出されています。

## まとめ

カーネルモジュールのサンプルから、calloutの機能を概観してみました。カーネルが提供する機能を学ぶにはカーネルモジュールの形で試してみるのが良さそうです。