callout(9)のcallout_init()の実装を調べてみる

[NetBSD Advent Calendar 2023](https://qiita.com/advent-calendar/2023/netbsd) 12日目の記事です。今日は[callout(9)](https://man.netbsd.org/callout.9)の機能をもう少し深堀りしてみます。

## callout_init()関数

`executor.c` では、以下のような形で `callout_init()` が呼ばれています。

```c
 51 static callout_t sc;
...
 96         callout_init(&sc, 0);
```

`callout_init()` は `/usr/src/sys/kern/kern_timeout.c` で以下のような関数定義になっています。

```c
277 void
278 callout_init(callout_t *cs, u_int flags)
```

`executor.c` の例では第2引数の `u_int flags` は `0` が渡されているため、今回は第1引数の `callout_t *cs` のみを見れば良さそうです。 `callout_t` は `/usr/src/sys/sys/callout.h` で以下のように定義されています。一見すると単なる `void*` 型の配列なので意味がわからず面食らってしまいますが、コメントを見ると[callout(9)](https://man.netbsd.org/callout.9)の機能自体はカーネル側で提供するものの、パフォーマンス上の理由から、使用するデータ領域はcalloutを呼び出す側で確保する使い方であるため、このような定義になっているようです。

```c
 38 /*
 39  * The callout implementation is private to kern_timeout.c yet uses
 40  * caller-supplied storage, as lightweight callout operations are
 41  * critical to system performance.
 42  *
 43  * The size of callout_t must remain constant in order to ensure ABI
 44  * compatibility for kernel modules: it may become smaller, but must
 45  * not grow.  If more space is required, rearrange the members of
 46  * callout_impl_t.
 47  */
 48 typedef struct callout {
 49     void    *_c_store[10];
 50 } callout_t;
```

`callout_t` がどのようなデータ型にマップされるかは、 `callout_init()` を見るのが良さそうです。実際は `callout_impl_t` 型にキャストされます。

```c
277 void
278 callout_init(callout_t *cs, u_int flags)
279 {
280     callout_impl_t *c = (callout_impl_t *)cs;
281     struct callout_cpu *cc;
```

`callout_impl_t` 型は `/usr/src/sys/sys/callout.h` で以下のように定義されています。各メンバ変数の役割を一気に理解するのは大変なので、まずは `void (*c_func)(void *)` と `int c_time` と `struct callout_cpu * volatile c_cpu` を見てみます。それぞれ[callout(9)](https://man.netbsd.org/callout.9)から呼び出してほしい関数と関数が発火するまでの時間となっています。

```c
 91 typedef struct callout_impl {
 92     struct callout_circq c_list;        /* linkage on queue */
 93     void    (*c_func)(void *);      /* function to call */
 94     void    *c_arg;             /* function argument */
 95     struct callout_cpu * volatile c_cpu;    /* associated CPU */
 96     int c_time;             /* when callout fires */
 97     u_int   c_flags;            /* state of this entry */
 98     u_int   c_magic;            /* magic number */
 99 } callout_impl_t;
100 #define CALLOUT_MAGIC       0x11deeba1
```

ここまでを踏まえると、 `callout_init()` の中身でポイントとなる箇所が見えてきます。 `callout_impl_t` の `c_func` に `NULL` を代入してcalloutされる関数が存在していない状態としておき、 `c_cpu` にcalloutと紐付けるCPUを設定します。
(calloutの初期化処理なのに `c_time` に `0` とか入れておかなくて大丈夫なの？という疑問はありますが...)

```c
277 void
278 callout_init(callout_t *cs, u_int flags)
279 {
280     callout_impl_t *c = (callout_impl_t *)cs;
281     struct callout_cpu *cc;
282
283     KASSERT((flags & ~CALLOUT_FLAGMASK) == 0);
284
285     cc = curcpu()->ci_data.cpu_callout;
286     c->c_func = NULL;
287     c->c_magic = CALLOUT_MAGIC;
288     if (__predict_true((flags & CALLOUT_MPSAFE) != 0 && cc != NULL)) {
289         c->c_flags = flags;
290         c->c_cpu = cc;
291         return;
292     }
293     c->c_flags = flags | CALLOUT_BOUND;
294     c->c_cpu = &callout_cpu0;
295 }
```

これでとりあえず、 `callout_init()` で行われる主要な処理について把握できました。記事的に長くなってので、実際に[callout(9)](https://man.netbsd.org/callout.9)の設定を行う部分の実装については日を改めて解説したいと思います。

## まとめ

[callout(9)](https://man.netbsd.org/callout.9)の初期化を行う `callout_init()` の関数の実装について紹介しました。カーネルモジュールサンプルの `executor.c` から深掘りしての解説になっていますが、カーネルが提供する機能を理解するための足がかりとしては有効そうです。