9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FreeBSDカーネル開発シリーズ

Part1 ビルド Part2 モジュール Part3 ドライバ Part4 システムコール Part5 DTrace
✅ Done 👈 Now - - -

はじめに

前回はカーネル全体をビルドした。

でも毎回カーネルを再ビルドするのは面倒だよね。

カーネルモジュール(KLD: Kernel Loadable Modules)を使えば、カーネルの再起動なしに機能を追加・削除できる。

今回はHello Worldから実用的なモジュールまで作ってみる。

カーネルモジュールとは

┌─────────────────────────────────────────────────────────────┐
│                    FreeBSD カーネル                          │
│                                                              │
│  ┌─────────────┐  動的に                                    │
│  │   静的に    │  ロード ┌─────────┐ ┌─────────┐           │
│  │ リンクされた │ ←────── │ hello.ko│ │ myfs.ko │           │
│  │   コード    │          └─────────┘ └─────────┘           │
│  └─────────────┘                                            │
│                                                              │
│  kldload hello.ko  ← ロード                                 │
│  kldunload hello   ← アンロード                             │
└─────────────────────────────────────────────────────────────┘

メリット

  • カーネル再ビルド不要
  • 再起動不要
  • 開発が速い
  • 不要な機能は外せる

最小のHello Worldモジュール

ディレクトリ作成

mkdir -p ~/kmod/hello
cd ~/kmod/hello

ソースコード

vi hello.c
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>

static int
hello_modevent(module_t mod, int event, void *arg)
{
    switch (event) {
    case MOD_LOAD:
        printf("Hello, FreeBSD kernel!\n");
        break;
    case MOD_UNLOAD:
        printf("Goodbye, FreeBSD kernel!\n");
        break;
    default:
        return (EOPNOTSUPP);
    }
    return (0);
}

static moduledata_t hello_mod = {
    "hello",           /* モジュール名 */
    hello_modevent,    /* イベントハンドラ */
    NULL               /* プライベートデータ */
};

