c linux malloc mmap virtual-address-space

¿Por qué podemos asignar una matriz de 1 PB(10 ^ 15) y obtener acceso al último elemento, pero no podemos liberarlo?



linux malloc (2)

Como se sabe: http://linux.die.net/man/3/malloc

Por defecto, Linux sigue una estrategia de asignación de memoria optimista. Esto significa que cuando malloc () devuelve non-NULL, no hay garantía de que la memoria realmente esté disponible. En caso de que el sistema se quede sin memoria, el asesino de OOM matará uno o más procesos.

Y podemos asignar con éxito 1 Petabyte de VMA (área de memoria virtual) utilizando 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; }

Resultado:

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

Y podemos obtener acceso (almacenamiento / carga) al último miembro de petabyte, pero ¿por qué obtenemos un error en free((void*)ptr); ?

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

  • 1000 ^ 5 PB petabyte
  • 1024 ^ 5 PiB pebibyte - Lo uso

Entonces, si realmente queremos asignar más que RAM + swap y overcommit_memory límite de overcommit_memory , entonces podemos asignar memoria usando VirtualAllocEx() en Windows, o mmap() en Linux, por ejemplo:

  • 16 TiB (16 * 2 ^ 40 bytes) luego podemos usar el ejemplo de la respuesta de Nominal Animal: https://stackoverflow.com/a/38574719/1558037
  • 127 TiB (127 * 2 ^ 40 bytes), entonces podemos usar mmap() con MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS MAP_NORESERVE | MAP_PRIVATE | MAP_ANONYMOUS y fd=-1 : http://coliru.stacked-crooked.com/a/c69ce8ad7fbe4560

(Esto no es una respuesta, pero es una nota importante para cualquiera que trabaje con grandes conjuntos de datos en Linux)

No es así como se usa mucho, en el orden de los terabytes y los conjuntos de datos ascendentes en Linux.

Cuando use malloc() o mmap() (la biblioteca GNU C usará mmap() internamente para grandes asignaciones de todos modos) para asignar memoria privada, el kernel limita el tamaño al tamaño de la RAM disponible (teóricamente) disponible y SWAP, multiplicado por El factor de sobrecompromiso.

En pocas palabras, sabemos que los conjuntos de datos más grandes que la memoria RAM deben ser intercambiados, por lo que el tamaño del intercambio actual afectará la forma en que se permiten las grandes asignaciones.

Para MAP_NORESERVE , creamos un archivo para ser utilizado como "intercambio" de los datos y lo MAP_NORESERVE usando la bandera MAP_NORESERVE . Esto le dice al kernel que no queremos usar swap estándar para este mapeo. (También significa que si, por alguna razón, el kernel no puede obtener una nueva página de respaldo, la aplicación obtendrá una señal SIGSEGV y morirá).

La mayoría de los sistemas de archivos en Linux admiten archivos dispersos. Esto significa que puede tener un archivo de tamaño terabyte, que solo toma unos pocos kilobytes de espacio en disco real, si la mayoría de su contenido no está escrito (y por lo tanto son ceros). (Crear archivos dispersos es fácil; simplemente omite tiradas largas de ceros. La perforación de agujeros es más difícil, ya que escribir ceros usa el espacio de disco normal, en su lugar se deben usar otros métodos).

Aquí hay un programa de ejemplo que puede usar para la exploración, 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; }

Compilarlo usando por ejemplo

gcc -Wall -O2 mapfile.c -o mapfile

y ejecutarlo sin argumentos para ver el uso.

El programa simplemente configura una asignación (ajustada a un múltiplo del tamaño de la página actual), y accede a los dos primeros y los últimos dos bytes de la asignación.

En mi máquina, ejecutando un kernel SMP 4.2.0-42-generic # 49 ~ 14.04.1-Ubuntu en x86-64, en un sistema de archivos ext4, no puedo asignar un petabyte completo. El máximo parece ser de aproximadamente 17,592,186,040,320 bytes (2 44 -4096) - 16 TiB - 4 KiB -, que llega a 4,294,967,296 páginas de 4096 bytes (2 32 páginas de 2 12 bytes cada una). Parece que el límite lo impone el sistema de archivos ext4, ya que la falla ocurre en la llamada ftruncate() (incluso antes de que se intente la asignación).

(En un tmpfs puedo obtener hasta 140,187,732,541,440 bytes o 127.5 TiB, pero eso es solo un truco, porque tmpfs está respaldado por RAM y swap, no es un dispositivo de almacenamiento real. Así que no es una opción para el trabajo de big data real. recordar que xfs funcionaría con archivos realmente grandes, pero soy demasiado vago para formatear una partición o incluso para buscar las especificaciones; no creo que nadie lea realmente esta publicación, a pesar de que la información incluida en este documento me ha sido muy útil. en la última década más o menos)

Así es como se ve ese ejemplo de ejecución en mi máquina (usando un shell Bash):

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

.


Creo que su problema es que malloc() no toma long long int como su argumento. Se necesita un size_t .

Después de cambiar su código para definir petabyte como size_t su programa ya no devuelve un puntero de malloc. Falla en su lugar.

Creo que su configuración de acceso a la matriz de petabyte-1 a 10 está escribiendo muy lejos de la matriz malloc devuelta. Ese es el accidente.

Utilice siempre los tipos de datos correctos al llamar a funciones.

Usa este código para ver lo que está pasando:

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

Si compilo en modo de 64 bits, falla a malloc 1 petabyte. Si compilo en modo de 32 bits, mallocs 0 bytes, con éxito, luego intenta escribir fuera de su matriz y segregnos.