transferir - subir archivos gratis
Mmap() un archivo grande completo (3)
Estoy tratando de "mmap" un archivo binario (~ 8Gb) usando el siguiente código (test.c).
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define handle_error(msg) /
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int main(int argc, char *argv[])
{
const char *memblock;
int fd;
struct stat sb;
fd = open(argv[1], O_RDONLY);
fstat(fd, &sb);
printf("Size: %lu/n", (uint64_t)sb.st_size);
memblock = mmap(NULL, sb.st_size, PROT_WRITE, MAP_PRIVATE, fd, 0);
if (memblock == MAP_FAILED) handle_error("mmap");
for(uint64_t i = 0; i < 10; i++)
{
printf("[%lu]=%X ", i, memblock[i]);
}
printf("/n");
return 0;
}
test.c se compila usando gcc -std=c99 test.c -o test
y file
de prueba gcc -std=c99 test.c -o test
: test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not stripped
Aunque esto funciona bien para archivos pequeños, recibo una falla de segmentación cuando intento cargar uno grande. El programa realmente regresa:
Size: 8274324021
mmap: Cannot allocate memory
Me las arreglé para mapear todo el archivo usando boost :: iostreams :: mapped_file, pero quiero hacerlo usando C y las llamadas al sistema. ¿Qué pasa con mi código?
Linux (y aparentemente algunos otros sistemas UNIX) tienen el indicador MAP_NORESERVE
para mmap(2) , que se puede usar para habilitar explícitamente el exceso de espacio de intercambio. Esto puede ser útil cuando desee asignar un archivo más grande que la cantidad de memoria disponible en su sistema.
Esto es particularmente útil cuando se usa con MAP_PRIVATE
y solo tiene la intención de escribir en una pequeña porción del rango mapeado en la memoria, ya que de lo contrario activaría la reserva de espacio de intercambio de todo el archivo (o haría que el sistema devuelva ENOMEM
, si el sistema no se ha comprometido demasiado) ha sido habilitado y usted excede la memoria libre del sistema).
El problema a tener en cuenta es que si escribe en una gran parte de esta memoria, la reserva de espacio de intercambio diferido puede hacer que su aplicación consuma toda la memoria RAM libre e intercambiar en el sistema, lo que eventualmente desencadena el OOM killer (Linux) o haciendo que su aplicación reciba un SIGSEGV
.
No tienes suficiente memoria virtual para manejar esa asignación.
Como ejemplo, tengo una máquina aquí con 8G de RAM, y ~ 8G de intercambio (tan 16G de memoria virtual total disponible).
Si ejecuto su código en una instantánea de VirtualBox que es ~ 8G, funciona bien:
$ ls -lh /media/vms/.../snap.vdi
-rw------- 1 me users 9.2G Aug 6 16:02 /media/vms/.../snap.vdi
$ ./a.out /media/vms/.../snap.vdi
Size: 9820000256
[0]=3C [1]=3C [2]=3C [3]=20 [4]=4F [5]=72 [6]=61 [7]=63 [8]=6C [9]=65
Ahora, si dejo caer el intercambio, me quedan 8G de memoria total. ( No ejecute esto en un servidor activo.) Y el resultado es:
$ sudo swapoff -a
$ ./a.out /media/vms/.../snap.vdi
Size: 9820000256
mmap: Cannot allocate memory
Así que asegúrese de tener suficiente memoria virtual para mantener esa asignación (incluso si solo toca algunas páginas en ese archivo).
MAP_PRIVATE
asignaciones MAP_PRIVATE
requieren una reserva de memoria, ya que la escritura en estas páginas puede dar como resultado asignaciones de copia en escritura. Esto significa que no puede mapear algo mucho más grande que su ram + intercambio físico. Intente utilizar un mapeo MAP_SHARED
lugar. Esto significa que las escrituras en la asignación se reflejarán en el disco; por lo tanto, el kernel sabe que siempre puede liberar memoria haciendo la reescritura, por lo que no lo limitará.
También observo que está mapeando con PROT_WRITE
, pero luego continúa y lee desde la asignación de memoria. También abrió el archivo con O_RDONLY
, esto en sí mismo puede ser otro problema para usted; debe especificar O_RDWR
si desea usar PROT_WRITE
con MAP_SHARED
.
En cuanto a PROT_WRITE
solamente, esto funciona en x86, porque x86 no admite asignaciones de solo escritura, pero puede causar segfaults en otras plataformas. Solicite PROT_READ|PROT_WRITE
- o, si solo necesita leer, PROT_READ
.
En mi sistema (VPS con 676MB RAM, intercambio de 256MB), reproduje su problema; al cambiar a MAP_SHARED
produce un error de EPERM
(ya que no puedo escribir en el archivo de respaldo abierto con O_RDONLY
). Cambiar a PROT_READ
y MAP_SHARED
permite que la asignación MAP_SHARED
éxito.
Si necesita modificar bytes en el archivo, una opción sería hacer privados los rangos del archivo en el que va a escribir. Es decir, munmap
y reasignar con MAP_PRIVATE
las áreas en las que desea escribir. Por supuesto, si tiene la intención de escribir en el archivo completo , necesita 8 GB de memoria para hacerlo.
Alternativamente, puede escribir 1
en /proc/sys/vm/overcommit_memory
. Esto permitirá que la solicitud de asignación tenga éxito; sin embargo, tenga en cuenta que si realmente intenta usar los 8GB completos de memoria COW, su programa (¡o algún otro programa!) será asesinado por el asesino OOM.