2016-07-25 34 views
19

Jak wiadomo: http://linux.die.net/man/3/mallocDlaczego możemy przydzielić tablicę 1 PB (10^15) i uzyskać dostęp do ostatniego elementu, ale nie możemy go zwolnić?

Domyślnie Linux następująco optymistyczną strategię alokacji pamięci. Oznacza to, że gdy malloc() zwraca wartość inną niż NULL, nie ma gwarancji, że pamięć jest naprawdę dostępna. Jeśli okaże się, że w systemie brak pamięci, jeden lub więcej procesów zostanie zabitych przez zabójcę OOM z .

I możemy z powodzeniem przeznaczyć 1 petabajta VMA (Virtual obszaru pamięci) za pomocą malloc(petabyte);: http://ideone.com/1yskmB

#include <stdio.h> 
#include <stdlib.h> 

int main(void) { 

    long long int petabyte = 1024LL * 1024LL * 1024LL * 1024LL * 1024LL; // 2^50 
    printf("petabyte %lld \n", petabyte); 

    volatile char *ptr = (volatile char *)malloc(petabyte); 
    printf("malloc() - success, ptr = %p \n", ptr); 

    ptr[petabyte - 1LL] = 10; 
    printf("ptr[petabyte - 1] = 10; - success \n"); 

    printf("ptr[petabyte - 1] = %d \n", (int)(ptr[petabyte - 1LL])); 

    free((void*)ptr); // why the error is here? 
    //printf("free() - success \n"); 

    return 0; 
} 

Wynik:

Error time: 0 memory: 2292 signal:6 
petabyte 1125899906842624 
malloc() - success, ptr = 0x823e008 
ptr[petabyte - 1] = 10; - success 
ptr[petabyte - 1] = 10 

I możemy z powodzeniem uzyskać dostęp (sklep/ładowanie) do ostatniego elementu petabajtów, ale dlaczego otrzymujemy błąd na free((void*)ptr);?

Uwaga: https://en.wikipedia.org/wiki/Petabyte

  • 1000^5 PB petabajta
  • 1024^5 PiB pebibajt - Używam go

Tak naprawdę, jeśli chcemy przeznaczyć ponad RAM + swapu i do pracy przy limicie overcommit_memory, możemy przydzielić pamięć za pomocą VirtualAllocEx() w systemie Windows lub mmap() w systemie Linux, na przykład:

+3

Jakiś powód, który kwalifikujesz jako "lotny"? Jest to bardzo prawdopodobne bzdury i uniemożliwia optymalizacje. – Olaf

+2

@Olaf 'volatile', aby uniknąć optymalizacji, w której nie ma dostępu do pamięci, i będzie działać w rejestrach. Aby pokazać, że naprawdę pracujemy z pamięcią. – Alex

+0

To powiedziawszy, po prostu odkurzenie, '(int)' rzutowanie nie jest wymagane w '...% d \ n", (int) (ptr [petabajt - 1LL])); ', to jest niejawne –

Odpowiedz

23

Uważam, że problemem jest to, że malloc() nie bierze long long int jako argument. Zajmuje size_t.

Po zmianie kodu w celu zdefiniowania petabyte jako size_t Twój program nie zwraca już wskaźnika z malloc. Zamiast tego zawiedzie.

Myślę, że twoje ustawienie dostępu do tablicy petabajt od 1 do 10 zapisuje daleko, daleko poza tablicą malloc zwróconą. To jest katastrofa.

Zawsze używaj prawidłowych typów danych podczas wywoływania funkcji.

użyć tego kodu, aby zobaczyć, co się dzieje:

long long int petabyte = 1024LL * 1024LL * 1024LL * 1024LL * 1024LL; 
size_t ptest = petabyte; 
printf("petabyte %lld %lu\n", petabyte, ptest); 

Gdybym kompilacji w trybie 64 bitowym to nie malloc 1 petabajta. Jeśli kompiluję w trybie 32-bitowym, to z powodzeniem wyprowadza 0 bitów, następnie próbuje pisać poza tablicą i segfaultami.

+2

Nie zgadzam się z "... Twój program nie zwraca już wskaźnika z malloc. Zamiast tego zawodzi ". Powinno" 1024LL * 1024LL * 1024LL * 1024LL * 1024LL' przekroczyć "SIZE_MAX", oczekiwana wartość 'size_t petabyte' będzie wynosić 0. Z pewnością' malloc (0) 'nie zawiedzie.' Malloc (0) 'zwraca wskaźnik lub' NULL' i oba są poprawnymi wartościami dla wskaźnika i nie wskazują OOM.Błąd jest w późniejszym kodzie próbującym usunąć odwołanie 'ptr'. – chux

