GNU/Linux >> Linux の 問題 >  >> Linux

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

特別に指定された匿名のプライベート メモリ マッピングがあります (従来は data/bss のすぐ後ろに配置されていましたが、最新の Linux では実際には ASLR で場所を調整します)。原則として、08 で作成できる他のマッピングより優れているわけではありません。 、しかし Linux には、このマッピングの末尾を拡張できるようにするいくつかの最適化があります (13 を使用) 27 に比べてロック コストが削減されています。 または 30 発生します。これは 43 にとって魅力的です メイン ヒープを実装するときに使用する実装。


59 を使用できます および 62 誰もが常に不満を漏らしている「malloc オーバーヘッド」を回避するために、しかし、このメソッドを 70 と組み合わせて簡単に使用することはできません 80 する必要がない場合にのみ適切です なんでも。できないからです。また、95 を使用する可能性のあるライブラリ呼び出しを避ける必要があります。 初めの。すなわち。 103 おそらく安全ですが、113 おそらく違います。

122 に電話する 135 と呼ぶのと同じように .現在の休憩へのポインターを返し、その量だけ休憩を増やします。

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

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

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

これらのリージョンを積み重ねて、ブレークをリージョンの先頭に巻き戻すことで、最新のリージョンを破棄することもできます。

もう一つ...

163 178 よりも 2 文字短いため、コード ゴルフにも役立ちます。 .


最小限の実行可能な例

<ブロック引用>

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

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

確認しないと、セグメンテーション フォールトが発生する可能性があります。

189 なし :

#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;
}

198 で :

#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 アップストリーム。

上記は 200 がなくても新しいページにヒットせず、セグメンテーション違反にもならない可能性があります 、したがって、これは 16MiB を割り当て、211 なしで segfault する可能性が非常に高い、より積極的なバージョンです。 :

#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 でテスト済み。

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

224 より前 :

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

238 の後 :

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

248 の後 :

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

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

250 の両方が必要な理由 と 262 ?

271 もちろん 287 で実装できます + オフセット計算、どちらも便宜上存在します。

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

12  common  brk         __x64_sys_brk

303 です POSIX?

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

削除は 330 の導入によるものと思われます 、これは、複数の範囲を割り当て、より多くの割り当てオプションを可能にするスーパーセットです。

348 を使用すべき有効なケースはないと思います 354 の代わりに または 366

376 vs 385

396 403 を実装する古い可能性の 1 つです。 .

412 423 を実装するために現在すべての POSIX システムが使用している可能性が高い、より厳密に強力な新しいメカニズムです。 .これは最小限の実行可能な 439 です メモリ割り当ての例

442 を混ぜてもいいですか と malloc?

452 の場合 465 で実装されています 、 475 以来、それがどうして爆破できないのか、私にはわかりません 単一のメモリ範囲のみを管理します。

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

  • https://www.gnu.org/software/libc/manual/html_mono/libc.html#Resizing-the-Data-Segment

486 以来、おそらくそこでうまくいくでしょう 494 に使用される可能性があります .

こちらもご覧ください:

  • brk/sbrk の安全でない/レガシーは何ですか?
  • sbrk(0) を 2 回呼び出すと異なる値が返されるのはなぜですか?

詳細

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

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


あなたが投稿した図では、「ブレーク」 - 509 によって操作されたアドレス および 512 —ヒープの一番上にある点線です。

あなたが読んだドキュメンテーションは、これを「データセグメント」の終わりとして説明しています。 ) Unix データ セグメントはヒープと連続していました。プログラムの開始前に、カーネルは「テキスト」および「データ」ブロックをアドレス 0 から RAM にロードし (実際にはアドレス 0 の少し上にあるため、NULL ポインターは実際には何も指していません)、ブレーク アドレスを次のように設定します。データセグメントの終わり。 538 への最初の呼び出し 544 を使用します 分割を移動し、間にヒープを作成します 図に示すように、データセグメントの先頭と新しい上位のブレークアドレス、およびその後の 555 の使用 必要に応じてヒープを大きくするために使用します。

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

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

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

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

ブレークはまだヒープの上限です。しかし、私が示していなかったのは、 569 で作成された、どこかの黒い場所に数十の独立したメモリ割り当てがある可能性があることです。 574 の代わりに . (OS は、これらを 585 から遠ざけようとします。 衝突しないようにします)


Linux
  1. errno !=EINTR かどうかを確認しています:どういう意味ですか?

  2. 実行権限は何をしますか?

  3. Linux でのライブラリ呼び出しとシステム呼び出しの違いは何ですか?

  1. Linuxシステムの現在のランレベルはどれくらいですか?

  2. -e は bash シバンで何をしますか?

  3. init は正確に何をしますか?

  1. 「lc_all=c」は何をしますか?

  2. Uniq -uのポイントとそれは何をしますか?

  3. `.bashrc` などの 'rc' は何を意味していますか?