特別に指定された匿名のプライベート メモリ マッピングがあります (従来は 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 回呼び出すと異なる値が返されるのはなぜですか?
詳細strong>
内部的には、カーネルはプロセスがそれだけ多くのメモリを持つことができるかどうかを判断し、その使用のためにメモリ ページを割り当てます。
これは、スタックがヒープとどのように比較されるかを説明しています: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
から遠ざけようとします。 衝突しないようにします)