brk()システムコールは何をしますか?


184

Linuxプログラマのマニュアルによると:

brk()およびsbrk()は、プロセスのデータセグメントの終わりを定義するプログラムブレークの場所を変更します。

ここでデータセグメントはどういう意味ですか?それは単なるデータセグメントですか、それともデータ、BSS、およびヒープの組み合わせですか?

wikiによると:

データ、BSS、およびヒープ領域は、まとめて「データセグメント」と呼ばれることがあります。

データセグメントのみのサイズを変更する理由はわかりません。それがデータ、BSS、およびヒープの場合は、ヒープがより多くのスペースを取得するため、理にかなっています。

それでは、2つ目の質問をさせていただきます。これまでに読んだすべての記事で、著者はヒープが上向きに成長し、スタックが下向きに成長すると言っています。しかし、彼らが説明していないのは、ヒープがスタックとスタックの間のすべてのスペースを占有するときに何が起こるかです。

ここに画像の説明を入力してください


1
では、スペースが足りなくなったらどうしますか?HDDにスワップします。スペースを使用した後、他の種類の情報のためにスペースを解放します。
Igoris Azanovas、2011

29
@Igoris:物理メモリ(仮想メモリを使用して、必要に応じてディスクにスワップできます)とアドレス空間が混乱しています。アドレス空間がいっぱいになっても、途中でそれらのアドレスを取り戻すことはできません。
ダニエル・プライデン

7
念のため、brk()システムコールはCよりもアセンブリ言語の方が便利です。Cでは、データ割り当てのmalloc()代わりに使用する必要がありbrk()ますが、提案された質問が無効になることはありません。
alecov 2011

2
@Brian:ヒープは、さまざまなサイズや配置の領域、空きプールなどを処理するための複雑なデータ構造です。スレッドスタックは、常に(仮想アドレス空間で)完全なページの連続です。ほとんどのOSでは、スタック、ヒープ、およびメモリマップファイルの基礎となるページアロケータがあります。
Ben Voigt

2
@ブライアン:だれが操作している「スタック」があるbrk()sbrk()言ったのですか?スタックは、はるかに低いレベルで、ページアロケーターによって管理されます。
Ben Voigt 2011

回答:


233

投稿した図では、「ブレーク」、つまりbrkand によって操作されるアドレスsbrkは、ヒープの上部にある点線です。

仮想メモリレイアウトの簡略図

これまでのドキュメントでは、これを「データセグメント」の終わりとして説明しています。これは、従来の(事前共有ライブラリ、事前mmap)Unixではデータセグメントがヒープと連続していたためです。プログラムの開始前に、カーネルは「テキスト」ブロックと「データ」ブロックをアドレス0からRAMにロードし(実際はアドレス0より少し上にあるため、NULLポインターは本当に何も指さない)、ブレークアドレスを次のように設定します。データセグメントの終わり。最初に呼び出しmalloc、次に使用するsbrkブレークを上に移動し、ヒープを作成するための間での図に示すように、データセグメントのトップと新しい、より高いブレークアドレス、およびその後の使用は、mallocヒープを大きくするためにそれを使用します必要に応じて。

その間、スタックはメモリの一番上から始まり、大きくなります。スタックは、それを大きくするために明示的なシステムコールを必要としません。可能な限り多くのRAMが割り当てられた状態で開始する(これは従来のアプローチでした)か、スタックの下に予約済みアドレスの領域があり、カーネルがそこへの書き込みの試みに気づいたときに自動的にRAMを割り当てます(これが最新のアプローチです)。どちらの場合も、スタックに使用できるアドレス空間の最下部に「ガード」領域がある場合とない場合があります。このリージョンが存在する場合(最新のすべてのシステムがこれを行います)、永続的にマップされません。もしどちらかスタックまたはヒープがそれに成長しようとすると、セグメンテーション違反が発生します。しかし、伝統的に、カーネルは境界を強制しようとはしませんでした。スタックがヒープに成長するか、ヒープがスタックに成長する可能性があり、どちらの方法でもお互いのデータを落書きしてプログラムがクラッシュします。運が良ければ、すぐにクラッシュします。

