NetBSDカーネルモジュールをロードした時にsysctl変数を生やす(？)方法

[NetBSD Advent Calendar](https://qiita.com/advent-calendar/2018/netbsd) 12日目の記事です。

[昨日の記事](https://qiita.com/furandon_pig/items/4caad87eb33ff3c979c3)でカーネルモジュールに `ioctl()` を受け取れるようにする方法を紹介しました。
ユーザランドからカーネルに対して値を取得・設定する方法には `ioctl()` だけでなく、 `sysctl` コマンドを使用する方法もあります。そこで今日の記事では、NetBSDのカーネルモジュールで `sysctl` を使用できるようにする手順を紹介しようと思います。

## sysctlとは

[sysctl(8)](http://netbsd.gw.com/cgi-bin/man-cgi?sysctl+8+NetBSD-current)は、manページにもあるようにカーネルの状態を取得・設定するコマンドです。

```
NAME
     sysctl ? get or set kernel state

SYNOPSIS
     sysctl [-bdehiNnoRTtqx] [-B bufsize] [-f filename] name[=value] ...
     sysctl [-bdehNnoRTtqx] [-B bufsize] -a

DESCRIPTION
     The sysctl utility retrieves kernel state and allows processes with
     appropriate privilege to set kernel state.  The state to be retrieved or
     set is described using a “Management Information Base” (“MIB”) style
     name, described as a dotted set of components.
```

例えば以下のような感じでカーネル内の設定値を取得できます。

```sh 
$ sysctl net.inet.ip.forwarding
net.inet.ip.forwarding: 0
```

## カーネルモジュールにsysctlを実装する

さっそくカーネルモジュールにsysctlを実装してみます。
今回は `kern.hello.maxcount` というsysctl名をカーネルモジュール側に用意し、ユーザランドから[sysctl(8)](http://netbsd.gw.com/cgi-bin/man-cgi?sysctl+8+NetBSD-current)コマンドで値の取得・設定を行えるようにしてみます。

### 既存のsysctl実装箇所を参考にする

`ioctl` の実装と同じく、 `sysctl` についてもNetBSDカーネルソースから既存の実装箇所を参考にするのが良さそうです。
ソースツリーを探してみると `/usr/src/sys/modules/lua/lua.c` の実装が参考になりそうです。

#### sysctl変数を追加している箇所

`lua_attach()` でsysctl変数を生やして(？)います。 `kern/kern_sysctl.c:sysctl_createev()` を使用することで、sysctlの変数を追加しています。

```c 
/usr/src/sys/modules/lua/lua.c:
125 static void
126 lua_attach(device_t parent, device_t self, void *aux)
127 {
...
129         const struct sysctlnode *node;
...
146         /* Sysctl to provide some control over behaviour */
147         sysctl_createv(&sc->sc_log, 0, NULL, &node,
148             CTLFLAG_OWNDESC,
149             CTLTYPE_NODE, "lua",
150             SYSCTL_DESCR("Lua options"),
151             NULL, 0, NULL, 0,
152             CTL_KERN, CTL_CREATE, CTL_EOL);
...
191         sysctl_createv(&sc->sc_log, 0, &node, NULL,
192             CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
193             CTLTYPE_INT, "maxcount",
194             SYSCTL_DESCR("Limit maximum instruction count"),
195             NULL, 0, &lua_max_instr, 0,
196             CTL_CREATE, CTL_EOL);
```

`kern.lua.maxcount` はstatic変数を定義し、それを `sysctl_createev()` の引数に指定することでsysctl変数と紐づけるようです。

```c
/usr/src/sys/modules/modules/lua/lua.c:
 75 static int      lua_max_instr;
 ...
125 static void
126 lua_attach(device_t parent, device_t self, void *aux)
127 {
...
191         sysctl_createv(&sc->sc_log, 0, &node, NULL,
192             CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
193             CTLTYPE_INT, "maxcount",
194             SYSCTL_DESCR("Limit maximum instruction count"),
195             NULL, 0, &lua_max_instr, 0,
196             CTL_CREATE, CTL_EOL);
```

`lua_attach()` は `CFATTACH_DECL_NEW()` で生成された箇所から呼ばれるようです。

```c 
/usr/src/sys/modules/lua/lua.c:
 89 CFATTACH_DECL_NEW(lua, sizeof(struct lua_softc),
 90         lua_match, lua_attach, lua_detach, NULL);
```

`CFATTACH_DECL_NEW()` マクロは `/usr/src/sys/sys/device.h` で定義されており、 `struct cfattach *_ca` が生成されます。
この例では `struct cfattach lua_ca.ca_attach = lua_attach` となるようですね。

```c
/usr/src/sys/sys/device.h:
324 #define CFATTACH_DECL3_NEW(name, ddsize, matfn, attfn, detfn, actfn, \
325         rescanfn, chdetfn, __flags) \
326 struct cfattach __CONCAT(name,_ca) = {                                  \
327         .ca_name                = ___STRING(name),                      \
328         .ca_devsize             = ddsize,                               \
329         .ca_flags               = (__flags) | DVF_PRIV_ALLOC,           \
330         .ca_match               = matfn,                                \
331         .ca_attach              = attfn,                                \
332         .ca_detach              = detfn,                                \
333         .ca_activate            = actfn,                                \
334         .ca_rescan              = rescanfn,                             \
335         .ca_childdetached       = chdetfn,                              \
336 }
337
338 #define CFATTACH_DECL2_NEW(name, ddsize, matfn, attfn, detfn, actfn,    \
339         rescanfn, chdetfn)                                              \
340         CFATTACH_DECL3_NEW(name, ddsize, matfn, attfn, detfn, actfn,    \
341             rescanfn, chdetfn, 0)
342
343 #define CFATTACH_DECL_NEW(name, ddsize, matfn, attfn, detfn, actfn)     \
344         CFATTACH_DECL2_NEW(name, ddsize, matfn, attfn, detfn, actfn, NULL, NULL)
```

そして、 `struct cfattach lua_ca` は `lua_modcmd()` から `MODULE_CMD_INIT` (カーネルモジュールのロード)処理の時に参照され、そこから `lua_attach()` が呼ばれるという流れになっているようです。

```c 
/usr/src/sys/modules/lua/lua.c:
824 static int
825 lua_modcmd(modcmd_t cmd, void *opaque)
826 {
...
833         switch (cmd) {
834         case MODULE_CMD_INIT:
835 #ifdef _MODULE
836                 error = config_cfdriver_attach(&lua_cd);
...
840                 error = config_cfattach_attach(lua_cd.cd_name,
841                     &lua_ca);
...
848                 error = config_cfdata_attach(lua_cfdata, 1);
...
857                 error = devsw_attach(lua_cd.cd_name, NULL, &bmajor,
858                     &lua_cdevsw, &cmajor);
...
866                 config_attach_pseudo(lua_cfdata);
869         case MODULE_CMD_FINI:
870 #ifdef _MODULE
871                 error = config_cfdata_detach(lua_cfdata);
...
875                 config_cfattach_detach(lua_cd.cd_name, &lua_ca);
876                 config_cfdriver_detach(&lua_cd);
877                 devsw_detach(NULL, &lua_cdevsw);
878 #endif
```

ここまででソースコードから見えてくる手順は以下ですね。モジュールのロード時にsysctlを生やせば良いという話ではなく、別途 `struct cfattach *_ca` にアタッチ処理を行う関数を設定し、 `config_cf*_attach()` を呼び出す必要があるということのようです。

 * モジュールのロード(MODULE_CMD_INIT)時に実行する
   * config_cfdriver_attach()
   * config_cfattach_attach()
   * config_cfdata_attach()
   * devsw_attach()
   * config_attach_pseudo()
 * モジュールのアンロード(MODULE_CMD_FINI)時に実行する
   * config_cfdata_detach()
   * config_cfattach_detach()
   * config_cfdriver_detach()
   * devsw_detach()

## カーネルモジュールサンプルにsysctlを実装する

### サンプルのひな型を用意する

[昨日の記事](https://qiita.com/furandon_pig/items/4caad87eb33ff3c979c3)と同じく、NetBSDカーネルソースツリーの `modules/examples` ディレクトリのコードをひな型にします。

```
# /usr/src/sys/modules/examples
# cp -r hello/ sysctl_sample
```

先述した `/usr/src/sys/modules/lua/lua.c` の内容を元にサンプルを実装します。サンプルコードは以下のGistに置いてあります。

 * [netbsd_sysctl_sample.c ](https://gist.github.com/furandon-pig/c35bd3dd418ca0467197d3ca9ed914b8)

ビルドしてカーネルモジュールを作成します。

```
# cd /usr/src/sys/modules/examples/sysctl_sample
# make
#   compile  sysctl_sample/hello.o
/usr/src/tooldir.NetBSD-8.0-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 -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/sysctl_sample -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    hello.c
/usr/src/tooldir.NetBSD-8.0-amd64/bin/nbctfconvert -L VERSION hello.o
#      link  sysctl_sample/hello.kmod
/usr/src/tooldir.NetBSD-8.0-amd64/bin/x86_64--netbsd-gcc  --sysroot=/ -Wl,-z,relro -Wl,--warn-shared-textrel -nostdlib -r -Wl,-T,/usr/src/sys/../sys/modules/xldscripts/kmodule,-d  -Wl,-Map=hello.kmod.map  -o hello.kmod hello.o
/usr/src/tooldir.NetBSD-8.0-amd64/bin/nbctfmerge -t -L VERSION -o hello.kmod hello.o
#
# file hello.kmod
hello.kmod: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
```

## カーネルモジュールを動かしてみる

ビルドしたカーネルモジュールをロードしてみます。

```
# cd /usr/src/sys/modules/examples/sysctl_sample
# modload ./hello.kmod
```

無事にロードできたようです。
<img width="361" alt="SnapCrab_netbsd8 (base) [実行中] - Oracle VM VirtualBox_2018-12-13_3-40-8_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/61137/ebec3aa9-e5f3-4df4-bb5d-b9481f54017a.png">


`sysctl` コマンドを実行してみると、 `kern.hello.maxcount` という変数が存在しています。

```
# sysctl -a | grep kern.hello
kern.hello.maxcount = 0
```

`sysctl -w` で値を書き換えてみます。

```
# sysctl -w kern.hello.maxcount=7
kern.hello.maxcount: 0 -> 7
#
# sysctl kern.hello.maxcount
kern.hello.maxcount = 7
```

モジュールをアンロードするとsysctl変数が消えることも確認できました。これでカーネルモジュールへのsysctl変数追加と一連の動作確認まで無事に完了しました。

<img width="361" alt="SnapCrab_netbsd8 (base) [実行中] - Oracle VM VirtualBox_2018-12-13_3-44-10_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/61137/3a1d2c9d-87cc-e066-bf1d-029ee047d574.png">

## まとめ

NetBSDカーネルモジュールにsysctl変数を追加する手順を紹介しました。ユーザランドからsysctlで値を設定できるようにしておくと、カーネルモジュールの設定操作だけでなく、開発中のデバッグ用途にも応用できそうです。
[カーネルモジュールでioctl](https://qiita.com/furandon_pig/items/4caad87eb33ff3c979c3)と同じく、こちらもサンプルコードをひな型的な形で用意しておくとちょっとした実験等で役立ちそうです。