4
0

More than 5 years have passed since last update.

NetBSDのカーネルモジュールサンプルを試してみる

Posted at

NetBSD Advent Calendar 2018 3日目の記事です。
今日はNetBSDのカーネルモジュールの話をしようと思います。

カーネルモジュール

カーネルモジュールについては module(7) の説明を見るのが手っ取り早いです。カーネルモジュールはシステム管理者が稼働中のシステムに対し、動的に追加・削除することができる機能で、開発者が再起動を必要としない形でカーネル機能の追加が可能になります。

DESCRIPTION
     Kernel modules allow the system administrator to dynamically add and
     remove functionality from a running system.  This also helps software
     developers add new parts of the kernel without constantly rebooting to
     test their changes.

カーネルモジュールのサンプル

NetBSDソースツリーの sys/modules/examples 以下にカーネルモジュールのサンプルが用意されています。
今回はこのサンプルを元にカーネルモジュールを試してみましょう。

# cd /usr/src/sys/modules/examples
# ls -F
CVS/           Makefile.inc   hello/         luareadhappy/  properties/
Makefile       README         luahello/      ping/          readhappy/

カーネルモジュールをビルドする環境を作る

カーネルモジュールのサンプルコードは、ビルドツールチェインが生成したパスにgccがあることを想定したMakefileになっています。
そのため、カーネルモジュールをビルドするには build.sh でセルフホスト用のツールチェインを構築してしまうのが早道です。
(前提として、 /usr/src にカーネルソースが展開されているものとします)

以下の手順で、x86_64向けのツールチェインを構築します。

$ sudo bash
# cd /usr/src
# ./build.sh -m amd64 -a x86_64 -T /usr/src/tooldir.NetBSD-8.0-amd64 -r -o -U tools

モジュールのサンプルを触ってみる

さっそくカーネルモジュールのサンプルを触ってみます。
サンプルには helloreadhappy という小さな2つのモジュールが用意されているので、それぞれを見てみましょう。

helloモジュール

まずは hello モジュールです。コメントを除くと30行程度のソースコードです。

# cd hello
# ls -F
CVS/      Makefile  hello.c
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hello.c,v 1.1 2015/05/13 07:07:36 pgoyette Exp $");

#include <sys/param.h>
#include <sys/module.h>

/*
 * Last parameter of MODULE macro is a list of names (as string; names are
 * separated by commas) of dependencies.  If module has no dependencies,
 * then NULL should be passed.
 */

MODULE(MODULE_CLASS_MISC, hello, NULL);

static int
hello_modcmd(modcmd_t cmd, void *arg __unused)
{
        switch (cmd) {
        case MODULE_CMD_INIT:
                printf("Example module loaded.\n");
                break;

        case MODULE_CMD_FINI:
                printf("Example module unloaded.\n");
                break;

        case MODULE_CMD_STAT:
                printf("Example module status queried.\n");
                break;

        default:
                return ENOTTY;
        }

        return 0;
}

make コマンドでそのままビルドできます。

# make
# ls -F
CVS/             amd64@           hello.kmod       hello.o          machine@
Makefile         hello.c          hello.kmod.map   i386@            x86@

カーネルモジュールのロード

カーネルモジュールのロードは modload コマンドで行います。

# cd /usr/src/sys/modules/examples/hello/
#
# # まだhelloモジュールはロードされていない
# modstat | grep hello | wc -l
       0
#
# # modloadコマンドでロード。
# # 「./」で相対パス指定にしないとロードエラーになるので注意。
# modload ./hello.kmod
#
# # helloモジュールがロードされていることを確認する。
# modstat | egrep 'NAME|hello'
NAME                    CLASS    SOURCE   FLAG  REFS    SIZE REQUIRES
hello                   misc     filesys  -        0      77 -

ソースコードと照らし合わせる形で、カーネルモジュールのロード・アンロードがどう振る舞うのかを見てみます。

static int
hello_modcmd(modcmd_t cmd, void *arg __unused)
{
        switch (cmd) {
        case MODULE_CMD_INIT:
                printf("Example module loaded.\n");
                break;

モジュールをロードすると...。

# modunload hello

対応する処理として、 hello_modcmd() 関数の MODULE_CMD_INIT が処理されます。

static int
hello_modcmd(modcmd_t cmd, void *arg __unused)
{
        switch (cmd) {
...中略...
        case MODULE_CMD_INIT:
                printf("Example module loaded.\n");
                break;

modunload の場合は、同関数の MODULE_CMD_FINI が処理されます。

        case MODULE_CMD_FINI:
                printf("Example module unloaded.\n");
                break;

readhappyモジュール

次に readhappy モジュールを見てみます。こちらもシンプルなコードのようですね。

# cd /usr/src/sys/modules/examples/readhappy
# ls -F
CVS/         Makefile     readhappy.c

こちらも make コマンドでビルドし、 modload コマンドでロードします。

# modload ./readhappy.kmod
# modstat | egrep '^NAME|happy'
NAME                    CLASS    SOURCE   FLAG  REFS    SIZE REQUIRES
happy                   misc     filesys  -        0     377 -

readhappy.c のソースコードコメントを見ると以下の記述があるので、それに従ってmknodコマンドを実行します。

 * Create a device /dev/happy from which you can read sequential
 * happy numbers.
 *
 * To use this device you need to do:
 *     mknod /dev/happy c 210 0
# mknod /dev/happy c 210 0
# ls -l /dev/happy
crw-r--r--  1 root  wheel  210, 0 Dec  3 18:58 /dev/happy
# file /dev/happy
/dev/happy: character special (210/0)

ソースコードを見ると、 readhappy.cstruct cdevsw.d_readread(2) に対応する関数が紐付けられています。そこで、紐づけられている happy_read() 関数に適当な printf() を挿入して動作を見てみましょう。

static struct cdevsw happy_cdevsw = {
        ...中略...
        .d_read = happy_read,
...中略...

happy_read(dev_t self __unused, struct uio *uio, int flags __unused)
{
        ...中略...
        /* Send it to User-Space */
        int e;
        printf("-=> (^_^)/ %s", line);  // 確認用に追加した行。
        if ((e = uiomove(line, len, uio)))
                return e;

ユーザランド側で簡単なサンプルコードを用意し、 /dev/happy からデータを読みだしてみます。

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

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

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

        for (i = 0; i < argc; i++) {
                read(fd, buf, BUFSIZ);
                printf("%s", buf);
        }
        close(fd);

        return 0;
}

データを read() する度に printf() で追加した文言がカーネルメッセージとして出力されていることと、カーネル内の値(データ)が read() でユーザランド側でも取得できることが分かります。
img1.png

まとめ

NetBSDのカーネルモジュールについて、提供されているサンプルを元にビルドやロード方法を紹介しました。
特に readhappy.c はシンプルでありながら、ユーザランドへのデータの渡し方等、ツボを押さえたサンプルになっています。

カーネルモジュールのサンプルには、他にもLuaで記述されたカーネルモジュールも含まれているので、こちらも後日紹介できればと思います。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0