この図の512GBはどこから来たのかわかりません。これは、64ビットの仮想アドレス空間を意味します。これは、そこにある非常に単純なメモリマップと一致しません。実際の64ビットアドレス空間は次のようになります。

簡素化されていないアドレス空間

              Legend:  t: text, d: data, b: BSS

これはリモートでスケーリングするものではなく、特定のOSがどのように機能するかを正確に解釈するべきではありません(描画した後、Linuxが実際に実行可能ファイルを思ったよりもアドレス0に非常に近く配置していることを発見しました。共有ライブラリ驚くほど高いアドレスで)。この図の黒い領域がマッピングされていないです-すべてのアクセスは、即時セグメンテーション違反を引き起こし- 、彼らは巨大な灰色の領域に対して相対的に。明るい灰色の領域は、プログラムとその共有ライブラリです(共有ライブラリは数十に及ぶ場合があります)。それぞれ独立していますテキストおよびデータセグメント(および "bss"セグメント。これにはグローバルデータも含まれますが、ディスク上の実行可能ファイルまたはライブラリのスペースを占有するのではなく、すべてゼロのビットに初期化されます)。ヒープは、必ずしも実行可能ファイルのデータセグメントと連続しているわけではありません-私はそれをそのように描画しましたが、少なくともLinuxはそうではないようです。スタックは仮想アドレス空間の最上部に固定されなくなり、ヒープとスタック間の距離が非常に大きいため、スタックを越えることを心配する必要はありません。

ブレークはまだヒープの上限です。しかし、私が示さなかったことは、のmmap代わりにで作られたどこかに、何十もの独立したメモリ割り当てがそこにある可能性があるということですbrk。(OSはこれらをbrkエリアから遠ざけるようにして、衝突しないようにします。)


7
詳細な説明については+1。mallocそれでも依存しているのbrkか、それともmmap別のメモリブロックを「戻す」ことができるのに使用しているのか知っていますか?
Anders Abel

18
それは特定の実装に依存しますが、IIUCの現在mallocの多くはbrk、小さな割り当てにこの領域を使用し、mmap大きな(たとえば、128Kを超える)割り当てには個々のを使用します。たとえば、Linux malloc(3)マンページのMMAP_THRESHOLDの説明を参照してください。
zwol 2011

1
確かに良い説明。しかし、あなたが言ったように、スタックはもはや仮想アドレス空間の最上部に位置しません。これは64ビットアドレス空間のみに当てはまりますか、それとも32ビットアドレス空間にも当てはまります。スタックがアドレス空間の一番上にある場合、匿名のメモリマップはどこで発生しますか?スタックの直前の仮想アドレス空間の最上部にありますか。
nik

3
@Nikhil:それは複雑です。ほとんどの 32ビットシステムは、スタックをユーザーモードアドレス空間の最上部に配置します。これは、多くの場合、完全なアドレス空間の下位2または3Gのみです(残りの空間はカーネル用に予約されています)。私は現在、そうしなかったものについて考えることはできませんが、それらすべてを知りません。ほとんどの64ビットCPUでは、実際には64ビット空間全体を使用できません。アドレスの上位10〜16ビットは、すべて0または1である必要があります。スタックは通常、使用可能な下位アドレスの最上部近くに配置されます。私はあなたにルールを与えることはできませんmmap。OSに非常に依存しています。
zwol

3
@RiccardoBestetti アドレススペースを浪費しますが、それは無害です。64ビットの仮想アドレススペースは非常に大きいため、毎秒1ギガバイトを使い果たした場合でも、実行に500年かかります。[1] ほとんどのプロセッサでは、2 ^ 48ビットから2 ^ 53ビットを超える仮想アドレスを使用することもできません(私が知っている唯一の例外は、ハッシュページテーブルモードのPOWER4です)。物理RAMを無駄にすることはありません。未使用のアドレスはRAMに割り当てられません。
zwol

26

最小限の実行可能な例

brk()システムコールは何をしますか?

