この記事を作成した理由
最近FreeBSD 11.0 STABLEを使用してシステムコール当たりの勉強を行うことになった。そこで今回カーネルにシステムコールを仕込むことになったのだが、サクッと"システムコール 追加"とか検索してみると Linux,Linux,Linux!! と君ら本当にLinux大好きだなと思うほどの Linuxでシステムコールを追加する方法 が書かれていた。まあLinuxは幅広く使用されているし使う人も多いので資料が充実していることは理解できる。
……あれ?FreeBSDの資料は?どこ?ここ?
FreeBSDでのシステムコール追加の方法についてまとめられた日本語資料はパッと見た感じシステムコールを作ろうというサイト以外見当たらない。 そのサイトでええやん って思ったあなた。
古いんですよ。FreeBSDのバージョン。
ほとんどは合ってるんですよ。追加の方法。でもね、syscall-hide.h1ってなんやねん。見つからんわ ってなりました。ほかにも、一番困ったところはユーザランドで作ったシステムコールを呼び出せるようにライブラリに追加しようとしてみると
さあサイトに書いてある通りにやってみるぞ!……あれ? コンパイルエラー出るやん。なんやこれ、いくら調べても資料出てこん……
じゃあ "freebsd 11.0 システムコール 追加"ってして探したらええやん? って思ったあなた。
ほかにないんですよ。FreeBSDのカーネルにシステムコールを追加する方法をまとめてるサイトって。stackoverflowにもあんまり乗ってないし……
というわけで、茶番は置いておいて、今回この記事を作成した理由は あまりにも資料が少なすぎる という自分自身の嘆きと備忘録も兼ねて作成した。
諸注意
-
現在(2017/6/7)において、この記事では システムコールを実際にどのようにして追加していくのかを書いていく ので、どうしてこのようになるのかという部分は記述しないことにする。その辺は後々調べていきたい。
-
この記事ではFreeBSDのカーネル部分をいじっていくので、何らかの要因でカーネルが破壊される可能性がある。 そのため、重要なデータなどはあらかじめ 別の計算機にバックアップを取っておくか仮想マシンもしくは壊れても構わない計算機でやることをお勧めする。
-
この記事の記述通りに実行したとき、カーネルなどが破壊され 何らかの損失が発生した場合でも一切の責任を負わない。
前提条件
今回使用した機材、システムなどを記載する。
-
ハード:HP Compaq Pro 6300 Small Form Factor
-
Intel Inside CORE i3
-
OS:FreeBSD 11.0 STABLE
-
言語:C言語
-
コンパイラ:clang (またはcc)
-
エディタおよびページャ:vi, less
システムコールの追加
新しく追加するシステムコールの定義
まずは新しく作る予定のシステムコールを定義する必要がある。システムコールを定義しているファイルは、 /usr/src/sys/kern/syscalls.master
ですべて定義されている。中身を見てみると
$FreeBSD: stable/11/sys/kern/syscalls.master 313450 2017-02-08 18:32:35Z jhb $
...
0 AUE_NULL STD { int nosys(void); } syscall nosys_args int
1 AUE_EXIT STD { void sys_exit(int rval); } exit \
sys_exit_args void
2 AUE_FORK STD { int fork(void); }
3 AUE_NULL STD { ssize_t read(int fd, void *buf, \
size_t nbyte); }
...
のように記述されている。これはシステムコール番号 タイプ {プロトタイプ宣言}
となっている。適当に開いているシステムコール番号(例えば172 AUE_NULL UNIMPL nosys
)をコメントアウトして、次のように記述する。
...
;172 AUE_NULL UNIMPL nosys
172 AUE_ATL STD { int atlantic(int x, int y, char *buf); }
...
次に、該当するシステムコール番号を修正するために、/usr/src/sys/bsm/audit_kevents.h
を編集する。この記述はどこに記述しても構わない。
...
# define AUE_ATL 172 /* atlantic syscall */
...
次に、/usr/src/sys/kern
で以下のファイルをバックアップとして名前を変更する。
-
syscall.h -> syscall.h.bak
-
sysproto.h -> sysproto.h.bak
その後、関連ファイルを更新するためにmakesyscalls.sh
を実行する。
/usr/src/sys/kern # sh makesyscalls.sh syscalls.master
これで、syscall.h
とsysproto.h
が更新された。
システムコールの作成
システムコールの実装自体は比較的簡単である。システムコールはC言語で記述されているので、少々嗜んでいる人ならば簡単に実装ができる。ここで注意すべき点として、stdio.h
のprintf()といった便利なライブラリ関数は 使用できない というところを注意したい。これは、カーネルとC言語では呼び出せるライブラリが違うので、C言語で呼び出せるライブラリはカーネルでは呼び出せないからである。
システムコールを実装する前に、/usr/src/sys/sys/sysproto.h
を見てみると、次のようなプロトタイプ宣言があるはず。
...
struct atlantic_args {
char x_l_[PADL_(int)]; int x; char x_r_[PADR_(int)];
char y_l_[PADL_(int)]; int y; char y_r_[PADR_(int)];
char buf_l_[PADL_(char *)]; char * buf; char buf_r_[PADL_(char *)];
};
...
int sys_atlantic(struct thread *, struct atlantic_args *);
...
システムコールを実装するには、sysproto.h
に記述されているプロトタイプ宣言をそのまま使用する必要がある。システムコールの作成例として次のようにする。
# include <sys/sysproto.h>
# include <sys/types.h>
# include <sys/systm.h>
# include <sys/proc.h>
int
sys_atlantic(struct thread *td, struct atlantic_args *at_ar) { /* 引数名は自由に決めてよい */
int x, y;
x = at_ar->x;
y = at_ar->y;
td->td_retval[0] = x + y;
printf("x + y = %d\n", x + y);
printf("get of char : %s\n", at_ar->buf);
return 0;
}
ユーザから渡される引数は、sysproto.h
で構造体に格納され、カーネルに渡される。上のstruct atlantic_args {...}
が実際に引数が格納される構造体である。ごちゃごちゃしているが、これを簡単にすると
struct atlantic_args {
int x;
int y;
char * buf;
}
とsyscalls.master
で記述した引数と同じ変数が設定されているのがわかる。
次に、td->td_retval[0] = x + y
はxとyを足し合わせた整数を返り値に設定している。次にprintf
を使用しているが、これはC言語のstdio.h
をインクルードしているわけではない。これはカーネルに実装されている printfによく似たナニカ である。sys/types.h
とsys/systm.h
をインクルードすると使用することができる関数であり、stdio.h
のprintf
との差異は、使用できる変換指定子が増えている点である。詳細はFreeBSDのドキュメントを参照してほしい。
システムコールをカーネルに組み込む
次はシステムコールをカーネルに組み込んで使用できるようにする。まず、システムコール関数ファイルに新しく作成したシステムコールを定義する。
...
kenr/sys_atlantic.c standard
...
次に行う作業は、カーネルを再構築しているか否かで作業内容が変わってくる。
- カーネルをカスタムしていない場合
# config GENERIC
# cd ../../compile/GENERIC
(/usr/src/sys/compile/GENERIC)# make depend; make; make install
- カーネルをカスタムしている場合
# make cleandepend
# make depend; make; make install
それでシステムコールを追加する作業は終了した。
追加したシステムコールをユーザレベルで使用できるようにする
現時点で追加したシステムコールはユーザが作成したアプリケーションから呼び出すことができないので、追加したシステムコールをユーザライブラリに追加する必要がある。そこで、作成したシステムコールをユーザライブラリに追加する作業を行う。
libcの再構築
まず初めに、退避が必要なファイルを次のようにして変更する。
# mv syscall.h syscall.h.bak
# mv sysproto.h sysproto.h.bak
退避後、更新されたシステムコールの定義ファイルをユーザライブラリにコピーさせる。
# cp /usr/src/sys/sys/syscall.h /usr/include/sys/.
# cp /usr/src/sys/sys/sysproto.h /usr/include/sys/.
次に、ダイナミックリンク時に使用されるシンボルマップに追加したシステムコールの定義を追加する。
...
atlantic;
...
次に、マシン依存コードとしてシステムコールライブラリの関数を新しく作成する。
# include "compat.h"
# include "SYS.h"
RSYSCALL(atlantic)
.section .note.GNU-stack,"",%progbits
最後にlibcをmakeして再構築終わり!としたいところだがこのままlibcをmakeしても必要なファイルが存在しないので途中でコンパイルエラーが発生する。そのため、libcでmakeする前にlibnetbsdでmakeする必要がある。
# make obj
# make depend
# make all
libnetbsdでmakeした後、libcをmakeして今度こそ再構築が終了する。
# make obj
# make depend
# make all
# make install
テストコードを書いてみる
ちゃんとユーザランドで追加したシステムコールが使えるかどうかをテストする。例として次のようなプログラムを作成してみる。作成場所は不都合がない場所ならばどこでもよい。おそらくコンパイルできるならばコンパイラは選ばない。
/* ヘッダファイルは必要ない */
int atlantic (int x, int y, char *buf);
int main (void) {
char *buf = "Test of system call";
atlantic(1, 2, buf);
return 0;
}
実行結果がこちら。
# clang -o test test.c
# ./test
x + y = 3
get of char : Test of system call
#
確かにシステムコールが実行されていることが確認できる。
終わりに
今回このような記事を作成したが、自分自身まだわかっていない部分や勉強不足な部分が大量に残されているので、 どの作業がどのように処理されているのかがしっかりとわかっていない。なので意味を知りたい方にはあまりお勧めできない記事となっている。どちらかといえば、システムコールの作り方がわからない、とっかかりを得たいといった方向けの記事なので、ぜひ興味を持った方はFreeBSDを突っ込んでシステムコールを作ってみてほしい。
この記事で、ここが間違っている、ここの作業はこうなっているなどといったご指摘は随時受け付けていますので、ご指摘いただけると大変うれしいです。
-
古い ↩