NetBSD Advent Calendar 2015もいよいよ今日で最終日です(正確には最終日ギリギリでありますが...)。最終日はNetBSD-7.0から標準搭載された、Luaカーネルモジュールをネタに、LuaスクリプトからNetBSDカーネル内の関数を呼び出してみる話をしたいと思います。
NetBSDのLuaカーネルモジュール
Announcing NetBSD 7.0を見ると、NetBSD-7.0における注目機能の一つとして"Lua kernel scripting."が挙げられています。機能としては、intro(9lua)で説明されているように、Luaスクリプトのカーネルバインディングを提供する機能です。これによりカーネル内でLuaスクリプトの実行とカーネル内関数をLuaから利用することが可能になります。
Luaカーネルモジュールの現状
Luaカーネルモジュールについては、私の方でもNetBSD-7.0_RC3で試してみたり、Luaカーネルモジュールで遊ぶ会という謎の会を開催してみたりするなど個人的に注目している機能です。
NetBSD-7.0_RC3で試したときは、systm.print()等のコンソール出力やpfm.system_shutdown()等のパワーマネジメント系機能、systm.panic()によるパニック機能(!)などが提供されるも、write(2)やread(2)といったシステムコールに相当するカーネル内関数とLuaスクリプトとのバインディングが行われていない状態でした。
NetBSD-7.0でも状況はあまり変わっておらず、現状では「Luaカーネルモジュールは面白そうだけど、Luaから呼べるカーネル内の関数が少ないから、実用にはまだ早いかな」というのが正直なところです。が、「バインディングされている関数が少ないなら追加すればいいじゃなイカ!」というワケで、Luaからカーネル内関数を呼び出すための手順(実質的にはLuaからC言語の関数を呼び出す方法)を調べてみました。
NetBSD-7.0ソースコードの準備
NetBSD-7.0_RC3での準備手順でもビルド環境の構築手順を紹介していましたが、NetBSD-7.0版での手順を再掲します。
今回の開発環境
Luaカーネルモジュールというだけあって、エラーやバグの内容によってはOSごとクラッシュする可能性があるので、今回は以下のように開発用とテスト用のマシンを分けておき、開発用マシンでビルドしたバイナリをNFS経由でテスト用マシンから参照する形にします。
+----------+ +--------+
| テスト用 | | 開発用 |
| NetBSD | | NetBSD |
+----+-----+ +---+----+
|172.16.10.48 |172.16.10.50
| |
----+------------------+---- 172.16.10.0/24
NFSサーバ・クライアントの設定
NFSサーバ(開発用NetBSD)
/etc/rc.confに起動時にNFSサーバとして動作させるための設定を追加します。
rpcbind=YES
mountd=YES
nfs_server=YES
lockd=YES
statd=YES
/etc/exportに以下を追記します。
/usr/src -maproot=root:wheel -network 172.16.10.0 -mask 255.255.255.0
/etc/exportsの設定を再読み込みします。蛇足ですがLinux系で同じことを行うのは"exportfs -r"になります(毎回どっちだったか思い出せず混乱する...)。
$ sudo kill -HUP `cat /var/run/mountd.pid`
NFSクライアント(テスト用NetBSD)
/etc/rc.confにNFSクライアントの用の設定を記述します。
rpcbind=YES
nfs_client=YES
lockd=YES
statd=YES
カーネルソースコードの取得
カーネルモジュールへの機能追加なので、以下のURLからNetBSD-7.0のソースコードを取得します。
$ curl -O http://ftp.jaist.ac.jp/pub/NetBSD/NetBSD-7.0/source/sets/gnusrc.tgz
$ curl -O http://ftp.jaist.ac.jp/pub/NetBSD/NetBSD-7.0/source/sets/sharesrc.tgz
$ curl -O http://ftp.jaist.ac.jp/pub/NetBSD/NetBSD-7.0/source/sets/src.tgz
$ curl -O http://ftp.jaist.ac.jp/pub/NetBSD/NetBSD-7.0/source/sets/syssrc.tgz
カーネルソースコードの展開
開発用マシンには一般ユーザを作成しているので、今回は一般ユーザで/usr/src以下を読み書きできる形で作業を進めます。
$ sudo mkdir /usr/src
$ sudo chown fpig:wheel /usr/src/
$ ls -lha /usr/src/ | grep ' \./' # ownerが変更されているか確認する
drwxr-xr-x 2 fpig wheel 512B Dec 14 20:06 ./
上記の設定と確認が完了したらNetBSD-7.0のソースコードを展開します。
$ for i in `/bin/ls *.tgz`;do tar zxvf $i -C / ; done
ビルドツールの構築
$ cd /usr/src
$ sudo ./build.sh -m `uname -m` -a `uname -p` -T /usr/cross/NetBSD-`uname -r`/`uname -m` -r -o -U tools
===> build.sh command: ./build.sh -m amd64 -a x86_64 -T /usr/cross/NetBSD-7.0/amd64 -r -o -U tools
===> build.sh started: Mon Dec 14 20:30:28 JST 2015
===> NetBSD version: 7.0
===> MACHINE: amd64
===> MACHINE_ARCH: x86_64
===> Build platform: NetBSD 7.0 amd64
===> HOST_SH: /bin/sh
===> No $TOOLDIR/bin/nbmake, needs building.
===> Bootstrapping nbmake
...中略...
===> build.sh ended: Mon Dec 14 20:48:24 JST 2015
===> Summary of results:
build.sh command: ./build.sh -m amd64 -a x86_64 -T /usr/cross/NetBSD-7.0/amd64 -r -o -U tools
build.sh started: Mon Dec 14 20:30:28 JST 2015
NetBSD version: 7.0
MACHINE: amd64
MACHINE_ARCH: x86_64
Build platform: NetBSD 7.0 amd64
HOST_SH: /bin/sh
No $TOOLDIR/bin/nbmake, needs building.
Bootstrapping nbmake
MAKECONF file: /etc/mk.conf (File not found)
TOOLDIR path: /usr/cross/NetBSD-7.0/amd64
DESTDIR path: /usr/src/destdir.amd64
RELEASEDIR path: /usr/src/releasedir
Removing /usr/cross/NetBSD-7.0/amd64
Removing /usr/src/destdir.amd64
Created /usr/cross/NetBSD-7.0/amd64/bin/nbmake
Updated makewrapper: /usr/cross/NetBSD-7.0/amd64/bin/nbmake-amd64
Tools built to /usr/cross/NetBSD-7.0/amd64
build.sh ended: Mon Dec 14 20:48:24 JST 2015
===> .
real 17m56.355s
user 11m45.163s
sys 3m14.825s
/usr/src以下のMakefileは/usr/cross/NetBSD-7.0/amd64/binにビルドツールがあることを想定した動作になっているので、シンボリックリンクを張っておきます。
(最初からbuild.shが生成するツールをこれに合わせておけば良いだけの話で、こちらの手順ミスで一手間ふえてしまいました...)
$ cd /usr/src/
$ mkdir tooldir.NetBSD-7.0-amd64
$ cd tooldir.NetBSD-7.0-amd64/
$ ln -s /usr/cross/NetBSD-7.0/amd64/bin ./bin
LuaスクリプトからNetBSDカーネル内関数を呼び出す
長かった準備が完了し、やっとLuaスクリプトまわりの修正が行える環境が整いました。さっそくLuaカーネルモジュールに手を入れてみます。
systm.getpid()を実装してみる
まずは簡単なところから、getpid(2)相当の処理を作成してみます。といっても、カーネル内の話なのでグローバル変数curproc->p_pid(struct procのp_pid)を返すだけでOKです。
できればLuaからos.getpid()みたいな呼び出し方だとそれっぽいのですが、sys/modules/luasystm/luasystm.cにsystm.print()などの関数が実装されているので、これに追加する形でgetpid()を実装してみます。
diff --git a/luasystm/luasystm.c b/luasystm/luasystm.c
index 6340a9c..7130757 100644
--- a/luasystm/luasystm.c
+++ b/luasystm/luasystm.c
@@ -37,6 +37,7 @@
#ifdef _MODULE
#include <sys/module.h>
#endif
+#include <sys/proc.h>
#include <sys/systm.h>
#include <lua.h>
@@ -158,6 +159,15 @@ systm_panic(lua_State *L)
/* mutexes */
+/* my sample functions */
+static int
+systm_getpid(lua_State *L)
+{
+ lua_pushinteger(L, curproc->p_pid);
+ lua_setglobal(L, "pid");
+ return 1;
+}
+
static int
luaopen_systm(lua_State *L)
{
@@ -179,6 +189,9 @@ luaopen_systm(lua_State *L)
/* mutexes */
+ /* my sample functions */
+ { "getpid", systm_getpid},
+
{NULL, NULL}
};
ビルドはmakeだけでOKです。
$ make
# compile luasystm/luasystm.o
/usr/src/tooldir.NetBSD-7.0-amd64/bin/x86_64--netbsd-gcc -O2 -std=gnu99 -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wno-sign-compare -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/sys/../external/mit/lua/dist/src -I/usr/src/sys/sys -I/usr/src/common/include -nostdinc -I. -I/usr/src/sys/modules/luasystm -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 luasystm.c
# link luasystm/luasystm.kmod
/usr/src/tooldir.NetBSD-7.0-amd64/bin/x86_64--netbsd-gcc --sysroot=/ -nostdlib -r -Wl,-T,/usr/src/sys/../sys/modules/xldscripts/kmodule,-d -o luasystm.kmod luasystm.o kern_descrip.o
Luaスクリプトは以下になります。systm.getpid()の結果を変数に代入するのではなく、グローバル変数経由での結果参照になります。
-- getpid.lua
local systm = require 'systm'
systm.getpid()
systm.print(pid)
そんでもって、テスト用環境からビルドしたLuaカーネルモジュールをロードし、Luaスクリプトを実行してみます。
# modload ./luasystm.kmod
# luactl create mylua
# luactl load mylua ./getpid.lua
# luactl destroy mylua
# modunload luasystm
# modunload lua
実行結果は以下のようになります。出力が混ざっているものの、PID=2494が表示されています。
systm.kill()を実装してみる
systm.getpid()では単にcurproc->p_pidを返すだけでしたが、今度はNetBSDカーネル内部の関数であるkern/sys_sig.c:sys_kill()を呼んでみます。
例によってsys/modules/luasystm/luasystm.cに追加します。追加内容は以下の通りです。sys_kill()の引数はPIDとシグナル番号なので、分かりやすさという点でサンプルにちょうど良いですね。
加えて、SIGINT,SIGKILLといったシグナル番号の定数を定義し、Lua側からsystm.SIGINTの形で利用できるようにしておきます。
diff --git a/luasystm/luasystm.c b/luasystm/luasystm.c
index 6340a9c..024b541 100644
--- a/luasystm/luasystm.c
+++ b/luasystm/luasystm.c
@@ -37,7 +37,9 @@
#ifdef _MODULE
#include <sys/module.h>
#endif
+#include <sys/syscallargs.h>
#include <sys/systm.h>
+#include <sys/types.h>
#include <lua.h>
#include <lauxlib.h>
@@ -158,6 +160,28 @@ systm_panic(lua_State *L)
/* mutexes */
+/* my sample functions */
+static int
+systm_kill(lua_State *L)
+{
+ const pid_t pid = (pid_t)lua_tointeger(L, 1);
+ const int signum = lua_tointeger(L, 2);
+ register_t retval;
+
+ struct sys_kill_args /* {
+ syscallarg(pid_t) pid;
+ syscallarg(int) signum;
+ } */ sk_args;
+
+ if (pid && signum) {
+ SCARG(&sk_args, pid) = pid;
+ SCARG(&sk_args, signum) = signum;
+ sys_kill(curlwp, &sk_args, &retval);
+ }
+
+ return 0;
+}
+
static int
luaopen_systm(lua_State *L)
{
@@ -179,6 +203,9 @@ luaopen_systm(lua_State *L)
/* mutexes */
+ /* my sample functions */
+ { "kill", systm_kill},
+
{NULL, NULL}
};
@@ -206,6 +233,12 @@ luaopen_systm(lua_State *L)
lua_pushinteger(L, ncpu);
lua_setfield(L, -2, "ncpu");
+ /* signal values */
+ lua_pushinteger(L, 2);
+ lua_setfield(L, -2, "SIGINT");
+ lua_pushinteger(L, 9);
+ lua_setfield(L, -2, "SIGKILL");
+
return 1;
}
makeします。
$ make
# compile luasystm/luasystm.o
/usr/src/tooldir.NetBSD-7.0-amd64/bin/x86_64--netbsd-gcc -O2 -std=gnu99 -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wno-sign-compare -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/sys/../external/mit/lua/dist/src -I/usr/src/sys/sys -I/usr/src/common/include -nostdinc -I. -I/usr/src/sys/modules/luasystm -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 luasystm.c
# link luasystm/luasystm.kmod
/usr/src/tooldir.NetBSD-7.0-amd64/bin/x86_64--netbsd-gcc --sysroot=/ -nostdlib -r -Wl,-T,/usr/src/sys/../sys/modules/xldscripts/kmodule,-d -o luasystm.kmod luasystm.o kern_descrip.o
シグナルを送信してみる
ユーザランド側で以下のようなサンプルプログラムを走らせ、シグナルが送られてきたことを確認してみます。
#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
fprintf(stderr, "got signal.\n");
}
int main(int argc, char *argv[])
{
signal(SIGINT, handler);
getchar();
return 0;
}
コンパイルしてプログラムを走らせておきます。
# gcc -Wall -Werror -g -o sample sample.c
# ./sample
サンプルプログラムのPIDを確認しつつ、
# ps ax | grep sample
2565 ttyp0 S+ 0:00.00 ./sample
以下のLuaスクリプトを用意します。target_pidが先ほど確認したプロセスIDですね。
local systm = require 'systm'
target_pid = 2565
systm.kill(target_pid, systm.SIGINT)
Luaスクリプトを実行します。
# luactl load mylua ./kill.lua
ユーザランド側のプログラムでシグナルが受け取れました。
# ./sample
got signal.
ちなみにSIGKILLも送信できます。
local systm = require 'systm'
target_pid = 2565
systm.kill(target_pid, systm.SIGKILL)
無事(?)にプロセスが強制終了しました。
# ./sample
Killed
#
まとめ
Luaカーネルモジュールでカーネル内関数を呼び出す方法を調べてみました。getpid(2)やkill(2)相当の関数は比較的簡単に実装できるようです。バインディングされているカーネル関数が少ない!という点がLuaカーネルモジュール普及の障害な気がしています。今回の記事をもとに、少なくとも頻繁に利用されるシステムコール相当の関数がLuaから呼べるようになればと考えています。
(実は手元の環境ではsys_writeをLuaから呼べるようにしているのですが、まだちゃんと動いていない...)
おわりに
NetBSD Advent Calendar 2015も無事に完走することができました。これもひとえに皆様にご協力いただいたおかげです。
ryoon様、@ebijun様、@adukot様、@masaru0714様、@oshimyja様、@tisihara様、@LabDrunker様、@bsh_tw様、@obache様、ozaki-r様、knakahara様、@nullnilaki様、nonakap様(担当日順です)、本当にありがとうございました。
@nullnilaki様による最後の楽園の開拓を(ちょこっとだけ)手伝った話ではOpenBSDネタも出ており、加えて今年はFreeBSD Advent Calendar 2015も開催されていることから、来年はついに主要なBSD系OSのAdvent Calendarが揃うという夢が実現できるかも、と思っています。
というわけで、来年もNetBSD Advent Calendar(+OpenBSD Advent Calendar)を開催できたらと考えていますので、何卒よろしくお願いいたします。