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?
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.
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).
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
.