fizzbuzz systemcall in FreeBSD

Linuxは知らないですが、FreeBSDはわりと簡単システムコール
追加できるので、紹介します。

試した環境

コード

#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysproto.h>
#include <sys/sysent.h>
#include <sys/kernel.h>
#include <sys/systm.h>

struct fizzbuzz_args {
	int a_number;
	char *a_buf;
};

static int
fizzbuzz(struct thread *td, struct fizzbuzz_args *uap)
{
	int num = uap->a_number, len;
	char buf[32] = {0}, *p;

	if (num % 5 == 0 && num % 3 == 0) {
		p = "fizzbuzz";
		len = sizeof("fizzbuzz");
	} else if (num % 5 == 0) {
		p = "buzz";
		len = sizeof("buzz");
	} else if (num % 3 == 0) {
		p = "fuzz";
		len = sizeof("fizz");
	} else {
		len = sprintf(buf, "%d", num);
		len +=1;
		p = buf;
	}

	return copyout(p, uap->a_buf, len);
}

static struct sysent fizzbuzz_sysent = {
	.sy_narg	= 2,
	.sy_call	= (sy_call_t*)fizzbuzz
};

static int fizzbuzz_offset = NO_SYSCALL;

static int
fizzbuzz_load(struct module *module, int cmd, void *arg)
{
	int error = 0;

	switch (cmd) {
	case MOD_LOAD :
		printf("load fizzbuzz syscall\n");
		break;
	case MOD_UNLOAD :
		printf("unload fizzbuzz syscall\n");
		break;
	default :
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

SYSCALL_MODULE(fizzbuzz, &fizzbuzz_offset, &fizzbuzz_sysent, fizzbuzz_load, NULL);

ローダブルモジュールのロード

まず Makefileを書きます。

KMOD=	fizzbuzz
SRCS=	fizzbuzz.c

.include <bsd.kmod.mk>


そしてビルドします。

  % make


ビルドに成功すると fizzbuzz.koができるので
それをローダブルモジュールとしてロードします。

  % sudo kldload ./fizzbuzz.ko


カーネル内の print文は dmesgに出力されるので確認します。

  % dmesg
  ...
  load fizzbuzz syscall

テストプログラム

システムコールの実行には間接的にシステムコールを呼び出す、
syscallシステムコールを使います。

#include <sys/types.h>
#include <sys/module.h>
#include <sys/syscall.h>

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

int main(void)
{
	struct module_stat stat;
	int syscall_number, i, error;
	char buf[32];

	stat.version = sizeof(stat);
	modstat(modfind("sys/fizzbuzz"), &stat);
	syscall_number = stat.data.intval;

	for (i = 1; i <= 15; i++) {
		error = syscall(syscall_number, i, buf);
		if (error) {
			fprintf(stderr, "Error %d\n", error);
			return -1;
		}

		printf("%2d = %s\n", i, buf);
	}

	return 0;
}

実行, 確認

あとは普通にコンパイルして実行するだけです。

% gcc test.c
%./a.out
 1 = 1
 2 = 2
 3 = fuzz
 4 = 4
 5 = buzz
 6 = fuzz
 7 = 7
 8 = 8
 9 = fuzz
10 = buzz
11 = 11
12 = fuzz
13 = 13
14 = 14
15 = fizzbuzz

わざわざカーネル空間に突入し、処理を行う高コストな fizzbuzz
できあがりました。

アンロード

最後に unloadしておきます。

  % sudo kldunload fizzbuzz.ko

おわりに

カーネル内で誤ったメモリ操作を行うと panicするので
若干の注意が必要です。まあ今時普通に OS使ってて panic
することなんて皆無なので一度ぐらい経験しておくと良い
かもしれません。