ヒープと呼ばれる連続したメモリのチャンクへの読み書きをカーネルに要求します。

質問しないと、segfaultする可能性があります。

なしbrk

#define _GNU_SOURCE
#include <unistd.h>

int main(void) {
    /* Get the first address beyond the end of the heap. */
    void *b = sbrk(0);
    int *p = (int *)b;
    /* May segfault because it is outside of the heap. */
    *p = 1;
    return 0;
}

brk

#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>

int main(void) {
    void *b = sbrk(0);
    int *p = (int *)b;

    /* Move it 2 ints forward */
    brk(p + 2);

    /* Use the ints. */
    *p = 1;
    *(p + 1) = 2;
    assert(*p == 1);
    assert(*(p + 1) == 2);

    /* Deallocate back. */
    brk(b);

    return 0;
}

GitHubアップストリーム

上記は、がなくても新しいページに到達せず、segfaultにもならないbrk可能性があるため、16MiBを割り当て、segfaultが発生しない可能性が非常に高いバージョンを次に示しbrkます。

#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>

int main(void) {
    void *b;
    char *p, *end;

    b = sbrk(0);
    p = (char *)b;
    end = p + 0x1000000;
    brk(end);
    while (p < end) {
        *(p++) = 1;
    }
    brk(b);
    return 0;
}

Ubuntu 18.04でテスト済み。

仮想アドレス空間の視覚化

brk

+------+ <-- Heap Start == Heap End

brk(p + 2)

+------+ <-- Heap Start + 2 * sizof(int) == Heap End 
|      |
| You can now write your ints
| in this memory area.
|      |
+------+ <-- Heap Start

brk(b)

+------+ <-- Heap Start == Heap End

アドレススペースをよりよく理解するには、ページングに慣れる必要があります。x86ページングは​​どのように機能しますか?

brkとの両方が必要なのはなぜsbrkですか?

brkもちろんsbrk+オフセット計算で実装することもできますが、どちらも便宜上存在しています。

バックエンドでは、Linuxカーネルv5.0には、brk両方を実装するために使用される単一のシステムコールがありますhttps : //github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64。 tbl#L23

12  common  brk         __x64_sys_brk

あるbrkPOSIXは?

brk以前はPOSIXでしたが、POSIX 2001で削除されたため_GNU_SOURCE、glibcラッパーにアクセスする必要があります。

削除の原因mmapは、はじめにである可能性があります。これは、複数の範囲を割り当てることができるスーパーセットであり、割り当てオプションを増やすことができます。

brk代わりに、mallocまたはmmap今日使用する必要がある有効なケースはないと思います。

brkmalloc

brk実装の古い可能性の1つmallocです。

mmapは、おそらくすべてのPOSIXシステムが実装に現在使用している、より厳密で強力な新しいメカニズムmallocです。次に、最小限の実行可能なmmapメモリ割り当ての例を示します。

混合brkしてmalloc できますか?

あなたmallocがで実装されている場合、単一の範囲のメモリしか管理しbrkないので、どうすればそれが爆発することがないのか私にはわかりませんbrk

しかし、glibcのドキュメントでそれについて何も見つけることができませんでした。例:

mmapがに使われている可能性が高いので、物事はおそらくそこで機能するでしょうmalloc

以下も参照してください。

より詳しい情報

内部的には、カーネルはプロセスがそれだけのメモリを持つことができるかどうかを決定し、その使用のためにメモリページを割り当てます

これは、スタックとヒープの比較方法を説明しています。x86アセンブリのレジスタで使用されるプッシュ/ポップ命令の機能は何ですか?


4
pはtypeへのポインタなので、これはそうではありintませんbrk(p + 2);か?
JohanBoulé2016年

小さなメモ:アグレッシブバージョンのforループ内の式は、おそらく次のようになります*(p + i) = 1;
lima.sierra 2018年

ところで、brk(p + 2)単純に増やすのではなく、なぜを使用する必要があるのsbrk(2)ですか?brkは本当に必要ですか?
Yi Lin Liu