DECLARE_MODULE(hello, hello_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

Makefile

vi Makefile
KMOD=   hello
SRCS=   hello.c

.include <bsd.kmod.mk>

たった2行のMakefile!FreeBSDのビルドシステムが全部やってくれる。

ビルド

make
Warning: Object directory not changed from original /root/kmod/hello
cc -O2 -pipe -fno-strict-aliasing -std=iso9899:1999 -Werror -D_KERNEL -DKLD_MODULE ...
ld -m elf_x86_64_fbsd -d -warn-common -r -o hello.ko hello.o
objcopy --strip-debug hello.ko

hello.koができた!

ロード

kldload ./hello.ko

dmesgで確認:

dmesg | tail -1
# Hello, FreeBSD kernel!

確認

kldstat
#  Id Refs Address                Size Name
#   1   37 0xffffffff80200000  1e6f3a8 kernel
#   2    1 0xffffffff82300000     2000 hello.ko

アンロード

kldunload hello
dmesg | tail -1
# Goodbye, FreeBSD kernel!

sysctlで値を公開するモジュール

カーネルの情報をsysctlで見られるようにしてみよう。

mkdir -p ~/kmod/sysctl_example
cd ~/kmod/sysctl_example
vi sysctl_example.c
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysctl.h>

/* カウンター変数 */
static int counter = 0;

/* 読み取り専用の値 */
static int readonly_value = 42;

/* 文字列 */
static char message[64] = "Hello from kernel";

/* sysctl OIDを作成 */
SYSCTL_DECL(_kern);

/* _kern.mymodule ノードを作成 */
SYSCTL_NODE(_kern, OID_AUTO, mymodule, CTLFLAG_RW, 0, "My Module");

/* _kern.mymodule.counter (読み書き可能なint) */
SYSCTL_INT(_kern_mymodule, OID_AUTO, counter, CTLFLAG_RW,
    &counter, 0, "A simple counter");

/* _kern.mymodule.readonly (読み取り専用のint) */
SYSCTL_INT(_kern_mymodule, OID_AUTO, readonly, CTLFLAG_RD,
    &readonly_value, 0, "A readonly value");

/* _kern.mymodule.message (読み書き可能な文字列) */
SYSCTL_STRING(_kern_mymodule, OID_AUTO, message, CTLFLAG_RW,
    message, sizeof(message), "A message string");

static int
sysctl_example_modevent(module_t mod, int event, void *arg)
{
    switch (event) {
    case MOD_LOAD:
        printf("sysctl_example loaded\n");
        printf("Try: sysctl kern.mymodule\n");
        break;
    case MOD_UNLOAD:
        printf("sysctl_example unloaded\n");
        break;
    default:
        return (EOPNOTSUPP);
    }
    return (0);
}

static moduledata_t sysctl_example_mod = {
    "sysctl_example",
    sysctl_example_modevent,
    NULL
};

DECLARE_MODULE(sysctl_example, sysctl_example_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
vi Makefile
KMOD=   sysctl_example
SRCS=   sysctl_example.c

.include <bsd.kmod.mk>
make
kldload ./sysctl_example.ko

動作確認

# 全変数を表示
sysctl kern.mymodule
# kern.mymodule.counter: 0
# kern.mymodule.readonly: 42
# kern.mymodule.message: Hello from kernel

# カウンターを変更
sysctl kern.mymodule.counter=100
# kern.mymodule.counter: 0 -> 100

# 読み取り専用は変更できない
sysctl kern.mymodule.readonly=999
# sysctl: kern.mymodule.readonly: Operation not permitted

# 文字列を変更
sysctl kern.mymodule.message="New message"
# kern.mymodule.message: Hello from kernel -> New message

文字デバイスを作るモジュール

/dev/mydeviceを作ってみよう。

mkdir -p ~/kmod/chardev
cd ~/kmod/chardev
vi chardev.c
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/malloc.h>

#define BUFFER_SIZE 1024

static struct cdev *mydev;
static char buffer[BUFFER_SIZE];
static size_t buffer_len = 0;

MALLOC_DECLARE(M_CHARDEV);
MALLOC_DEFINE(M_CHARDEV, "chardev", "chardev buffer");

/* open */
static int
chardev_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
{
    printf("chardev: opened\n");
    return (0);
}

/* close */
static int
chardev_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
{
    printf("chardev: closed\n");
    return (0);
}

/* read */
static int
chardev_read(struct cdev *dev, struct uio *uio, int ioflag)
{
    size_t len;
    int error;

    len = MIN(uio->uio_resid, buffer_len - uio->uio_offset);
    if (len <= 0)
        return (0);

    error = uiomove(buffer + uio->uio_offset, len, uio);
    return (error);
}

/* write */
static int
chardev_write(struct cdev *dev, struct uio *uio, int ioflag)
{
    size_t len;
    int error;

    len = MIN(uio->uio_resid, BUFFER_SIZE - 1);
    if (len <= 0)
        return (ENOSPC);

    error = uiomove(buffer, len, uio);
    if (error == 0) {
        buffer[len] = '\0';
        buffer_len = len;
        printf("chardev: wrote %zu bytes\n", len);
    }
    return (error);
}

static struct cdevsw chardev_cdevsw = {
    .d_version = D_VERSION,
    .d_open    = chardev_open,
    .d_close   = chardev_close,
    .d_read    = chardev_read,
    .d_write   = chardev_write,
    .d_name    = "chardev"
};

static int
chardev_modevent(module_t mod, int event, void *arg)
{
    switch (event) {
    case MOD_LOAD:
        mydev = make_dev(&chardev_cdevsw, 0, UID_ROOT, GID_WHEEL,
                         0600, "mydevice");
        printf("chardev: /dev/mydevice created\n");
        break;
    case MOD_UNLOAD:
        destroy_dev(mydev);
        printf("chardev: /dev/mydevice destroyed\n");
        break;
    default:
        return (EOPNOTSUPP);
    }
    return (0);
}

static moduledata_t chardev_mod = {
    "chardev",
    chardev_modevent,
    NULL
};

DECLARE_MODULE(chardev, chardev_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
vi Makefile
KMOD=   chardev
SRCS=   chardev.c

.include <bsd.kmod.mk>
make
kldload ./chardev.ko

動作確認

# デバイスが作成された
ls -la /dev/mydevice
# crw-------  1 root  wheel  0x5c Jan  1 12:00 /dev/mydevice

# 書き込み
echo "Hello, device!" > /dev/mydevice
# chardev: opened
# chardev: wrote 15 bytes
# chardev: closed

# 読み込み
cat /dev/mydevice
# chardev: opened
# Hello, device!
# chardev: closed

自動ロードの設定

/boot/loader.conf

echo 'hello_load="YES"' >> /boot/loader.conf

/etc/rc.conf

sysrc kld_list+="hello"

インストール

# /boot/kernel/ にインストール
make install

既存モジュールの例を見る

# 実際のモジュールソースを見る
ls /usr/src/sys/modules/
# aac/  ath/  bpf/  em/  if_bridge/  null/  zfs/  ...

# シンプルな例
less /usr/src/sys/modules/null/null.c

デバッグ方法

printfデバッグ

printf("mymodule: value=%d\n", value);
uprintf("Message to user terminal\n");  // ユーザー端末に表示

log()

#include <sys/syslog.h>

log(LOG_INFO, "mymodule: info message\n");
log(LOG_WARNING, "mymodule: warning!\n");
log(LOG_ERR, "mymodule: error!\n");

/var/log/messagesに出力される。

WITNESS(ロック解析)

# カーネル設定に追加
options         WITNESS
options         WITNESS_SKIPSPIN

デッドロックを検出してくれる。

まとめ

FreeBSDカーネルモジュール開発:

  1. Makefileが超シンプル(2行)
  2. kldload/kldunloadで動的にロード/アンロード
  3. sysctlで値を公開できる
  4. /dev/*デバイスを作れる
  5. printfでカジュアルにデバッグ

Linuxのカーネルモジュール開発より楽だと思う。ビルドシステムが優秀。

次回予告

Part3: デバイスドライバを書いてハードウェアを制御する

今回は仮想的なデバイスを作ったけど、次回は実際のハードウェアを制御するデバイスドライバを書く。PCI/USB/I2Cデバイスの扱い方を解説するよ。

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

9
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
9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?