Czy malloc leniwie tworzy strony pomocnicze dla alokacji na Linuksie (i innych platformach)?

Na Linuksie gdybym miał malloc(1024 * 1024 * 1024), to co właściwie robi malloc?

Jestem pewien, że przypisuje wirtualny adres do przydziału(przechodząc po wolnej liście i tworząc nowe mapowanie, jeśli to konieczne), ale czy faktycznie tworzy 1 GiB stron wymiany? A może mprotect zakres adresów i tworzenie stron, gdy faktycznie dotykasz ich tak, jak mmap robi?

(podaję Linuksa, ponieważ standard milczy na tego typu szczegóły, ale chciałbym wiedzieć, co inne platformy również.)

Author: Aaron Maenpaa, 2009-05-26

6 answers

Linux robi odroczoną alokację stron, aka. "optymistyczna alokacja pamięci". Pamięć, którą odzyskujesz z malloc, nie jest wspierana przez nic, a gdy ją dotkniesz, możesz uzyskać warunek OOM (jeśli nie ma miejsca na wymianę dla żądanej strony), w którym to Przypadku Proces jest bezceremonialnie zakończony.

Zobacz na przykład http://www.linuxdevcenter.com/pub/a/linux/2006/11/30/linux-out-of-memory.html

 35
Author: Remus Rusanu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-26 17:50:18

9. Pamięć (część jądro Linuksa, Kilka uwag na temat jądra Linuksa Andries Brouwer) jest dobrym dokumentem.

Zawiera on następujące programy, które demonstrują obsługę fizycznej pamięci Linuksa w porównaniu z rzeczywistą pamięcią i wyjaśniają wewnętrzne mechanizmy jądra.

Zazwyczaj pierwszy program demonstracyjny otrzyma bardzo dużą ilość pamięci zanim malloc () zwróci NULL. Drugi program demo otrzyma znacznie mniejszą ilość pamięć, teraz, gdy wcześniej uzyskana pamięć jest faktycznie używana. Trzeci program otrzyma taką samą dużą ilość jak pierwszy program, a następnie zostanie zabity, gdy chce użyć swojej pamięci.

Program Demo 1: przydzielanie pamięci bez jej używania.

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

Program Demo 2: przydziel pamięć i dotknij wszystkiego.

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

Program Demo 3: najpierw przydzielaj, a później używaj.

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

(na dobrze funkcjonującym systemie, takim jak Solaris, trzy programy demo uzyskują ta sama ilość pamięci i nie powoduje awarii, ale patrz malloc () return NULL.)

 13
Author: Aiden Bell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-02 18:27:41

Odpowiedziałem na podobny post w tym samym temacie:

Czy niektórzy alokatorzy są leniwi?

To zaczyna się trochę od tematu (a potem przywiążę to do twojego pytania), ale to, co się dzieje, jest podobne do tego, co dzieje się, gdy rozwidlasz proces w Linuksie. Podczas rozwidlania istnieje mechanizm zwany copy on write, który kopiuje przestrzeń pamięci dla nowego procesu tylko wtedy, gdy pamięć jest zapisana. W ten sposób jeśli rozwidlony proces exec jest od razu nowym programem to masz zapisano narzut kopiowania oryginalnej pamięci programów.

Wracając do twojego pytania, pomysł jest podobny. Jak zauważyli inni, żądanie pamięci natychmiast otrzymuje wirtualną przestrzeń pamięci, ale rzeczywiste strony są przydzielane tylko podczas zapisu do nich. Jaki jest tego cel? To w zasadzie sprawia, że pamięć mallocingowa jest mniej lub bardziej stałą operacją czasu Big O (1) zamiast dużej operacji O(n) (podobnie jak sposób, w jaki scheduler Linuksa rozprzestrzenia to działa zamiast robić to w jednym dużym kawałku).

Aby zademonstrować co mam na myśli zrobiłem następujący eksperyment:

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
Program bigmalloc przydziela 20 milionów int, ale nic z nimi nie robi. deadbeef zapisuje po jednej int do każdej strony, w wyniku czego 19531 pisze, a justwrites przydziela 19531 int i zeruje je. Jak widać DeaDBeeF trwa około 100 razy dłużej niż bigmalloc i około 50 razy dłużej niż / align = "left" /
#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;
}
 9
Author: Robert S. Barnes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:09:45

Malloc alokuje pamięć z bloków zarządzanych przez libc. Gdy potrzebna jest dodatkowa pamięć, biblioteka trafia do jądra za pomocą wywołania systemowego brk.

Jądro przydziela strony pamięci wirtualnej procesowi wywołującemu. Strony są zarządzane jako część zasobów należących do procesu. Strony fizyczne nie są przydzielane, gdy pamięć jest brk 'D. gdy proces uzyskuje dostęp do dowolnej lokalizacji pamięci w jednej ze stron brk' D, występuje błąd strony. Jądro potwierdza, że pamięć wirtualna został przydzielony i przechodzi do mapowania strony fizycznej do strony wirtualnej.

Przydzielanie stron nie ogranicza się do zapisu i różni się od kopiowania przy zapisie. Każdy dostęp, odczyt lub zapis, powoduje błąd strony i mapowanie strony fizycznej.

Pamiętaj, że pamięć stosu jest automatycznie mapowana. Oznacza to, że jawny brk nie jest wymagany do mapowania stron do pamięci wirtualnej używanej przez stos.

 4
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-27 01:20:25

W systemie Windows strony są przydzielane (tzn. wolna pamięć spada), ale nie zostaną przydzielone, dopóki nie dotkniesz Stron (albo przeczytasz, albo zapiszesz).

 4
Author: Paul Betts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-02 18:30:16

W większości systemów uniksopodobnych zarządza BRK . Maszyna wirtualna dodaje strony po uderzeniu przez procesor. Przynajmniej Linux i BSDs robią to.

 2
Author: Javier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-02 18:29:36