1
@YiLinLiuそれは、単一のカーネルバックエンド(brksyscall)の2つの非常によく似たCフロントエンドだと思います。brk以前に割り当てられたスタックを復元するには、少し便利です。
Ciro Santilli郝海东冠状病六四事件法轮功

1
@CiroSantilli新疆改造中心996ICU六四事件intのサイズが4バイトであり、int *のサイズが4バイト(32ビットマシン上)であることを考えると、(4ビットではなく)4バイトだけ増えるのではないかと思っていました。 8-(2 * sizeof int))。次の利用可能なヒープストレージを指すべきではありません-(8バイトではなく)4バイトの距離になります。ここに何か不足している場合は修正してください。
Saket Sharad、

10

自分自身を使用してbrksbrk誰もが常に不満を言う「mallocオーバーヘッド」を回避できます。しかし、この方法を併用するのは簡単でmallocはないので、free何もする必要がない場合にのみ適切です。できないから。また、malloc内部で使用する可能性のあるライブラリ呼び出しは避けてください。つまり。strlenおそらく安全ですが、おそらく安全でfopenはありません。

コールsbrkあなたのように呼び出しますmalloc。現在のブレークへのポインタを返し、その分だけブレークをインクリメントします。

void *myallocate(int n){
    return sbrk(n);
}

個々の割り当てを解放することはできませんが(malloc-overheadがないため、覚えておいてください)、最初の呼び出しで返された値を使用してを呼び出すことでスペース全体解放し、それによりbrk巻き戻すことができます。brksbrk

void *memorypool;
void initmemorypool(void){
    memorypool = sbrk(0);
}
void resetmemorypool(void){
    brk(memorypool);
}

これらのリージョンをスタックして、ブレークをリージョンの先頭に巻き戻すことにより、最新のリージョンを破棄することもできます。


もう一つ ...

sbrkは2文字より短いので、コードゴルフにも役立ちますmalloc


7
-1ので:malloc/ free最も確かにすることができます(とやっていることは)OSにメモリバックを与えます。彼らはあなたが望むときにいつもそれをするわけではないかもしれませんが、それはあなたのユースケースのために不完全に調整されているヒューリスティックの問題です。さらに重要なことに、これsbrkまでに呼び出す可能性があるプログラムでゼロ以外の引数を指定て呼び出すことは安全ではありません。mallocほとんどすべてのCライブラリ関数はmalloc内部で呼び出すことができます。間違いなくそうならない唯一のものは、非同期シグナルセーフ関数です。
zwol 2013

「安全でない」とは、「プログラムがクラッシュする」という意味です。
zwol 2013

戻ってきたメモリの自慢を削除するために編集し、ライブラリ関数が内部でを使用する危険性について言及しましたmalloc
luser droog 2013

1
派手なメモリ割り当てを行う場合は、mallocの上に配置するか、mmapの上に配置します。brkとsbrkには触れないでください。これらは、過去の遺物であり、害よりも害を及ぼします(マンページでさえ、それらに近づかないように指示しています!)
Eloff

3
これはばかげています。多くの小さな割り当てでmallocのオーバーヘッドを回避したい場合は、1つの大きな割り当てを(sbrk ではなく mallocまたはmmapを使用して)実行し、自分で実行します。バイナリツリーのノードを配列に保持する場合、64bポインタの代わりに8bまたは16bインデックスを使用できます。これは、すべてのノードを削除する準備ができるまでノードを削除する必要がない場合に役立ちます。(たとえば、ソートされたディクショナリをその場で構築します。)これを使用sbrkすることは、ソースコードのサイズを除くすべての点で手動で使用する方が良いため、コードゴルフにのみ役立ちmmap(MAP_ANONYMOUS)ます。
Peter Cordes、2015年

3

特別に指定された匿名のプライベートメモリマッピングがあります(伝統的にdata / bssのすぐ後ろにありますが、最新のLinuxは実際にASLRで場所を調整します)。原則としてmmap、で作成できる他のどのマッピングよりも優れていませんが、Linuxには、(brksyscall を使用して)このマッピングの終わりを上方に拡張して、何が発生するか、mmapまたはmremap発生する可能性があるロックコストを削減できるようにするいくつかの最適化があります。これによりmalloc、メインヒープを実装するときに実装が使用しやすくなります。


