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

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

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

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

Ответы [ 6 ]

37 голосов
/ 26 мая 2009

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

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

14 голосов
/ 26 мая 2009

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.)

9 голосов
/ 26 мая 2009

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

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

Это начинается немного не по теме (а затем я свяжу это с вашим вопросом), но то, что происходит, похоже на то, что происходит, когда вы запускаете процесс в 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;
}
5 голосов
/ 27 мая 2009

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

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

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

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

4 голосов
/ 26 мая 2009

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

2 голосов
/ 26 мая 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...