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

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

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

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

6 ответов

Решение

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. Вторая демонстрационная программа получит намного меньший объем памяти, теперь, когда фактически использованная ранее память используется. Третья программа получит столько же, сколько первая программа, и затем ее убивают, когда она хочет использовать свою память.

Демонстрационная программа 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() возвращает NULL.)

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

Некоторые распределители ленивы?

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

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

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

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

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 выделяет 20 миллионов целых, но ничего с ними не делает. deadbeef записывает по одному int на каждую страницу, в результате чего 19531 пишет, а justwrites выделяет 19531 целое и обнуляет их. Как видите, выполнение deadbeef занимает в 100 раз больше времени, чем bigmalloc, и примерно в 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'd, возникает сбой страницы. Ядро проверяет, что виртуальная память была выделена, и приступает к сопоставлению физической страницы с виртуальной страницей.

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

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

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

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

Другие вопросы по тегам