FreeBSDのローダブルモジュール入門

必要になりそうなので、勉強。Linuxだと参考資料がいろいろあるけど
FreeBSDは少ない。でもまあ *BSDだとツリー内にドキュメント入っているん
だろうなってことで、探すと /usr/share/examples/kld/cdev にキャラクタ
デバイスの参考資料があった。なんとなくわかったけど、Hello World的な
ものの方がいいかなって思って、書いてみた。

動作環境

FreeBSD 8.1-Release i386

ソースコードの取得

ソースコードの取得はこちら
参照してください。今回は 8.1-Releaseを利用しました。

はじめに

はじめにローダブルモジュール用のディレクトリを作成します。
ソースツリーの sys/modules 以下にディレクトリを作成します。
とりあえず sample_moduleという名前にしました。

  % cd 8.1.0/sys/modules
  % mkdir sample_module

Makefileを作成する

次に Makefileを作成します。作り方とかよくわからないので、
modulesにある他のローダブルモジュールの Makefileをコピーして、
ソースファイルだけ変更します。以下のようになりました。

# $FreeBSD$

.PATH: .

KMOD=   sample_module
SRCS=   sample_module.c

.include <bsd.kmod.mk>

modules/以下って Makefileだけ置いて、ソースは dev/の下に置くって
いうのが流儀のようなんですが、今回はそんなこと気にせず、sample_module
以下にソースコードを置きます。できあがるモジュール名が sample_module、
そのソースが sample_module.cとします。詳しくはわかってないですが、
ローダブルモジュールは bsd.kmod.mkを includeする必要があるようなので、
書いておきます。

ソースを書く

ロードしたときに、ロードしました、アンロードしたときにアンロード
しましたというメッセージを出すだけのシンプルなものです。

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/conf.h>

static int
sample_module_load(module_t mod, int cmd, void *arg)
{
    switch (cmd) {
    case MOD_LOAD:
      printf("Load module\n");
      break;
    case MOD_UNLOAD:
      printf("Unloaded kld character device driver\n");
      break;
    default:
      return EOPNOTSUPP;
    }

    return 0;
}

DEV_MODULE(cdev, sample_module_load, NULL);

ロードとアンロードのときに printfを呼んでいるだけです。
肝心なところは、DEV_MODULEというマクロです。manpageによると以下の
ような説明となっています。

DEV_MODULE(name, modeventhand_t evh, void *arg);
  name : 名前
  evh  : モジュールのイベントハンドラ。load, unload, shutdownなどの
         ときに呼ばれるハンドラ
  arg  : イベントハンドラに与えられる引数

manpageではイベントハンドラで、make_dev(デバイスファイルの作成)、
destroy_dev(デバイスドライバの削除)を呼べるようです。そのあたりは
まだ理解ができていないので、今後の課題となります。


DEV_MODULEの定義を見てみましょう。定義は sys/sys/conf.hにあります。

#define DEV_MODULE(name, evh, arg)                                      \
static moduledata_t name##_mod = {                                      \
    #name,                                                              \
    evh,                                                                \
    arg                                                                 \
};                                                                      \
DECLARE_MODULE(name, name##_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE)

moduledata_tという構造体変数を作成して、DECLARE_MODULEマクロを
呼び出しています。これ以上深追いはしませんが、DECLARE_MODULEのmanpageを
読むと、DECLARE_MODULEマクロを使うことでカーネルモジュールの宣言を
行うことができるようです。


とにかくこれで準備ができたので、makeします。

make

makeします。

Warning: Object directory not changed from original /usr/home/syohei/freebsd/8.1.0/sys/modules/sample_module
cc -O2 -pipe -fno-strict-aliasing -Werror -D_KERNEL -DKLD_MODULE -nostdinc   -I. -I@ -I@/contrib/altq -finline-limit=8000 --param inline-unit-growth=100 --param large-function-growth=1000 -fno-common  -mno-align-long-strings -mpreferred-stack-boundary=2  -mno-mmx -mno-3dnow -mno-sse -mno-sse2 -mno-sse3 -ffreestanding -fstack-protector -std=iso9899:1999 -fstack-protector -Wall -Wredundant-decls -Wnested-externs -Wstrict-prototypes  -Wmissing-prototypes -Wpointer-arith -Winline -Wcast-qual  -Wundef -Wno-pointer-sign -fformat-extensions -c sample_module.c
ld  -d -warn-common -r -d -o sample_module.kld sample_module.o
:> export_syms
awk -f /usr/home/syohei/freebsd/8.1.0/sys/modules/sample_module/../../conf/kmod_syms.awk sample_module.kld  export_syms | xargs -J% objcopy % sample_module.kld
ld -Bshareable  -d -warn-common -o sample_module.ko sample_module.kld
objcopy --strip-debug sample_module.ko

警告が出ているようですが、カーネルオブジェクトの .koファイルが
できました。

ロードする

FreeBSDでは kldloadでロードできます。Linuxでいうところの insmodです。

  % sudo kldload ./sample_module.ko

kldloadはデフォルトでは /boot/kernel以下からファイルを探すので、
明示的にカレントディレクトリであることを指定する必要があります。


printfで文字列が出るんじゃないかって思ったんですが、まあでないよね
ってことで、dmesgを確認します。

  % dmesg | tail -n 1
  Load sample_module

ちゃんとメッセージが記録されていました。

アンロードする

同じ手順でアンロードしてみます。unloadは kldunloadで行えます。

  % sudo kldunload sample_module.ko
  % dmesg | tail -n 1
  Unloaded sample_module

ちゃんとメッセージが出てますね。

まとめ

FreeBSDで恐ろしく簡単なローダブルモジュールを書く方法を示しました。
DEV_MODULEでイベントハンドラを登録できることがわかりました。
次はデバイスファイルを作って、open、read、write、ioctlが
できるようなものを作成しようかと思います。