このマッピングの終わりを上方に拡張することを可能にするつもりでしたね?
zwol 2011

はい、修正されました。申し訳ありません!
R .. GitHub ICE HELPING ICEの停止

0

2つ目の質問にお答えします。mallocは失敗し、nullポインターを返します。そのため、メモリを動的に割り当てるときに、常にnullポインターをチェックします。


次に、brkとsbrkの用途は何ですか?
nik

3
@NikhilRathod:内部でmalloc()使用するかbrk()、内部で使用しsbrk()ますmalloc()。また、独自にカスタマイズしたバージョンのを実装したい場合にも使用できます。
ダニエル・プライデン

@Daniel Pryden:上の図に示すように、スタックとデータセグメントの間にあるヒープで、brkとsbrkがどのように機能するか。これがヒープを機能させるためには、最後にすべきです。私は正しいですか?
nik

2
@ブライアン:ダニエルは、OSがスタックポインターではなくスタックセグメントを管理していると言っていました... 重要なのは、スタックセグメントにsbrk / brk syscallがないことです。Linuxは、スタックセグメントの最後に書き込もうとすると、ページを自動的に割り当てます。
ジムバルター2011

1
そしてブライアン、あなたは質問の半分だけに答えました。残りの半分は、使用可能なスペースがないときにスタックにプッシュしようとするとどうなるかです...セグメンテーション違反が発生します。
ジムバルター2011

0

ヒープはプログラムのデータセグメントの最後に配置されます。brk()ヒープのサイズを変更(拡張)するために使用されます。ヒープがこれ以上成長できない場合、malloc呼び出しは失敗します。


つまり、私の質問の図のように、インターネット上のすべての図が間違っているということです。可能であれば、正しい図を教えてください。
nik

2
@Nikkhil図の一番上は記憶の終わりであることを覚えておいてください。スタックが大きくなるにつれて、スタックの上部が図のに移動します。ヒープが展開されると、ヒープの上部が図の上方に移動します。
ブライアンゴードン

0

データセグメントは、すべての静的データを保持するメモリの一部であり、起動時に実行可能ファイルから読み込まれ、通常はゼロで埋められます。


また、ガベージである可能性がある初期化されていない静的データ(実行可能ファイルには存在しない)も保持します。
luser droog 2011

初期化されていない静的データ(.bss)は、プログラムの開始前にOSによってすべてビット0に初期化されます。これは実際にはC標準によって保証されています。一部の組み込みシステムは気にならないかもしれません(私は一度も見たことはありませんが、組み込みのすべてを動作させるわけではありません)
zwol

@zwol:Linuxには、によって返されるページをゼロにしないコンパイル時オプションがありますmmapが、私.bssはそれでもゼロになると思います。BSSスペースは、プログラムがいくつかのゼロ化された配列を必要とするという事実を表現するためのおそらく最もコンパクトな方法です。
Peter Cordes、2015年

1
@PeterCordes C標準では、初期化子なしで宣言されたグローバル変数は、ゼロに初期化された場合と同様に扱われると述べています。したがって、そのような変数を入れて.bssゼロに.bssならないCの実装は、不適合になります。しかし、C実装を強制的に使用.bssしたり、そのようなことを強制したりするものはありません。
zwol

@PeterCordesまた、「C実装」とプログラムの間の境界線は非常にあいまいになる可能性があります。たとえば、通常、各実行可能ファイルに静的にリンクされた、実装からのコードの小さなチャンクが前に実行されmainます。そのコードは.bss、カーネルにそれを実行させるのではなく、その領域をゼロにすることができ、それでもなお適合します。
zwol

0

mallocはbrkシステムコールを使用してメモリを割り当てます。

含む

int main(void){

char *a = malloc(10); 
return 0;
}

この単純なプログラムをstraceで実行すると、brkシステムが呼び出されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.