第27章 brk / sbrk (unistd.h)

目次

27.1. gdb で sbrk() の解析
27.2. strace によって brk() / malloc() のコールをチェック

32 ビットのプロセスレイアウトってまだ覚えてますかね?

そうです… メモリーのことです。

sbrk() は start program break または start break を設定してくれる関数です。

#include <unistd.h>
int brk(void *addr);
void *sbrk(intptr_t increment);

unistd.h をインクルードすると使えるようになります。

それで brk と sbrk の意味するところを思い出すために、もう一度 32 ビットのメモリーレイアウトを復習してみましょう。

img/process_address_space_brk.png

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