Linuxでmalloc(1024 * 1024 * 1024)
を実行した場合、mallocは実際に何をしますか?
(フリーリストをたどり、必要に応じて新しいマッピングを作成することによって)割り当てに仮想アドレスを割り当てることは確かですが、実際には1 GiB相当のスワップページを作成しますか?またはそれはmprotect
アドレス範囲であり、mmap
のように実際にそれらに触れたときにページを作成しますか?
(Linuxを指定しているのは、 標準 がこれらの種類の詳細について沈黙しているためですが、他のプラットフォームでも同様に機能することを知りたいと思います。)
Linuxは、遅延ページ割り当て、別名を行います。 「楽観的なメモリ割り当て」。 mallocから取得したメモリは何もバックアップされておらず、それに触れると、実際にOOM状態になる可能性があります(要求したページのスワップスペースがない場合)。その場合 プロセスが不用意に終了しました 。
たとえば、 http://www.linuxdevcenter.com/pub/a/linux/2006/11/30/linux-out-of-memory.html
9。メモリ( Linuxカーネルの一部、Linuxカーネルに関するいくつかのコメント Andries Brouwerによる)は良いドキュメントです。
Linuxの物理メモリと実際のメモリの処理を示し、カーネルの内部を説明する次のプログラムが含まれています。
通常、最初のデモプログラムは、malloc()がNULLを返す前に非常に大量のメモリを取得します。 2番目のデモプログラムは、以前に取得したメモリが実際に使用されるため、取得するメモリの量がはるかに少なくなります。 3番目のプログラムは、最初のプログラムと同じ量を取得し、メモリを使用するときに強制終了されます。
デモプログラム1:メモリを使用せずに割り当てます。
#include <stdio.h>
#include <stdlib.h>
int main (void) {
int n = 0;
while (1) {
if (malloc(1<<20) == NULL) {
printf("malloc failure after %d MiB\n", n);
return 0;
}
printf ("got %d MiB\n", ++n);
}
}
デモプログラム2:メモリを割り当て、実際にすべてに触れます。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (void) {
int n = 0;
char *p;
while (1) {
if ((p = malloc(1<<20)) == NULL) {
printf("malloc failure after %d MiB\n", n);
return 0;
}
memset (p, 0, (1<<20));
printf ("got %d MiB\n", ++n);
}
}
デモプログラム3:最初に割り当て、後で使用します。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define N 10000
int main (void) {
int i, n = 0;
char *pp[N];
for (n = 0; n < N; n++) {
pp[n] = malloc(1<<20);
if (pp[n] == NULL)
break;
}
printf("malloc failure after %d MiB\n", n);
for (i = 0; i < n; i++) {
memset (pp[i], 0, (1<<20));
printf("%d\n", i+1);
}
return 0;
}
( Solaris のように正常に機能しているシステムでは、3つのデモプログラムは同じ量のメモリを取得してクラッシュしませんが、malloc()がNULLを返すことを確認してください。)
私は同じ主題に関する同様の投稿にこの答えを与えました:
これは少し主題から外れ始めます(そして私はそれをあなたの質問に結び付けます)が、起こっていることはあなたがLinuxでプロセスをフォークしたときに起こることと似ています。フォークする場合、コピーオンライトと呼ばれるメカニズムがあります。これは、メモリが書き込まれるときにのみ、新しいプロセスのメモリスペースをコピーします。このようにして、フォークされたプロセスexecがすぐに新しいプログラムである場合、元のプログラムのメモリをコピーするオーバーヘッドを節約できます。
質問に戻ると、考え方は似ています。他の人が指摘しているように、メモリを要求するとすぐに仮想メモリスペースが得られますが、実際のページはそれらに書き込むときにのみ割り当てられます。
これの目的は何ですか?これは基本的に、メモリのスケジューリングを多かれ少なかれ一定時間の操作にします。BigO(1)操作の代わりにBig O(n) Linuxスケジューラーは、1つの大きなチャンクで実行するのではなく、ワークアウトを分散します)。
私が何を意味するかを示すために、私は次の実験をしました:
rbarnes@rbarnes-desktop:~/test_code$ time ./bigmalloc
real 0m0.005s
user 0m0.000s
sys 0m0.004s
rbarnes@rbarnes-desktop:~/test_code$ time ./deadbeef
real 0m0.558s
user 0m0.000s
sys 0m0.492s
rbarnes@rbarnes-desktop:~/test_code$ time ./justwrites
real 0m0.006s
user 0m0.000s
sys 0m0.008s
Bigmallocプログラムは2000万intを割り当てますが、それらに対しては何もしません。 deadbeefは各ページに1つのintを書き込み、19531の書き込みを行い、justwritesは19531のintを割り当て、それらをゼロにします。ご覧のとおり、deadbeefの実行にはbigmallocの約100倍、justwriteの約50倍の時間がかかります。
#include <stdlib.h>
int main(int argc, char **argv) {
int *big = malloc(sizeof(int)*20000000); // Allocate 80 million bytes
return 0;
}
。
#include <stdlib.h>
int main(int argc, char **argv) {
int *big = malloc(sizeof(int)*20000000); // Allocate 80 million bytes
// Immediately write to each page to simulate an all-at-once allocation
// assuming 4k page size on a 32-bit machine.
for (int* end = big + 20000000; big < end; big += 1024)
*big = 0xDEADBEEF;
return 0;
}
。
#include <stdlib.h>
int main(int argc, char **argv) {
int *big = calloc(sizeof(int), 19531); // Number of writes
return 0;
}
Mallocは、libcによって管理されるブロックからメモリを割り当てます。追加のメモリが必要な場合、ライブラリはbrkシステムコールを使用してカーネルに移動します。
カーネルは、仮想メモリのページを呼び出しプロセスに割り当てます。ページは、プロセスが所有するリソースの一部として管理されます。メモリがbrkされている場合、物理ページは割り当てられません。プロセスがbrkされたページの1つにあるメモリ位置にアクセスすると、ページフォールトが発生します。カーネルは、仮想メモリが割り当てられていることを検証し、物理ページを仮想ページにマップします。
ページ割り当ては書き込みに限定されず、コピーオンライトとはまったく異なります。読み取りまたは書き込みにアクセスすると、ページフォールトが発生し、物理ページがマッピングされます。
スタックメモリは自動的にマッピングされることに注意してください。つまり、スタックで使用される仮想メモリにページをマップするために、明示的なbrkは必要ありません。
Windowsでは、ページはコミットされます(つまり、使用可能な空きメモリが減少します)が、ページに触れる(読み取りまたは書き込み)まで実際には割り当てられません。