32 ビットのプロセスレイアウトってまだ覚えてますかね?
そうです… メモリーのことです。
sbrk() は start program break または start break を設定してくれる関数です。
#include <unistd.h> int brk(void *addr); void *sbrk(intptr_t increment);
unistd.h をインクルードすると使えるようになります。
それで brk と sbrk の意味するところを思い出すために、もう一度 32 ビットのメモリーレイアウトを復習してみましょう。
brk と sbrk に関係があるのはヒープセグメントですが、これは下位アドレスから上位アドレスにあげていきます。
また開始位置はランダムに生成されるデータセグメント(DATA/BSS)の終了アドレスに brk オフセットを足したアドレスとなります。
ちなみに sbrk は start break の略だと筆者は思っていますが、まあ sbrk() 関数のコールという意味で考えると少し違ってきます。
sbrk() 関数はヒープセグメントの開始位置に対して increment バイト分だけ足してくれます。
sbrk() の引数である increment に 0 を指定すると直前のプログラムブレイクのアドレスを返します。
sbrk() が返すのは直前のプログラムブレイクという意味は、割り当てたばかりの領域の開始アドレスを返すということです。
それと brk は program break の略だと筆者は思ってますが、brk() 関数はヒープセグメントの終了位置を設定する関数です。
brk() の引数の addr に NULL を指定するとヒープセグメントの終端アドレスを返します。
brk() は malloc を実装する際に使われるためバックグランドでは活躍しますが、プログラマーとして意識することは普段は無いと思います。
ただ strace といった Linux のシステムコマンドを使うと malloc の代わりに brk() や mmap() が表示されるので、こういうものがあるという理解は必須ですね。
sbrk も start break つまりヒープの開始アドレスを取得するのに使いますが brk と同じくこれを使う機会はまず無いでしょうが、試しに使用してみましょう。
main.cpp.
1 #include <unistd.h> 2 #include <cstdlib> 3 #include <cstdio> 4 #include <cstdint> 5 6 int main() 7 { 8 std::printf("initial sbrk: 0x%lx\n",(unsigned long)sbrk(0)); 9 10 int *p = (int*)std::malloc(sizeof(int)); 11 12 std::printf("malloc address: 0x%lx\n",(unsigned long)p); 13 std::printf("second sbrk: 0x%lx\n",(unsigned long)sbrk(0)); 14 15 int *w = (int*)std::malloc(sizeof(int)*8192*1024); 16 if(w == nullptr) 17 perror("allocation error: w"); 18 19 std::printf("malloc address: 0x%lx\n",(unsigned long)w); 20 std::printf("third sbrk: 0x%lx\n",(unsigned long)sbrk(0)); 21 22 int *v = (int*)std::malloc(sizeof(int)*8192*1024*1024); 23 if(v == nullptr) 24 perror("allocation error v"); 25 26 std::printf("final sbrk: 0x%lx\n",(unsigned long)sbrk(0)); 27 28 std::free(w); 29 std::free(p); 30 31 return 0; 32 }
このコードは C 言語でなく C++ で書いてみました。
結果は以下の通りです。
ビルドと実行結果.
$ g++ main.cpp -g $ ./a.out initial sbrk: 0x5609a499c000 malloc address: 0x5609a499c670 second sbrk: 0x5609a49bd000 malloc address: 0x7f74ecb96010 third sbrk: 0x5609a49bd000 allocation error v: Cannot allocate memory final sbrk: 0x5609a49bd000
まず最初に sbrk(0) によってヒープセグメントの開始アドレスを取得します。
8 std::printf("initial sbrk: 0x%lx\n",(unsigned long)sbrk(0));
これは「 0x5609a499c000 」になりましたね。
後で説明しますが std::printf は裏でヒープを既に確保していると筆者は思っています。(間違ってる可能性もあり……です)
次はポインター p に malloc で割り当てたヒープ領域のアドレスを代入します。
10 int *p = (int*)std::malloc(sizeof(int)); 11 12 std::printf("malloc address: 0x%lx\n",(unsigned long)p);
printf の出力結果は「 0x5609a499c670 」となりますね。
13 std::printf("second sbrk: 0x%lx\n",(unsigned long)sbrk(0));
2 回目の sbrk() のコールの結果は「 0x5609a49bd000 」となりますね。
まあ前の sbrk アドレスである「 0x5609a499c000 」とは少し違いますね。
1回目と2回目の差分を取ると「 0x21000 」バイトですんで、十進数だと「 135168 」バイトが新たに確保されているようです。
次にちょっと大きめのデータを確保してみましょう。
15 int *w = (int*)std::malloc(sizeof(int)*8192*1024); 16 if(w == nullptr) 17 perror("allocation error: w"); 18 19 std::printf("malloc address: 0x%lx\n",(unsigned long)w);
「 4 x 8192 x 1024 」バイトなので、小さくは無いデータ領域ですね。
実はこれって 128 KiB の上限を超えてちゃっています…
つまり 128 KiB は 「 131072 」バイトなのですが、「 4 x 8192 x 1024 」バイトは「 33554432 」バイトです。
てことは mmap が使われるであろうと想定できる巨大なヒープへのリクエストですね。
出力結果は「 0x7f74ecb96010 」です。
まあどう見ても直前に malloc で確保したヒープアドレスの割り当て領域となる「0x5609a499c670」 とは違う場所です。
20 std::printf("third sbrk: 0x%lx\n",(unsigned long)sbrk(0));
3 回目の sbrk(0) のコールの出力結果は「 0x5609a49bd000 」となりますね。
つまり 2 回目の sbrk(0) のコールと結果は一緒ですね。
22 int *v = (int*)std::malloc(sizeof(int)*8192*1024*1024); 23 if(v == nullptr) 24 perror("allocation error v");
これの出力は「allocation error v: Cannot allocate memory」となります。
割り当てできるサイズの最大値は空きメモリーを超えられないので、軽く超えています。
ちなみに筆者の環境は以下のようになります。
$ free -h total used free shared buff/cache available Mem: 7.5G 3.0G 848M 528M 3.7G 3.7G Swap: 2.0G 0B 2.0G
てなことでメモリーの割り当てには失敗しますたね。
最後に sbrk(0) でヒープ領域の開始アドレスをチェックです。
26 std::printf("final sbrk: 0x%lx\n",(unsigned long)sbrk(0));
表示されるアドレスは「 0x5609a49bd000 」となりますんで 2 回目の sbrk() のコール返したアドレスと同じです。
Copyright 2018-2019, by Masaki Komatsu