Mmap () un archivo grande completo


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

Prueba.c se compila usando gcc -std=c99 test.c -o test y file de test returns: 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, tengo un error de segmentación cuando intento cargar uno grande. El programa realmente devuelve:

Size: 8274324021 
mmap: Cannot allocate memory

Logré mapear todo el archivo usando boost::iostreams::mapped_file pero quiero hacerlo usando C y llamadas al sistema. ¿Qué tiene de malo mi código?

 63
Author: sigjuice, 2011-08-28

3 answers

MAP_PRIVATE las asignaciones requieren una reserva de memoria, ya que escribir en estas páginas puede resultar en asignaciones de copia al escribir. Esto significa que no puedes mapear algo mucho más grande que tu ram + swap físico. Intente usar una asignación MAP_SHARED en su lugar. Esto significa que las escrituras en la asignación se reflejarán en el disco-como tal, el núcleo sabe que siempre puede liberar memoria haciendo writeback, por lo que no lo limitará.

También noto que estás mapeando con PROT_WRITE, pero luego sigues y lees desde el mapeo de memoria. También abrió el archivo con O_RDONLY - esto en sí puede ser otro problema para usted; debe especificar O_RDWR si desea usar PROT_WRITE con MAP_SHARED.

En cuanto a PROT_WRITE solo, esto funciona en x86, porque x86 no admite asignaciones de solo escritura, pero puede causar errores de segmento en otras plataformas. Request PROT_READ|PROT_WRITE - o, si solo necesita leer, PROT_READ.

En mi sistema (VPS con 676MB RAM, 256MB swap), reproduje su problema; cambiar a MAP_SHARED resulta en un EPERM error (ya que no se me permite escribir en el archivo de respaldo abierto con O_RDONLY). Cambiar a PROT_READ y MAP_SHARED permite que la asignación tenga éxito.

Si necesita modificar bytes en el archivo, una opción sería hacer privados solo los rangos del archivo en el que va a escribir. Es decir, munmap y remapea con MAP_PRIVATE las áreas en las que quieres escribir. Por supuesto, si tiene la intención de escribir en el archivo completo, entonces necesita 8 GB de memoria para hacerlo.

Alternativamente, puede escribir 1 a /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 8 GB completos de memoria COW, su programa (o algún otro programa!) será asesinado por el asesino OOM.

 63
Author: bdonlan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-28 17:36:02

No tiene suficiente memoria virtual para manejar esa asignación.

Como ejemplo, tengo una máquina aquí con 8G RAM y ~8G swap (así que 16G de memoria virtual total disponible).

Si corro tu 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 quedo con 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 unas pocas páginas en ese archivo).

 4
Author: Mat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-28 16:58:13

Linux (y aparentemente algunos otros sistemas UNIX) tienen la bandera MAP_NORESERVE para mmap(2), que se puede usar para habilitar explícitamente el sobrecompletado de espacio de intercambio. Esto puede ser útil cuando desea asignar un archivo mayor que la cantidad de memoria libre 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 asignado de memoria, ya que de lo contrario activaría la reserva de espacio de intercambio de todo el archivo (o causaría la system to return ENOMEM, si no se ha habilitado el sobrecompletado de todo el sistema y se 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 perezoso puede hacer que su aplicación consuma toda la RAM gratuita y el intercambio en el sistema, eventualmente desencadenando el asesino de OOM (Linux) o causando que su aplicación reciba un SIGSEGV.

 4
Author: dcoles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-04 01:24:12