+0

@chux: Na moim komputerze 64-bitowym odmawia alokacji petabajt, bez względu na to, czy overcommit_memory jest ustawione na 0 czy 1. Co do oczekiwanej wartości wynoszącej 0, dlaczego tak uważasz? 2^50 zawija się w 2^32 i nie widzę, jak to się kiedykolwiek stanie. Pamiętaj, że podpisane całkowite zawijanie jest * niezdefiniowanym zachowaniem * w C. –

+2

Nie jest to _signed_ całkowite zawijanie. 'Size_t' jest typem bez znaku. Przypisanie 2^50 do' size_t' jest dobrze zdefiniowane bez względu na jego szerokość bitową. size_t' jako 50-bitowy lub węższy typ, 'size_t petabyte = 1024LL ...' będzie 0. – chux

9

(To nie jest odpowiedź, ale ważna uwaga na nikogo pracy z dużymi zbiorami danych w Linuksie)

To nie jest to, jak użyć bardzo duże - rzędu terabajtów i up - zestawów danych w Linuksie .

Podczas korzystania malloc() lub mmap() (biblioteka GNU C użyje mmap() wewnętrznie dla dużych przydziałów tak) przydzielić prywatnej pamięci, jądro ogranicza wielkość do wielkości (teoretycznie) dostępnej pamięci RAM i swap, pomnożonej przez overcommitu czynnik.

Po prostu wiemy, że większy niż RAM zbiory danych mogą być zamienione na zewnątrz, więc wielkość bieżącej wymiany wpłynie jak duże przydziały są dozwolone.

Aby to obejść, tworzymy plik, który ma służyć jako "wymiana" danych i mapuje go przy użyciu flagi MAP_NORESERVE. To mówi jądru, że nie chcemy używać standardowej wymiany dla tego odwzorowania. (Oznacza to również, że jeśli z jakiegokolwiek powodu jądro nie może uzyskać nowej strony kopii zapasowej, aplikacja otrzyma sygnał SIGSEGV i zginie).

Większość systemów plików w systemie Linux obsługuje pliki rozrzedzone. Oznacza to, że można mieć plik terabajt wielkości, że zajmuje tylko kilka kilobajtów rzeczywistej przestrzeni dyskowej, jeśli większość jego zawartość nie są zapisywane (a zatem są zerami). (Tworzenie rzadkich plików jest łatwe, po prostu pomijane są długie zera, a dziurkowanie jest trudniejsze, ponieważ zapis zer nie używa normalnej przestrzeni dyskowej, należy zamiast tego użyć innych metod).

Oto przykładowy program które można wykorzystać do poszukiwań, mapfile.c:

#define _POSIX_C_SOURCE 200809L 
#define _GNU_SOURCE 
#include <stdlib.h> 
#include <unistd.h> 
#include <fcntl.h> 
#include <sys/mman.h> 
#include <string.h> 
#include <errno.h> 
#include <stdio.h> 

int main(int argc, char *argv[]) 
{ 
    const char *filename; 
    size_t   page, size; 
    int   fd, result; 
    unsigned char *data; 
    char   dummy; 

    if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { 
     fprintf(stderr, "\n"); 
     fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]); 
     fprintf(stderr, "  %s MAPFILE BYTES\n", argv[0]); 
     fprintf(stderr, "\n"); 
     return EXIT_FAILURE; 
    } 

    page = sysconf(_SC_PAGESIZE); 
    if (page < 1) { 
     fprintf(stderr, "Unknown page size.\n"); 
     return EXIT_FAILURE; 
    } 

    filename = argv[1]; 
    if (!filename || !*filename) { 
     fprintf(stderr, "No map file name specified.\n"); 
     return EXIT_FAILURE; 
    } 

    if (sscanf(argv[2], " %zu %c", &size, &dummy) != 1 || size < 3) { 
     fprintf(stderr, "%s: Invalid size in bytes.\n", argv[2]); 
     return EXIT_FAILURE; 
    } 

    if (size % page) { 
     /* Round up to next multiple of page */ 
     size += page - (size % page); 
     fprintf(stderr, "Adjusted to %zu pages (%zu bytes)\n", size/page, size); 
    } 

    do { 
     fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600); 
    } while (fd == -1 && errno == EINTR); 
    if (fd == -1) { 
     fprintf(stderr, "Cannot create %s: %s.\n", filename, strerror(errno)); 
     return EXIT_FAILURE; 
    } 

    do { 
     result = ftruncate(fd, (off_t)size); 
    } while (result == -1 && errno == EINTR); 
    if (result == -1) { 
     fprintf(stderr, "Cannot resize %s: %s.\n", filename, strerror(errno)); 
     unlink(filename); 
     close(fd); 
     return EXIT_FAILURE; 
    } 

    data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0); 
    if ((void *)data == MAP_FAILED) { 
     fprintf(stderr, "Mapping failed: %s.\n", strerror(errno)); 
     unlink(filename); 
     close(fd); 
     return EXIT_FAILURE; 
    } 

    fprintf(stderr, "Created file '%s' to back a %zu-byte mapping at %p successfully.\n", filename, size, (void *)data); 

    fflush(stdout); 
    fflush(stderr); 

    data[0] = 1U; 
    data[1] = 255U; 

    data[size-2] = 254U; 
    data[size-1] = 127U; 

    fprintf(stderr, "Mapping accessed successfully.\n"); 

    munmap(data, size); 
    unlink(filename); 
    close(fd); 

    fprintf(stderr, "All done.\n"); 
    return EXIT_SUCCESS; 
} 

