またしても遅ればせながら NetBSD Advent Calendar 2018 25日目の記事です。
NetBSDのintro(9lua)には、Luaカーネルモジュールが提供する機能が記載されています。
提供されているLuaのモジュールとしてsystm(9lua)やpmf(9lua)があり、これらのモジュールをLuaスクリプト内から呼び出すことで、NetBSDカーネル内部の機能やデータを参照できます。が、現状で利用できるモジュールはsystm(9lua)とpmf(9lua)の2つくらいで、Luaスクリプトから利用できるモジュールを増やしたいところです。
今回の記事では、Luaスクリプトから利用できるLuaカーネルモジュールのモジュール(※1)を作成する手順を紹介しようと思います。
※1:systm(9lua)とかはLuaから local systm = require 'systm'
で利用可能になるので、モジュールと言えるのですが、Luaカーネルモジュールと呼称が被るので良い呼称が欲しいところです...。
今回作成するサンプル
今回作成するサンプルの挙動は以下になります。
- Luaカーネルモジュール+Luaスクリプトを用意し、
read(2)
が呼ばれたらLuaスクリプト内の関数を呼び出し、結果を返す。 -
systm(9lua)のようなモジュールを作成し、Luaスクリプト内から参照できるようにする。
- 今回は
luaread
という名前のモジュールにしてみます。
- 今回は
Luaカーネルモジュールを作成する
24日目の記事で紹介したluareadhappyモジュールを参考に必要な機能を実装してみます。
作成したサンプルは以下のGistにUPしてあります。
Luaスクリプトから参照可能なモジュールを用意する
Luaスクリプト側から local systm = require 'systm'
の形で参照できるモジュールを用意します。具体的にはLua stateに対し、 luaL_newlib()
lua_pushstring()
等で関数ポインタと値を紐づけるだけです。 コード中の const luaL_Reg system_lib[]
に関数名と関数へのポインタのペアを列挙します(今回のサンプルは {NULL, NULL}
のみなので紐づく関数は存在しません)。
lua_pushstring()
では、モジュールに文字列値を紐づけています。今回のサンプルでは、 luaread.copyright
でNetBSDのCopuright文字列が参照できるようになります。
(変数 copyright
はNetBSDカーネル内でグローバル変数として定義されているようです)
69 static int
70 luaopen_systm(lua_State *L)
71 {
72 const luaL_Reg systm_lib[] = {
73 { NULL, NULL }
74 };
75 luaL_newlib(sc.kL->L, systm_lib);
76
77 lua_pushstring(sc.kL->L, copyright);
78 lua_setfield(sc.kL->L, -2, "copyright");
79
80 return 1;
81 }
...
157 static int
158 luaread_modcmd(modcmd_t cmd, void *arg __unused)
159 {
...
164 switch (cmd) {
165 case MODULE_CMD_INIT:
...
168 klua_mod_register("luaread", luaopen_systm);
181 case MODULE_CMD_FINI:
...
185 klua_mod_unregister("luaread");
...
193 }
read(2)が呼ばれたときにLuaスクリプト内の関数が呼ばれるようにする
read(2)
が呼ばれたときにLuaスクリプト内部の関数が呼ばれるようにしましょう。まずは read(2)
とLuaカーネルモジュール側の関数を紐づけます。
struct cdevsw luaread_cdevsw
の d_read
メンバ変数に対応する関数ポインタと指定します。この例では luaread_read()
になります。
43 dev_type_read(luaread_read);
...
45 static struct cdevsw luaread_cdevsw = {
...
48 .d_read = luaread_read,
...
58 };
...
157 static int
158 luaread_modcmd(modcmd_t cmd, void *arg __unused)
159 {
...
164 switch (cmd) {
165 case MODULE_CMD_INIT:
...
170 if (devsw_attach("luaread", NULL, &bmajor, &luaread_cdevsw,
171 &cmajor))
172 return ENXIO;
luaread_read()
の全体像は以下のようになります(デバッグ用のコードがちょっと残っていますが...)。Luaスクリプト内の myread()
という関数を呼び出し、その戻り値をそのまま read(2)
で読み出したデータとして返します。
ここで呼び出すLuaスクリプトの関数は、あらかじめ luactl load luaread ./sample.lua
等でロードしておきます。 myread()
関数が存在しない(見つけられない)場合は、単にエラーリターンします。
109 int
110 luaread_read(dev_t self __unused, struct uio *uio, int flags __unused)
111 {
112 int len;
113 int e;
114
115 printf("-=> luaread_read()\n");
116
117 klua_lock(sc.kL);
118 lua_getglobal(sc.kL->L, "myread");
119
120 if (!lua_isfunction(sc.kL->L, -1)) {
121 printf("-=> fail: lua_isfunction()\n");
122
123 lua_pop(sc.kL->L, 1);
124 klua_unlock(sc.kL);
125 return -1;
126 }
127
128 if (lua_pcall(sc.kL->L, 0 /* args */, 1 /* res */, 0) != 0) {
129 printf("-=> fail: lua_pcall()\n");
130
131 lua_pop(sc.kL->L, 2);
132 klua_unlock(sc.kL);
133 return -1;
134 }
135
136 if (!lua_isstring(sc.kL->L, -1)) {
137 printf("-=> fail: lua_isstring()\n");
138
139 lua_pop(sc.kL->L, 2);
140 klua_unlock(sc.kL);
141 return -1;
142 }
143
144 char line[256];
145 strncpy(line, lua_tostring(sc.kL->L, -1), 254);
146 line[255] = '\0';
147
148 /* Send it to User-Space */
149 if ((e = uiomove(line, len, uio)))
150 return e;
151
152 return 0;
153 }
Luaスクリプトは以下になります。 read(2)
された際にスクリプト内の myread()
が呼ばれ、Luaカーネルモジュールが提供する luaread.copyright
の値が返され、それがNetBSDカーネル内で read(2)
で読み出した値としてユーザランド側に返されます。
local luaread = require 'luaread'
function myread()
return luaread.copyright
end
動かしてみる
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char buf[BUFSIZ];
int fd;
fd = open("/dev/happy", O_RDONLY);
if (fd == -1) {
fprintf(stderr, "open() failed.\n");
exit(-1);
}
read(fd, buf, BUFSIZ);
printf("%s", buf);
close(fd);
return 0;
}
ビルドしたLuaカーネルモジュールを動かしてみます。サンプルプログラム( a.out
)から read(2)
すると、NetBSDのCopyright表示が取得できます。NetBSDのカーネルメッセージだと緑色で表示されるので、ちゃんとユーザランド側で値を表示されていることが分かります。
まとめ
NetBSDのLuaカーネルモジュールで read(2)
時にLuaスクリプト内の関数を呼び出す方法と、カーネルモジュール内でLuaスクリプト側にモジュールとして値を見せる手順を紹介しました。Luaカーネルモジュールで提供される機能はまだ少ないので、もっと便利なモジュールが増えてくれればと思います。
おわりに
今年もどうにかNetBSD Advent Calendar 2018を書ききることができました。今年はいつもより投稿が遅くなってしまい申し訳ありませんでした。
Advent Calendar にご参加いただいた、ryoon様、oshimyja様、ebijun様(投稿日順です)、ありがとうございます。ぜひ来年も開催できればと思いますので、どうか何卒よろしくお願いいたします。