web-dev-qa-db-fra.com

Mmap () un grand fichier entier

J'essaie de "mmap" un fichier binaire (~ 8 Go) en utilisant le code suivant (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 est compilé en utilisant gcc -std=c99 test.c -o test et file des retours de 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

Bien que cela fonctionne bien pour les petits fichiers, j'obtiens un défaut de segmentation lorsque j'essaye d'en charger un gros. Le programme renvoie en fait:

Size: 8274324021 
mmap: Cannot allocate memory

J'ai réussi à mapper le fichier entier en utilisant boost :: iostreams :: mapped_file mais je veux le faire en utilisant C et les appels système. Quel est le problème avec mon code?

64
Emer

MAP_PRIVATE les mappages nécessitent une réservation de mémoire, car l'écriture sur ces pages peut entraîner des allocations de copie sur écriture. Cela signifie que vous ne pouvez pas mapper quelque chose de trop grand que votre RAM physique + swap. Essayez d'utiliser un MAP_SHARED mappage à la place. Cela signifie que les écritures sur le mappage seront reflétées sur le disque - en tant que tel, le noyau sait qu'il peut toujours libérer de la mémoire en effectuant une réécriture, donc cela ne vous limitera pas.

Je note également que vous mappez avec PROT_WRITE, mais vous continuez ensuite à lire le mappage de la mémoire. Vous avez également ouvert le fichier avec O_RDONLY - cela peut être un autre problème pour vous; vous devez spécifier O_RDWR si vous souhaitez utiliser PROT_WRITE avec MAP_SHARED.

Pour ce qui est de PROT_WRITE uniquement, cela fonctionne sur x86, car x86 ne prend pas en charge les mappages en écriture seule, mais peut provoquer des erreurs de segmentation sur d'autres plates-formes. Demande PROT_READ|PROT_WRITE - ou, si vous avez seulement besoin de lire, PROT_READ.

Sur mon système (VPS avec 676 Mo de RAM, 256 Mo de swap), j'ai reproduit votre problème; changer en MAP_SHARED entraîne une erreur EPERM (car je ne suis pas autorisé à écrire dans le fichier de sauvegarde ouvert avec O_RDONLY). Passer à PROT_READ et MAP_SHARED permet au mappage de réussir.

Si vous devez modifier des octets dans le fichier, une option serait de rendre privé uniquement les plages du fichier dans lequel vous allez écrire. Autrement dit, munmap et remappez avec MAP_PRIVATE les zones dans lesquelles vous souhaitez écrire. Bien sûr, si vous avez l'intention d'écrire dans le fichier entier , vous avez besoin de 8 Go de mémoire pour le faire.

Vous pouvez également écrire 1 à /proc/sys/vm/overcommit_memory . Cela permettra à la demande de mappage de réussir; Cependant, gardez à l'esprit que si vous essayez réellement d'utiliser les 8 Go de mémoire COW, votre programme (ou un autre programme!) sera tué par le tueur OOM.

63
bdonlan

Vous n'avez pas assez de mémoire virtuelle pour gérer ce mappage.

Par exemple, j'ai ici une machine avec 8G de RAM et ~ 8G de swap (donc 16G de mémoire virtuelle totale disponible).

Si j'exécute votre code sur un instantané VirtualBox qui est ~ 8G, cela fonctionne très 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 

Maintenant, si je laisse tomber le swap, je me retrouve avec une mémoire totale de 8G. (Don't exécutez ceci sur un serveur actif.) Et le résultat est:

$ Sudo swapoff -a
$ ./a.out /media/vms/.../snap.vdi
Size: 9820000256 
mmap: Cannot allocate memory

Assurez-vous donc que vous disposez de suffisamment de mémoire virtuelle pour contenir ce mappage (même si vous ne touchez que quelques pages dans ce fichier).

4
Mat

Linux (et apparemment quelques autres systèmes UNIX) ont le MAP_NORESERVE drapeau pour mmap (2) , qui peut être utilisé pour activer explicitement la surcharge de l'espace d'échange. Cela peut être utile lorsque vous souhaitez mapper un fichier plus grand que la quantité de mémoire disponible sur votre système.

Ceci est particulièrement pratique lorsqu'il est utilisé avec MAP_PRIVATE et n'a l'intention d'écrire que sur une petite partie de la plage mappée en mémoire, car cela déclencherait autrement la réservation d'espace de swap de tout le fichier (ou obligerait le système à renvoyer ENOMEM, si la surcharge du système n'a pas été activé et vous dépassez la mémoire disponible du système).

Le problème à surveiller est que si vous écrivez sur une grande partie de cette mémoire, la réservation d'espace d'échange paresseux peut entraîner la consommation de tous les RAM et swap sur le système, éventuellement déclencher le tueur OOM (Linux) ou faire en sorte que votre application reçoive un SIGSEGV.

4
dcoles