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カーネルモジュール開発:
- Makefileが超シンプル(2行)
-
kldload/kldunloadで動的にロード/アンロード - sysctlで値を公開できる
-
/dev/*デバイスを作れる - printfでカジュアルにデバッグ
Linuxのカーネルモジュール開発より楽だと思う。ビルドシステムが優秀。
次回予告
Part3: デバイスドライバを書いてハードウェアを制御する
今回は仮想的なデバイスを作ったけど、次回は実際のハードウェアを制御するデバイスドライバを書く。PCI/USB/I2Cデバイスの扱い方を解説するよ。
この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!