NetBSDのLuaカーネルモジュールからLuaスクリプトの関数を呼び出してみる

遅ればせながら [NetBSD Advent Calendar 2018](https://qiita.com/advent-calendar/2018/netbsd) 24日目の記事です。
今日はLuaカーネルモジュールからLuaスクリプトの関数を呼び出してみる手順を紹介してみようと思います。

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

[18日目](https://qiita.com/furandon_pig/items/7a0ad4de22d1ee75802d)の記事でNetBSD-8.0におけるLuaカーネルの変更箇所を紹介しました(あまり変更箇所はありませんでしたが...)。
今回の記事では、Luaカーネルモジュールをもうちょっと掘り下げて見てみます。

## サンプルのluareadを読み込んでみる

[3日目](https://qiita.com/furandon_pig/items/647c21cbe21eeb3aafc2)の記事でNetBSDカーネルモジュールの紹介をした際に、サンプルとして `readhappy` カーネルモジュールを[試して](https://qiita.com/furandon_pig/items/647c21cbe21eeb3aafc2#readhappy%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB)いました。
`readhappy` のLuaカーネルモジュール版として、 `luareadhappy` カーネルモジュールがサンプルとして提供されているので、今回はこちらを試してみます。

他のカーネルモジュールと同じく、 `make` して `modload` するだけです。

```
# cd /usr/src/sys/modules/examples/luareadhappy/
# ls -F
CVS/              Makefile          happy.lua         luareadhappy.c
# make
# ls -F
CVS/                       happy.lua                  luareadhappy.kmod          machine@
Makefile                   i386@                      luareadhappy.kmod.map      x86@
amd64@                     luareadhappy.c             luareadhappy.o
# modload ./luareadhappy.kmod
# modstat | grep happy
happy                   misc     filesys  -        0     646 lua
```

`luareadhappy` カーネルモジュールは、 `modload` した時点で `happy` という名前のLua stateが内部的に作られています。

```
# luactl
1 active state:
Name             Creator  Description
happy            kernel   Example Happy Number calculator
```

`happy` stateに対し、サンプルディレクトリにある `happy.lua` をロードします。

```
# luactl load happy ./happy.lua
./happy.lua loaded into happy
```

`happy.lua` はソースコメント部分を除くと以下のようなコードになっています。

```lua
local HAPPY_NUMBER = 1

local SAD_NUMBER = 4

function dsum(n)
        local sum = 0
        while n > 0 do
                local x = n % 10
                sum = sum + (x * x)
                n = n / 10
        end
        return sum
end

function is_happy(n)
        while true do
                local total = dsum(n)

                if total == HAPPY_NUMBER then
                        return 1
                end
                if total == SAD_NUMBER then
                        return 0
                end

                n = total
        end
end
```

`luareadhappy` カーネルモジュールの中では、read(2)された時に `happy_read()` が呼ばれ、さらにそこから呼ばれる `check_happy()` の中で先述したLuaスクリプト内の関数 `is_happy()` を呼び出しているようです。

```c 
/usr/src/sys/modules/examples/luareadhappy/luareadhappy.c:
 68 static struct cdevsw happy_cdevsw = {
 69         .d_open = happy_open,
 70         .d_close = happy_close,
 71         .d_read = happy_read,
 ...
 92 /* Function that calls a Lua routine and returns whether a number is happy */
 93 static int
 94 check_happy(unsigned n)
 95 {
 96         int rv;
 97
 98         klua_lock(sc.kL);
 99         lua_getglobal(sc.kL->L, "is_happy");
100
101         if (!lua_isfunction(sc.kL->L, -1)) {
102                 lua_pop(sc.kL->L, 1);
103                 klua_unlock(sc.kL);
104                 return -1;
105         }
...
154 int
155 happy_read(dev_t self __unused, struct uio *uio, int flags __unused)
156 {
157         int rv;
158         char line[80];
159
160         /* Get next happy number */
161         while ((rv = check_happy(++sc.last)) == 0)
162                 continue;
...
168         /* Print it into line[] with trailing \n */
169         int len = snprintf(line, sizeof(line), "%u\n", sc.last);
...
177         /* Send it to User-Space */
178         int e;
179         if ((e = uiomove(line, len, uio)))
180                 return e;
181
182         return 0;
183 }
```

ユーザランド側で、以下のような簡単なサンプルを走らせ、Luaカーネルモジュールの動作を見てみましょう。

```c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
        char buf[BUFSIZ];
        char line[BUFSIZ];
        int fd;

        fd = open("/dev/happy", O_RDONLY);
        if (fd == -1) {
                fprintf(stderr, "open() failed.\n");
                exit(-1);
        }

        printf("Ctrl-D to quit > ");
        while (fgets(line, BUFSIZ, stdin) != NULL) {
                read(fd, buf, BUFSIZ);
                printf("%s", buf);
                printf("Ctrl-D to quit > ");
        }
        printf("\n");

        close(fd);

        return 0;
}
```

サンプルプログラムを動かすと、以下のような動作になります。カーネルから返された値がユーザランド側で取得されていることが確認できます。

![sample9.gif](https://qiita-image-store.s3.amazonaws.com/0/61137/32b82f10-e584-b3b7-0f87-374ab3304a8c.gif)

## まとめ

NetBSDのLuaカーネルモジュールからLuaスクリプトの関数を呼び出してみる手順を紹介しました。
[happy.luaを動かす](https://qiita.com/furandon_pig/items/7a0ad4de22d1ee75802d#lua%E3%81%A7helloworld%E3%81%99%E3%82%8B)手順では、単に `printf()` 的な動作をさせるだけでしたが、Luaカーネルモジュール内でLua stateを作成し、その中からLuaスクリプトの関数を呼び出す形にすると、カーネル内の処理をLuaで記述できるという形になります。むしろこちらの使い方の方がLuaカーネルモジュールで想定されている使い方かもしれません。