NetBSD-7.0_RC3でLuaカーネルモジュールを試してみる

少し前の話になりますが、[NetBSD-7.0のLuaカーネルモジュール機能で遊ぶ会](https://yry2.doorkeeper.jp/events/29556)という勉強会を開催し、NetBSD-7系で利用できるLuaカーネルモジュール機能を試してみました。

これに前後する形で[NetBSD-7.0_RC3](http://blog.netbsd.org/tnf/entry/netbsd_7_0_rc3)が8/15にリリースされていました。手元の環境ではNetBSD-7.0_RC1での手順を調べていたので、改めてNetBSD-7.0_RC3における、Luaカーネルモジュール機能の利用手順をまとめてみました。

# NetBSD-7.0_RC3のインストール

まずはNetBSD-7.0_RC3のインストールです。ISOイメージは以下のURLからダウンロードできます。

 * http://ftp.jaist.ac.jp/pub/NetBSD/iso/7.0_RC3/NetBSD-7.0_RC3-amd64.iso

NetBSDのインストールは以下の記事を参考にしながら行えます。が、NetBSD-7系では一部インストール手順が変更になっているので注意してください。

 * NetBSDをVirtualBoxにインストールしてみる
   * http://qiita.com/furandon_pig/items/5882ede06b5b5d3bf439

# Luaカーネルモジュールを試す

## 用意されているLuaカーネルモジュール

インストールしたNetBSD-7.0_RC3に用意されているLuaカーネルモジュールを確認してみます。Lua関連のカーネルモジュールは3つあるようです。

```
# ls /stand/amd64/7.0/modules/lua*/*.kmod 
/stand/amd64/7.0/modules/lua/lua.kmod
/stand/amd64/7.0/modules/luapmf/luapmf.kmod
/stand/amd64/7.0/modules/luasystm/luasystm.kmod
```

## Luaに関するmanページ

それぞれのカーネルモジュールがどんな機能を提供しているのがmanページで確認してみます。

```
# find /usr/share/man/man* -type f | grep lua 
...中略...
/usr/share/man/man8/luactl.8
/usr/share/man/man9lua/intro.9lua
/usr/share/man/man9lua/pmf.9lua
/usr/share/man/man9lua/systm.9lua
```

各manページをざっと見て行きましょう。

### intro.9lua

intro.9luaはLuaカーネルモジュールの概要を記載したmanページのようです。ただ、現状ではホントに概要のみのようです...。

```
# man 9lua intro 
man: Formatting manual page...
INTRO(9lua)                          9lua                          INTRO(9lua)

NAME
     intro -- introduction to the Lua kernel bindings

DESCRIPTION
     This section provides an overview of the Lua kernel bindings, their
     functions, error returns and other common definitions and concepts.

SEE ALSO
     lua(1), luac(1), intro(3lua), lua(4), pmf(9lua), systm(9lua)

HISTORY
     An intro manual for Lua kernel bindings appeared in NetBSD 7.0.

NetBSD 7.0_RC3                 October 26, 2013                 NetBSD 7.0_RC3
```

### pmf.9lua

pmf.9luaはパワーマネジメント関連のカーネルモジュールのようです。

```
# man 9lua pmf
man: Formatting manual page...
PMF(9lua)                            9lua                            PMF(9lua)

NAME
     pmf -- Lua binding to the power management framework

SYNOPSIS
     local pmf = require 'pmf'

     pmf.system_shutdown(howto)
     pmf.set_platform(key, value)
     value = pmf.get_platform(key)

DESCRIPTION
     The pmf Lua binding provides access to the power management framework.
...
```

### systm.9lua

systm.9luaはLuaカーネルモジュールの基本的な機能について記述しているようです。

```
# man 9lua systm
man: Formatting manual page...
SYSTM(9lua)                          9lua                          SYSTM(9lua)

NAME
     systm -- access to general kernel functionality from Lua

SYNOPSIS
     local systm = require 'systm'

     systm.print(msg)
     systm.print_nolog(msg)
     systm.uprint(msg)
     systm.aprint_normal(msg)
     systm.aprint_naive(msg)
     systm.aprint_verbose(msg)
     systm.aprint_debug(msg)
     systm.aprint_error(msg)
     count = systm.aprint_get_error_count()
     systm.panic(msg)
...
```

## Hello,World.

Luaカーネルモジュールで提供されている機能をざっと把握したところで、さっそくLuaカーネルモジュールの機能を使ってみましょう。先ほどのsystm.9luaで解説されていたsystm.print()を使うと"Hello,World."が実行できそうです。

まずはLuaスクリプトを作成します。

```lua
-- hello.lua
local systm = require 'systm' 
systm.print('Hello,World.')
```

## Luaカーネルモジュール上でLuaスクリプトを動かす

これをLuaカーネルモジュールから実行してみます。一連のコマンドは以下になります。

```shell-session
# modload lua
# luactl create hello
# luactl load hello ./hello.lua
# luactl destroy hello
# modunload luasystem
# modunload lua
```

実行結果は以下です。無事に"Hello,World."というメッセージがカーネルから出力されていいます(NetBSDではカーネル内でprintf()した内容は緑色で出力されます)。

![img101.png](https://qiita-image-store.s3.amazonaws.com/0/61137/a6c09aa2-a8bb-68e7-5a1e-6f67e9f31878.png)

## Luaカーネルモジュールの一連の流れ

Luaカーネルモジュールとluactlコマンドを相互に使う形でLuaスクリプトを実行しています。一連の手順がちょっとややこしいので、簡単なシーケンス図を描いてみました。

![img105.png](https://qiita-image-store.s3.amazonaws.com/0/61137/68c1156b-2744-44cf-9773-e18e3dbac303.png)

# Luaカーネルモジュールへの機能追加

最初から用意されているsystm.print()だけでなく、独自にLuaカーネルモジュールに関数を追加してみたいと思うのが人情(?)です。というわけで、Luaカーネルモジュールに独自の関数を追加してみます。

## カーネルソースコードの用意

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

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

```shell-session
# curl -O http://ftp.jaist.ac.jp/pub/NetBSD/NetBSD-7.0_RC3/source/sets/gnusrc.tgz
# curl -O http://ftp.jaist.ac.jp/pub/NetBSD/NetBSD-7.0_RC3/source/sets/sharesrc.tgz
# curl -O http://ftp.jaist.ac.jp/pub/NetBSD/NetBSD-7.0_RC3/source/sets/src.tgz
# curl -O http://ftp.jaist.ac.jp/pub/NetBSD/NetBSD-7.0_RC3/source/sets/syssrc.tgz
```

ダウンロードしたソースコードは以下のようにして展開します。展開先は/usr/srcになります。

```shell-session
# for i in `echo *.tgz` ; do tar zxvf $i -C / ; done
```

### build.shによるツールチェイン構築

カーネルソースコードを展開した後、build.shを使用してツールチェインを構築します。

```shell-session
# cd /usr/src
# ./build.sh -m `uname -m` -a `uname -p` -T /usr/cross/NetBSD-`uname -r`/`uname -m` -r -o -U tools
```

これで開発準備が整いました。以下の手順でLuaカーネルモジュールをビルドできます(nbmakeではなくnbmake-amd64を使うのがポイントです)。

```shell-session
# cd /usr/src/sys/modules/luasystm/
# /usr/cross/NetBSD-7.0_RC3/amd64/bin/nbmake-amd64
```

ビルドしたLuaカーネルモジュールに差し替えます。

```shell-session
# cp -p \
    /usr/src/sys/modules/luasystm/luasystm.kmod \
    /stand/amd64/7.0/modules/luasystm/luasystm.kmod
```

以降の例ではこの手順でluasystm.kmodをビルドしています。

## Luaカーネルモジュールに関数を追加する

### 九九の表を出力する関数を追加する

まずは簡単に、九九の表を出力する関数を追加してみます。luasystm.cを以下のように修正します。

```diff
--- luasystm.c	2014-07-20 03:38:35.000000000 +0900
+++ luasystm.c	2015-09-15 21:14:34.000000000 +0900
@@ -141,6 +141,26 @@
 	return 1;
 }
 
+static int
+kuku(lua_State *L)
+{
+	int x, y, val;
+
+	for (x = 1; x <= 9; x++) {
+		for (y = 1; y <=9; y++) {
+			val = x * y;
+			if (val < 10) {
+				printf("  %d", val);
+			} else {
+				printf(" %d", val);
+			}
+		}
+		printf("\n");
+	}
+
+	return 0;
+}
+
 /* panicing */
 
 static int
@@ -171,6 +191,7 @@
 		{ "aprint_debug",		systm_aprint_debug },
 		{ "aprint_error",		systm_aprint_error },
 		{ "aprint_get_error_count",	systm_aprint_get_error_count },
+		{ "kuku",			kuku },
 
 		/* panicing */
 		{ "panic",			systm_panic },
```

Luaスクリプトからは単に"systm.kuku()"を呼び出すだけです。

```lua
-- kuku.lua
local systm = require 'systm'
systm.kuku()
```

実行結果は以下になります。

![img106.png](https://qiita-image-store.s3.amazonaws.com/0/61137/d34a41b1-f346-6c82-e647-2bde2ae1875c.png)

### プロセス一覧を出力する関数を追加する

次にカーネル内の情報を参照する例として、プロセスの一覧を出力する関数を追加してみます。luasystm.cの修正内容は以下になります。

```diff
--- luasystm.c	2014-07-20 03:38:35.000000000 +0900
+++ luasystm.c	2015-09-15 21:45:46.000000000 +0900
@@ -39,6 +39,11 @@
 #endif
 #include <sys/systm.h>
 
+#include <sys/proc.h>
+#include <sys/filedesc.h>
+#include <sys/syscallargs.h>
+#include <sys/file.h>
+
 #include <lua.h>
 #include <lauxlib.h>
 
@@ -141,6 +146,22 @@
 	return 1;
 }
 
+static int
+show_all_process(lua_State *L)
+{
+	struct proc *p;
+
+	LIST_FOREACH(p, &allproc, p_list) {
+		if (curproc->p_pid == p->p_pid) {
+			printf("* %5d %s\n", p->p_pid, p->p_comm);
+		} else {
+			printf("  %5d %s\n", p->p_pid, p->p_comm);
+		}
+	}
+
+	return 0;
+}
+
 /* panicing */
 
 static int
@@ -171,6 +192,7 @@
 		{ "aprint_debug",		systm_aprint_debug },
 		{ "aprint_error",		systm_aprint_error },
 		{ "aprint_get_error_count",	systm_aprint_get_error_count },
+		{ "show_all_process",		show_all_process },
 
 		/* panicing */
 		{ "panic",			systm_panic },
```

先ほどのLuaスクリプトと同じく、こちらも"systm.show_all_process()"を呼び出すだけです。

```lua
-- show_all_process.lua
local systm = require 'systm'
systm.show_all_process()
```
実行結果は以下です。プロセスの一覧がだーっと出力されています。

![img102.png](https://qiita-image-store.s3.amazonaws.com/0/61137/6cf04e3b-d6bf-92e6-dc3a-4d13426968e6.png)

### カーネルスレッドの作成・破棄を行う関数を追加する

先の例では単に何か実行してそれで終わりだったのですが、今度はカーネルスレッドを立ち上げて定期的に処理が走る例を作成してみます。luasystm.cの修正内容は以下です。

```diff
--- luasystm.c	2014-07-20 03:38:35.000000000 +0900
+++ luasystm.c	2015-09-15 22:36:40.000000000 +0900
@@ -39,12 +39,23 @@
 #endif
 #include <sys/systm.h>
 
+#include <sys/types.h>
+#include <sys/mutex.h>
+#include <sys/kernel.h>
+#include <sys/kthread.h>
+
 #include <lua.h>
 #include <lauxlib.h>
 
 #ifdef _MODULE
 MODULE(MODULE_CLASS_MISC, luasystm, "lua");
 
+static kcondvar_t	yry2_wait;
+static kmutex_t		yry2_waitlock;
+static lwp_t		*yry2_worker_lwp;
+
+static void yry2_thread(void *arg);
+
 /* Various printing functions */
 static int
 print(lua_State *L)
@@ -141,6 +152,70 @@
 	return 1;
 }
 
+static int
+yry2_kthread_create(lua_State *L)
+{
+	mutex_init(&yry2_waitlock, MUTEX_DEFAULT, IPL_NONE);
+	cv_init(&yry2_wait, "balloon");
+
+	yry2_worker_lwp = (lwp_t *)0xdeadbabe;
+
+	if (kthread_create(PRI_NONE, KTHREAD_MPSAFE | KTHREAD_MUSTJOIN, NULL,
+   	    yry2_thread, NULL, &yry2_worker_lwp, "yry2_thread")) {
+		printf("cannot create kthread(luasystm).\n");
+
+		cv_destroy(&yry2_wait);
+		mutex_destroy(&yry2_waitlock);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static int
+yry2_kthread_destroy(lua_State *L)
+{
+	lwp_t *l = yry2_worker_lwp;
+
+	/* Notify the worker and wait for the exit. */
+	mutex_enter(&yry2_waitlock);
+	yry2_worker_lwp = NULL;
+	cv_broadcast(&yry2_wait);
+	mutex_exit(&yry2_waitlock);
+	kthread_join(l);
+
+	cv_destroy(&yry2_wait);
+	mutex_destroy(&yry2_waitlock);
+
+	printf("thread destroyed.\n");
+
+	return 0;
+}
+
+
+static void
+yry2_thread(void *arg)
+{
+	int sleeptime = 10000;
+	int count = 0;
+
+	for (;;) {
+		const bool finish = (yry2_worker_lwp == NULL);
+		mutex_enter(&yry2_waitlock);
+		if (finish) {
+			mutex_exit(&yry2_waitlock);
+			break;
+		}
+		printf("hello,world. (%d)\n", count++);
+		cv_timedwait(&yry2_wait, &yry2_waitlock,
+			mstohz(sleeptime));
+		mutex_exit(&yry2_waitlock);
+	}
+
+	kthread_exit(0);
+}
+
 /* panicing */
 
 static int
@@ -171,6 +246,8 @@
 		{ "aprint_debug",		systm_aprint_debug },
 		{ "aprint_error",		systm_aprint_error },
 		{ "aprint_get_error_count",	systm_aprint_get_error_count },
+		{ "yry2_kthread_create",	yry2_kthread_create },
+		{ "yry2_kthread_destroy",	yry2_kthread_destroy },
 
 		/* panicing */
 		{ "panic",			systm_panic },

```

カーネルスレッドを生成・破棄するため、Luaスクリプトは2つ作成します。

```lua
-- kthread_create.lua
local systm = require 'systm'
systm.yry2_kthread_create("")
```

```lua
-- kthread_destroy.lua
local systm = require 'systm'
systm.yry2_kthread_destroy()
```

Luaスクリプトからカーネルスレッドを起動してみます。一定間隔でメッセージが表示されます。

![img103.png](https://qiita-image-store.s3.amazonaws.com/0/61137/4e97beb7-e6c6-f01a-e718-2811540f3d54.png)

カーネルスレッドを停止してみます。

![img104.png](https://qiita-image-store.s3.amazonaws.com/0/61137/e3ac3aaa-acd9-2cd8-b2b1-53b6f94fa8d4.png)

ちなみに、カーネルスレッドの実行中にluactl destroyを実行するとカーネルパニックが発生します...。実行中の状態を破棄したらそれはダメだよなと思いつつ、チェック機構とかは無いのかという気持ちも。

ともかく、こんな感じで引数を伴わない簡単な関数についてLuaカーネルモジュールに追加することができました。

# まとめ

NetBSD-7.0_RC3でLuaカーネルモジュール機能を試してみました。Luaスクリプトから呼び出せるのはsystm.print()的なものやpmf関連のもので、提供されている機能がまだ少ない感じです。各システムコールがLuaスクリプトから触れられるようになると、応用範囲が広がりそうではありますが、何かユースケースを思いついておかないと「XXという機能が欲しいんだけど、C言語でカーネルモジュールとして実装すればイイじゃん」という話になってしまいそうな気もします。

この応用例が思いつかない感じ、これは既視感があるぞと記憶を辿ったら、[Linuxのライブパッチを試してみた時](http://qiita.com/furandon_pig/items/379ceacf234134798df2)と同じく、便利そうではあるけれどイマイチ応用例が思いつかない、というのと同じ感覚なのでした。

# 参考URL

 * [NetBSD 7.0_RC3](http://blog.netbsd.org/tnf/entry/netbsd_7_0_rc3)
 * [Lua](http://www.lua.org/)
 * [Modern net bsd kernel module](http://www.slideshare.net/masaruoki9/modern-net-bsd-kernel-module)
   * 最近のNetBSDカーネルモジュールの解説。いつの間にやらLKM(Loadable Kernel Module)ではなくなっていた...。
 * [NetBSDをVirtualBoxにインストールしてみる](http://qiita.com/furandon_pig/items/5882ede06b5b5d3bf439)
   * 手前味噌ながら自分の投稿内容...