NetBSD Advent Calendar 12日目の記事です。
昨日の記事でカーネルモジュールに ioctl()
を受け取れるようにする方法を紹介しました。
ユーザランドからカーネルに対して値を取得・設定する方法には ioctl()
だけでなく、 sysctl
コマンドを使用する方法もあります。そこで今日の記事では、NetBSDのカーネルモジュールで sysctl
を使用できるようにする手順を紹介しようと思います。
sysctlとは
sysctl(8)は、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.
例えば以下のような感じでカーネル内の設定値を取得できます。
$ sysctl net.inet.ip.forwarding
net.inet.ip.forwarding: 0
カーネルモジュールにsysctlを実装する
さっそくカーネルモジュールにsysctlを実装してみます。
今回は kern.hello.maxcount
というsysctl名をカーネルモジュール側に用意し、ユーザランドからsysctl(8)コマンドで値の取得・設定を行えるようにしてみます。
既存のsysctl実装箇所を参考にする
ioctl
の実装と同じく、 sysctl
についてもNetBSDカーネルソースから既存の実装箇所を参考にするのが良さそうです。
ソースツリーを探してみると /usr/src/sys/modules/lua/lua.c
の実装が参考になりそうです。
sysctl変数を追加している箇所
lua_attach()
でsysctl変数を生やして(?)います。 kern/kern_sysctl.c:sysctl_createev()
を使用することで、sysctlの変数を追加しています。
/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変数と紐づけるようです。
/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()
で生成された箇所から呼ばれるようです。
/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
となるようですね。
/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()
が呼ばれるという流れになっているようです。
/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を実装する
サンプルのひな型を用意する
昨日の記事と同じく、NetBSDカーネルソースツリーの modules/examples
ディレクトリのコードをひな型にします。
# /usr/src/sys/modules/examples
# cp -r hello/ sysctl_sample
先述した /usr/src/sys/modules/lua/lua.c
の内容を元にサンプルを実装します。サンプルコードは以下のGistに置いてあります。
ビルドしてカーネルモジュールを作成します。
# 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
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変数追加と一連の動作確認まで無事に完了しました。
まとめ
NetBSDカーネルモジュールにsysctl変数を追加する手順を紹介しました。ユーザランドからsysctlで値を設定できるようにしておくと、カーネルモジュールの設定操作だけでなく、開発中のデバッグ用途にも応用できそうです。
カーネルモジュールでioctlと同じく、こちらもサンプルコードをひな型的な形で用意しておくとちょっとした実験等で役立ちそうです。