Является ли malloc лениво созданием резервных страниц для размещения на Linux (и других платформах)?

В Linux, если бы я был malloc(1024 * 1024 * 1024), что делает malloc на самом деле?

Я уверен, что он присваивает виртуальный адрес распределению (ходя по свободному списку и при необходимости создавая новое сопоставление), но действительно ли он создает страницы подкачки на 1 гигабайт? Или это mprotect диапазон адресов и создавать страницы, когда вы на самом деле прикасаетесь к ним, например mmap?

(Я указываю Linux, потому что стандарт не говорит об этих подробностях, но мне было бы интересно узнать, какие другие платформы сделайте также.)

Ответы

Ответ 1

Linux делает отложенное распределение страниц, иначе. "оптимистичное распределение памяти". Память, которую вы возвращаете из malloc, не поддерживается ничем, и когда вы ее касаетесь, вы можете фактически получить условие OOM (если нет места подкачки для запрашиваемой страницы), и в этом случае процесс бесцеремонно завершен.

См. например http://www.linuxdevcenter.com/pub/a/linux/2006/11/30/linux-out-of-memory.html

Ответ 2

9. Память (часть Ядро Linux, некоторые замечания по ядру Linux от Andries Brouwer) - хороший документ.

Он содержит следующие программы, которые демонстрируют обработку Linux физической памяти по сравнению с фактической памятью и объясняет внутренность ядра.

Как правило, первая демо-программа получит очень большой объем памяти, прежде чем malloc() вернет NULL. Вторая демо-программа получит гораздо меньший объем памяти, теперь, когда используется ранее полученная память. Третья программа получит такую ​​же большую сумму, как и первая программа, а затем она будет убита, когда захочет использовать ее память.

Демо-программа 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, три демо-программы получают одинаковый объем памяти и не сбой, но см. malloc() return NULL.)

Ответ 3

Я дал ответ на аналогичный пост по тому же вопросу:

Являются ли некоторые распределители ленивыми?

Это немного начинается с темы (и затем я привяжу его к вашему вопросу), но то, что происходит, похоже на то, что происходит, когда вы разыгрываете процесс в Linux. При форкировании существует механизм, называемый copy on write, который копирует только пространство памяти для нового процесса, когда память также написана. Таким образом, если разветвленный процесс сразу же запускает новую программу, вы сохранили накладные расходы на копирование исходной памяти программ.

Возвращаясь к вашему вопросу, идея схожа. Как указывали другие, запрос на память сразу получает виртуальное пространство памяти, но фактические страницы выделяются только при их записи.

Какова цель этого? В основном это делает mallocing memory более или менее постоянной операцией Big O (1) вместо операции Big O (n) (аналогично тому, как планировщик Linux расширяет его, вместо того, чтобы делать это в одном большом фрагменте).

Чтобы продемонстрировать, что я имею в виду, я сделал следующий эксперимент:

[email protected]:~/test_code$ time ./bigmalloc

real    0m0.005s
user    0m0.000s
sys 0m0.004s
[email protected]:~/test_code$ time ./deadbeef

real    0m0.558s
user    0m0.000s
sys 0m0.492s
[email protected]:~/test_code$ time ./justwrites

real    0m0.006s
user    0m0.000s
sys 0m0.008s

Программа bigmalloc выделяет 20 миллионов ints, но ничего не делает с ними. deadbeef записывает один int на каждую страницу, в результате чего в 19531 году записи и justwrites выделяют 19531 ints и нули их. Как вы видите, deadbeef занимает около 100 раз дольше, чем bigmalloc, и примерно в 50 раз дольше, чем justwrites.

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

Ответ 4

В Windows страницы зафиксированы (т.е. свободная доступная память снижается), но они фактически не будут выделены до тех пор, пока вы не коснетесь страниц (читаете или пишите).

Ответ 5

Malloc выделяет память из блоков, управляемых libc. Когда требуется дополнительная память, библиотека переходит в ядро ​​с помощью системного вызова brk.

Ядро выделяет страницы виртуальной памяти вызывающему процессу. Страницы управляются как часть ресурсов, принадлежащих процессу. Физические страницы не выделяются, когда память является brk'd. Когда процесс обращается к любой ячейке памяти на одной из страниц brk'd, возникает ошибка страницы. Ядро проверяет, была ли выделена виртуальная память, и переходит к отображению физической страницы на виртуальную страницу.

Распределение страниц не ограничивается записью и отличается от копирования при записи. Любой доступ, чтение или запись приводит к ошибке страницы и отображению физической страницы.

Обратите внимание, что память стека автоматически отображается. То есть, явный brk не требуется для сопоставления страниц с виртуальной памятью, используемой стеком.

Ответ 6

В большинстве Unix-подобных систем он управляет границей brk. VM добавляет страницы при попадании процессора. По крайней мере, Linux и BSD делают это.