skompilować go za pomocą np

gcc -Wall -O2 mapfile.c -o mapfile 

i uruchomić go bez argumentów, aby zobaczyć użycie.

Program po prostu konfiguruje mapowanie (dostosowanego do wielokrotności rozmiaru bieżącej strony), a dostęp do dwóch pierwszych i dwóch ostatnich bajtów mapowania.

Na moim komputerze, pracując z jądrem 4.2.0-42-generiC# 49 ~ 14.04.1-Ubuntu na systemie x86-64, na systemie plików ext4, nie mogę zmapować pełnego petabajta. Wydaje się, że maksimum wynosi około 17 592,186,040,320 bajtów (2 -4096) - 16 TiB - 4 KiB - co daje 4 294 967 296 stron o 4096 bajtach (2 stron o 2 bajtów każdy). Wygląda na to, że system plików ext4 narzuca limit, ponieważ awaria występuje w wywołaniu ftruncate() (zanim mapowanie zostanie wypróbowane).

(Na tmpfs mogę uzyskać do około 140,187,732,541,440 bajtów lub 127,5 TiB, ale to tylko chwyt, ponieważ tmpfs jest wspierany przez RAM i swap, a nie rzeczywiste urządzenie magazynujące, więc nie jest to opcja dla prawdziwych dużych danych . praca wydaje mi się przypomnieć XFS będzie pracować dla naprawdę dużych plików, ale jestem zbyt leniwy, aby sformatować partycję lub nawet zajrzeć do specyfikacji; nie sądzę, ktoś rzeczywiście czytać ten post, mimo że informacje tu był bardzo przydatne dla mnie w ciągu ostatnich kilkunastu lat)

Oto jak to wygląda na przykład bieg na moim komputerze (przy użyciu powłoki bash).

$ ./mapfile datafile $[(1<<44)-4096] 
Created file 'datafile' to back a 17592186040320-byte mapping at 0x6f3d3e717000 successfully. 
Mapping accessed successfully. 
All done. 

.

+0

Dziękujemy! Czy możemy po prostu zamapować '/ dev/zero' (lub użyć MAP_ANONYMOUS) zamiast' plik_danych', aby przydzielić nieprzesłoniętą tablicę i obejść ograniczenia systemu plików: 2^32 stron i obsługujących rozrzucone pliki? Jak powiedział tam: http://stackoverflow.com/questions/2782628/any-way-to-reserve-but-not-commit-memory-in-linux/2782910#2782910 – Alex

+1

'Nie sądzę, że ktokolwiek będzie rzeczywiście czytał ten post "Przeczytałem cały post i dowiedziałem się czegoś. – pandalion98

+0

@Alex: Nie, niezupełnie. W końcu chodzi o to, że strony z jądrem * strona na zewnątrz * nie są dostępne, ponieważ nie mamy wystarczającej ilości pamięci RAM dla całego zestawu danych. Zamiast tego możesz po prostu użyć 'mmap (NULL, bajtów, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE, -1, 0)' aby uzyskać prywatne anonimowe strony, aż do liczby obsługiwanych przez jądro (chyba że są ograniczone do bieżącego procesu). Jeśli skończy Ci się pamięć RAM, otrzymasz "SIGSEGV", ponieważ nie ma gdzie eksmitować nieużywanych stron, aby zrobić miejsce na nowe. –