C言語での malloc
のお手本実装がK&Rの本に載っていて(リンク)、
その中で実際にヒープとして使えるメモリ領域をOSに要求するライブラリ関数として sbrk
というものを使っている。
これをCのライブラリを使わずにシステムコールの直接呼び出しで同様のことを行いたい。
libc関数を調べる
まずは brk
、sbrk
関数がどんなものかを調べると以下の通り:
|
brk
でデータセグメントの最後のアドレスを変更、 sbrk
は増分を指定することができる。
ヒープを増やすだけでOSに返却しないのであれば brk
は使わずに、メモリが必要になったら sbrk
を呼び出して取得したアドレスを使用すればよい。
システムコールを調べる
ライブラリcの助けを借りずにシステムコールだけで実現したいので、まずは64bit Linuxのシステムコールを調べる:
$ grep sbrk < /usr/include/asm/unistd_64.h # 結果:なし |
結果が空ということで、 sbrk
はシステムコールではなかった…。
brk
だと出てくる:
$ grep brk < /usr/include/asm/unistd_64.h |
ので、 brk
がシステムコールだった。
brkシステムコールの使い方を調べる
sbrk
はシステムコールじゃないので brk
を使う必要があるが、その場合に brk
に渡すアドレスの最初の値はどうやって取得するんだろうか?
ググってもわからなかったのでシステムコールの呼び出しをトレースできる strace
を使ってみることにした。
これを使ってlibc内部でどういう呼び出しをしているか見てみる。
以下のように sbrk
を使用するプログラムを書いて:
|
コンパイルして strace
で動かす:
$ gcc sbrktest.c |
上記の出力から推測するに、 sbrk
の内部で最初に現在のデータセグメントの値を brk(NULL)
で取得して、
それを使って増分を加算したアドレスを brk
に与えているっぽい
(4番目のbrk
はprintf
呼び出しで使われているぽい)。
システムコール brk
の戻り値はlibcの単純な成功/失敗じゃなくてアドレスが返るっぽい。
なにそれググってもそういう記述見つからないしシステムコールの使い方はどうやって調べるのよ…。
そんなこんなで、自前で brk
と sbrk
を実装してみた:
void *_brk(void *addr) { |
「現在の値」を curbrk
に保持しておき、増分を加えた値をシステムコール brk
でセットしつつ、増やす前の値を返せばOK。
後日談
ググってもシステムコールの内容が見つからないと思っていたのだけど、あるところにはあった: Ubuntu Manpage: brk, sbrk - データセグメントのサイズの変更する
C ライブラリとカーネル ABI の違い
上で説明した brk() の返り値についての動作は、 Linux の brk() システムコールをラップする glibc の関数によるものである。 (その他の多くの実装でも、 brk() の返り値はこれと同じであ る。 この返り値は SUSv2 でも規定されている。) しかし、実際の Linux システムコールは、成功 した場合、 プログラムの新しいブレークを返す。 失敗した場合、このシステムコールは現在のブ レークを返す。 glibc ラッパー関数は同様の働きをし (すなわち、新しいブレークが addr より小 さいかどうかをチェックし)、 上で説明した 0 と -1 という返り値を返す。
Linux では sbrk() は brk() システムコールを使うライブラリ関数として実装されており、 以前 のブレークの値を返すことができるように内部で調整が行われている。
brk
システムコールに NULL
を与えると変更に失敗して、現在の値が得られるというわけですな。
man brk
でも同じものが見れた。
それにしても man は、
- 2 System calls (functions provided by the kernel)
- 3 Library calls (functions within program libraries)
となっているのに、brk(2)の説明は全然カーネルじゃなくてlibcだな…。
ソースを出せ
ググってさらった二次ソースと、動かしてみての結果から勝手に推測してばっかいないで、ちゃんとソースに当たらんかい:
- brk: arch/x86/entry/syscalls/syscall_64.tbl 64bitでは
12
- brk libc: sysdeps/unix/sysv/linux/x86_64/brk.c
brk
システムコール呼び出し、現在の値を保存している - sbrk libc: misc/sbrk.c
brk
を使用して実装 - brk syscall: mm/nommu.c
mm
の範囲を調べて、動かしてよいようだったら動かす
これらの参照先が自分が使っている環境のものなのかどうかの保証はできないが…。