Luaカーネルモジュールでgetpid(2)やkill(2)相当の関数を呼んだりしてみる


[NetBSD Advent Calendar 2015](http://qiita.com/advent-calendar/2015/netbsd)もいよいよ今日で最終日です(正確には最終日ギリギリでありますが...)。最終日は[NetBSD-7.0から標準搭載された](http://blog.netbsd.org/tnf/entry/announcing_netbsd_7_0)、Luaカーネルモジュールをネタに、LuaスクリプトからNetBSDカーネル内の関数を呼び出してみる話をしたいと思います。

## NetBSDのLuaカーネルモジュール

[Announcing NetBSD 7.0](http://blog.netbsd.org/tnf/entry/announcing_netbsd_7_0)を見ると、NetBSD-7.0における注目機能の一つとして"Lua kernel scripting."が挙げられています。機能としては、intro(9lua)で説明されているように、Luaスクリプトのカーネルバインディングを提供する機能です。これによりカーネル内でLuaスクリプトの実行とカーネル内関数をLuaから利用することが可能になります。

## Luaカーネルモジュールの現状

Luaカーネルモジュールについては、私の方でも[NetBSD-7.0_RC3で試してみたり](http://qiita.com/furandon_pig/items/9b762e7a59f08702a7f1)、[Luaカーネルモジュールで遊ぶ会](https://yry2.doorkeeper.jp/events/29556)という謎の会を開催してみたりするなど個人的に注目している機能です。

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での準備手順](http://qiita.com/furandon_pig/items/9b762e7a59f08702a7f1#カーネルソースコードの用意)でもビルド環境の構築手順を紹介していましたが、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サーバとして動作させるための設定を追加します。

```/etc/rc.conf:
rpcbind=YES
mountd=YES
nfs_server=YES
lockd=YES
statd=YES
```

/etc/exportに以下を追記します。

```/etc/exports:
/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クライアントの用の設定を記述します。

```/etc/rc.conf:
rpcbind=YES
nfs_client=YES
lockd=YES
statd=YES
```

### カーネルソースコードの取得
カーネルモジュールへの機能追加なので、以下のURLからNetBSD-7.0のソースコードを取得します。

 * http://ftp.jaist.ac.jp/pub/NetBSD/NetBSD-7.0/source/sets/

```
$ 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
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()の結果を変数に代入するのではなく、グローバル変数経由での結果参照になります。

```lua
-- 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が表示されています。

![スクリーンショット 2015-12-25 22.50.36.png](https://qiita-image-store.s3.amazonaws.com/0/61137/57f2d030-efb0-d61e-21c9-08ce816495e5.png)

## 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
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
```

#### シグナルを送信してみる

ユーザランド側で以下のようなサンプルプログラムを走らせ、シグナルが送られてきたことを確認してみます。

```c
#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ですね。

```lua
local systm = require 'systm'

target_pid = 2565
systm.kill(target_pid, systm.SIGINT)
```

Luaスクリプトを実行します。

```
# luactl load mylua ./kill.lua
```

ユーザランド側のプログラムでシグナルが受け取れました。

```
# ./sample                                                         
got signal.
```

ちなみにSIGKILLも送信できます。

```lua
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](http://wiki.netbsd.org/users/ryoon/)様、[@ebijun](https://twitter.com/ebijun)様、[@adukot](https://twitter.com/tokudahiroshi)様、[@masaru0714](https://twitter.com/masaru0714)様、[@oshimyja](https://twitter.com/oshimyja)様、[@tisihara](https://twitter.com/tisihara)様、[@LabDrunker](https://twitter.com/LabDrunker)様、[@bsh_tw](https://twitter.com/bsh_tw)様、[@obache](https://twitter.com/obache)様、[ozaki-r](https://github.com/ozaki-r)様、[knakahara](https://github.com/knakahara)様、[@nullnilaki](https://twitter.com/nullnilaki)様、[nonakap](http://www.asahi-net.or.jp/~aw9k-nnk/)様(担当日順です)、本当にありがとうございました。

@nullnilaki様による[最後の楽園の開拓を(ちょこっとだけ)手伝った話](http://nullnilaki.hatenablog.com/entries/2015/12/23)ではOpenBSDネタも出ており、加えて今年は[FreeBSD Advent Calendar 2015](http://qiita.com/advent-calendar/2015/freebsd)も開催されていることから、来年はついに主要なBSD系OSのAdvent Calendarが揃うという夢が実現できるかも、と思っています。

というわけで、来年もNetBSD Advent Calendar(+OpenBSD Advent Calendar)を開催できたらと考えていますので、何卒よろしくお願いいたします。

## 参考URL

 * [Lua in the NetBSD Kernel(PDF)](https://www.netbsd.org/gallery/presentations/mbalmer/fosdem2012/kernel_mode_lua.pdf)
 *