Quand dois-je utiliser mmap pour accéder aux fichiers?

276

Les environnements POSIX offrent au moins deux façons d'accéder aux fichiers. Il y a l'appel standard du système open(), read(), write()et amis, mais il y a aussi la possibilité d'utiliser mmap()pour cartographier le fichier dans la mémoire virtuelle.

Quand est-il préférable d'utiliser l'un sur l'autre? Quels sont leurs avantages individuels qui méritent d'inclure deux interfaces?

Peter Burns
la source
16
Voir aussi mmap () vs blocs de lecture et ce post de Linus Torvalds référencé dans l'une des réponses.
MvG

Réponses:

299

mmapest idéal si plusieurs processus accèdent aux données en lecture seule à partir du même fichier, ce qui est courant dans le type de systèmes de serveurs que j'écris. mmappermet à tous ces processus de partager les mêmes pages de mémoire physique, économisant ainsi beaucoup de mémoire.

mmappermet également au système d'exploitation d'optimiser les opérations de pagination. Par exemple, considérons deux programmes; programme Aqui lit dans un 1MBfichier dans un tampon créant avec malloc, et programme B qui mmapsle fichier 1 Mo en mémoire. Si le système d'exploitation doit échanger une partie de Ala mémoire, il doit écrire le contenu du tampon à échanger avant de pouvoir réutiliser la mémoire. Dans Ble cas, toutes mmaples pages non modifiées peuvent être réutilisées immédiatement car le système d'exploitation sait comment les restaurer à partir du fichier existant dont elles provenaient mmap. (Le système d'exploitation peut détecter les pages non modifiées en marquant initialement les mmappages inscriptibles comme étant en lecture seule et en détectant les erreurs de segmentation , comme dans la stratégie Copier en écriture ).

mmapest également utile pour la communication inter-processus . Vous pouvez mmaputiliser un fichier en lecture / écriture dans les processus qui doivent communiquer, puis utiliser des primitives de synchronisation dans la mmap'drégion (c'est à cela que sert l' MAP_HASSEMAPHOREindicateur).

Un endroit mmappeut être gênant si vous devez travailler avec de très gros fichiers sur une machine 32 bits. Cela est dû au mmapfait qu'il doit trouver un bloc d'adresses contigu dans l'espace d'adressage de votre processus qui est suffisamment grand pour s'adapter à toute la plage du fichier en cours de mappage. Cela peut devenir un problème si votre espace d'adressage devient fragmenté, où vous pourriez avoir 2 Go d'espace d'adressage libre, mais aucune plage individuelle ne peut s'adapter à un mappage de fichier de 1 Go. Dans ce cas, vous devrez peut-être mapper le fichier en plus petits morceaux que vous ne le souhaitez.

Une autre gêne potentielle avec mmapen remplacement de la lecture / écriture est que vous devez commencer votre mappage sur des décalages de la taille de la page. Si vous voulez simplement obtenir des données en offsetX vous devrez corriger cet offset pour qu'il soit compatible avec mmap.

Et enfin, lecture / écriture sont la seule façon que vous pouvez travailler avec certains types de fichiers. mmapne peut pas être utilisé sur des choses comme les tuyaux et les tys .

Don Neufeld
la source
10
Pouvez-vous utiliser mmap () sur des fichiers en croissance? Ou la taille est-elle fixée au moment où vous allouez la mémoire / le fichier mmap ()?
Jonathan Leffler
29
Lorsque vous effectuez l'appel mmap, vous devez spécifier une taille. Donc, si vous voulez faire quelque chose comme une opération de queue, ce n'est pas très approprié.
Don Neufeld
5
Afaik MAP_HASSEMAPHOREest spécifique à BSD.
Patrick Schlüter
6
@JonathanLeffler Vous pouvez certainement utiliser mmap () sur des fichiers en croissance, mais vous devez appeler à nouveau mmap () avec la nouvelle taille lorsque le fichier atteint la limite de l'espace que vous avez initialement alloué. PosixMmapFile de LevelDB vous donne un bon exemple. Mais il a cessé d'utiliser mmap à partir de 1.15. Vous pouvez obtenir l'ancienne version de Github
baotiao
4
mmap pourrait également être utile dans le cas où un fichier doit être traité en plusieurs passes: le coût d'allocation des pages de mémoire virtuelle n'est payé qu'une seule fois.
Jib
69

Un domaine où j'ai trouvé que mmap () n'était pas un avantage était lors de la lecture de petits fichiers (moins de 16 Ko). Le surdébit de page défaillant pour lire le fichier entier était très élevé par rapport à un simple appel système read (). C'est parce que le noyau peut parfois satisifier une lecture entièrement dans votre tranche de temps, ce qui signifie que votre code ne change pas. Avec un défaut de page, il semblait plus probable qu'un autre programme soit planifié, ce qui rendait l'opération de fichier plus latente.

Ben Combee
la source
4
+1 Je peux le confirmer. Pour les petits fichiers, il est plus rapide de malloccréer un morceau de mémoire et d'en faire 1 read. Cela permet d'avoir le même code qui gère les cartes mémoire malloc'ed.
Patrick Schlüter
35
Cela dit, votre justification n'est pas juste. L'ordonnanceur n'a rien à voir avec la différence. La différence vient des accès en écriture aux tables de pages, qui est une structure globale du noyau contenant quels processus contiennent quelle page mémoire et ses droits d'accès. Cette opération peut être très coûteuse (elle peut invalider les lignes de cache, elle peut traverser TLB, la table est globale donc doit être protégée contre les accès simultanés, etc.). Vous avez besoin d'une certaine taille de carte pour que la surcharge des readaccès soit supérieure à celle de la manipulation de la mémoire virtuelle.
Patrick Schlüter
1
@ PatrickSchlüter D'accord, je comprends qu'il y a une surcharge au début de mmap () qui implique de modifier le tableau des pages. Supposons que nous mappions 16 Ko d'un fichier à la mémoire. Pour une taille de page de 4K, mmapdoit mettre à jour 4 entrées dans le tableau des pages. Mais utiliser readpour copier dans un tampon de 16 Ko implique également la mise à jour des entrées de table de 4 pages, sans oublier qu'il doit copier le 16 Ko dans l'espace utilisateur. Alors, pourriez-vous nous expliquer les différences d'opérations sur la table des pages et comment cela coûte plus cher mmap?
flow2k
45

mmapa l'avantage lorsque vous avez un accès aléatoire sur de gros fichiers. Un autre avantage est que vous y accédez avec des opérations de mémoire (memcpy, arithmetic pointeur), sans vous soucier de la mise en mémoire tampon. Les E / S normales peuvent parfois être assez difficiles lorsque vous utilisez des tampons lorsque vous avez des structures plus grandes que votre tampon. Le code à gérer qui est souvent difficile à obtenir correctement, mmap est généralement plus facile. Cela dit, il existe certains pièges lorsque vous travaillez avec mmap. Comme les gens l'ont déjà mentionné,mmap sa mise en place est assez coûteuse, il vaut donc la peine de l'utiliser uniquement pour une taille donnée (variant d'une machine à l'autre).

Pour les accès séquentiels purs au fichier, ce n'est pas toujours la meilleure solution, bien qu'un appel approprié à madvise puisse atténuer le problème.

Vous devez être prudent avec les restrictions d'alignement de votre architecture (SPARC, itanium), avec les E / S en lecture / écriture, les tampons sont souvent correctement alignés et ne se coincent pas lors du déréférencement d'un pointeur casté.

Vous devez également faire attention à ne pas accéder en dehors de la carte. Cela peut facilement se produire si vous utilisez des fonctions de chaîne sur votre carte et que votre fichier ne contient pas de \ 0 à la fin. Cela fonctionnera la plupart du temps lorsque la taille de votre fichier n'est pas un multiple de la taille de la page car la dernière page est remplie de 0 (la zone mappée est toujours de la taille d'un multiple de la taille de votre page).

Patrick Schlüter
la source
30

En plus d'autres bonnes réponses, une citation de la programmation système Linux écrite par l'expert de Google, Robert Love:

Les avantages de mmap( )

La manipulation de fichiers via mmap( )présente plusieurs avantages par rapport aux appels standard read( )et write( )système. Parmi eux:

  • La lecture et l'écriture dans un fichier mappé en mémoire évite la copie superflue qui se produit lors de l'utilisation des appels système read( )ou write( ), où les données doivent être copiées vers et depuis un tampon d'espace utilisateur.

  • Mis à part les éventuels défauts de page, la lecture et l'écriture dans un fichier mappé en mémoire n'entraîne aucun appel système ni changement de contexte. C'est aussi simple que d'accéder à la mémoire.

  • Lorsque plusieurs processus mappent le même objet en mémoire, les données sont partagées entre tous les processus. Les mappages accessibles en lecture seule et partagés sont partagés dans leur intégralité; les mappages accessibles en écriture privés ont leurs pages pas encore COW (copie sur écriture) partagées.

  • La recherche autour de la cartographie implique des manipulations de pointeur triviales. Il n'y a pas besoin de l' lseek( )appel système.

Pour ces raisons, mmap( )est un choix intelligent pour de nombreuses applications.

Inconvénients de mmap( )

Il y a quelques points à garder à l'esprit lors de l'utilisation mmap( ):

  • Les mappages de mémoire sont toujours un nombre entier de pages. Ainsi, la différence entre la taille du fichier de sauvegarde et un nombre entier de pages est "gaspillée" en tant qu'espace libre. Pour les petits fichiers, un pourcentage important du mappage peut être perdu. Par exemple, avec des pages de 4 Ko, un mappage de 7 octets gaspille 4 089 octets.

  • Les mappages de mémoire doivent tenir dans l'espace d'adressage du processus. Avec un espace d'adressage 32 bits, un très grand nombre de mappages de différentes tailles peut entraîner une fragmentation de l'espace d'adressage, ce qui rend difficile la recherche de grandes régions contiguës libres. Ce problème, bien sûr, est beaucoup moins apparent avec un espace d'adressage 64 bits.

  • Il y a un surcoût dans la création et la maintenance des mappages de mémoire et des structures de données associées à l'intérieur du noyau. Cette surcharge est généralement évitée par l'élimination de la double copie mentionnée dans la section précédente, en particulier pour les fichiers plus volumineux et fréquemment consultés.

Pour ces raisons, les avantages de mmap( )sont plus largement réalisés lorsque le fichier mappé est volumineux (et donc tout espace gaspillé représente un petit pourcentage du mappage total), ou lorsque la taille totale du fichier mappé est divisible de manière égale par la taille de la page ( et donc il n'y a pas d'espace perdu).

Miljen Mikic
la source
13

Le mappage de la mémoire offre un énorme avantage en termes de vitesse par rapport aux E / S traditionnelles. Il permet au système d'exploitation de lire les données du fichier source lorsque les pages du fichier mappé en mémoire sont touchées. Cela fonctionne en créant des pages défaillantes, que le système d'exploitation détecte, puis le système d'exploitation charge automatiquement les données correspondantes du fichier.

Cela fonctionne de la même manière que le mécanisme de pagination et est généralement optimisé pour les E / S à grande vitesse en lisant les données sur les limites et les tailles des pages système (généralement 4K) - une taille pour laquelle la plupart des caches du système de fichiers sont optimisés.

AndyG
la source
15
Notez que mmap () n'est pas toujours plus rapide que read (). Pour les lectures séquentielles, mmap () ne vous donnera aucun avantage mesurable - ceci est basé sur des preuves empiriques et théoriques. Si vous ne me croyez pas, écrivez votre propre test.
Tim Cooper
1
Je peux donner des chiffres provenant de notre projet, une sorte d'index de texte pour une base de données de phrases. L'index est de plusieurs gigaoctets et les clés sont conservées dans un arbre ternaire. L'index croît toujours en parallèle pour accéder en lecture, l'accès en dehors des parties mappées se fait via pread. Sur Solaris 9 Sparc (V890), l'accès au pread est entre 2 et 3 fois plus lent que celui memcpydu mmap. Mais vous avez raison, l'accès séquentiel n'est pas nécessairement plus rapide.
Patrick Schlüter
19
Juste une petite piqûre. Cela ne fonctionne pas comme le mécanisme de pagination, c'est le mécanisme de pagination. Le mappage d'un fichier consiste à affecter une zone de mémoire à un fichier au lieu du fichier d'échange anonyme.
Patrick Schlüter
2

Un avantage qui n'est pas encore répertorié est la possibilité de mmap()conserver un mappage en lecture seule en tant que pages propres . Si l'on alloue un tampon dans l'espace d'adressage du processus, alors utiliseread() pour remplir le tampon à partir d'un fichier, les pages mémoire correspondant à ce tampon sont maintenant sales depuis qu'elles ont été écrites.

Les pages sales ne peuvent pas être supprimées de la RAM par le noyau. S'il y a de l'espace de swap, ils peuvent être paginés pour swap. Mais cela coûte cher et sur certains systèmes, tels que les petits appareils embarqués avec uniquement une mémoire flash, il n'y a aucun échange du tout. Dans ce cas, le tampon sera bloqué dans la RAM jusqu'à la fin du processus, ou peut-être le restitue avecmadvise() .

Les mmap()pages non écrites sont propres. Si le noyau a besoin de RAM, il peut simplement les supprimer et utiliser la RAM dans laquelle les pages se trouvaient. . De la même manière, ils ont été peuplés en premier lieu.

Cela ne nécessite pas plus d'un processus utilisant le fichier mappé pour être un avantage.

TrentP
la source
Le noyau ne peut-il pas supprimer une page mmap'd «sale» en écrivant d'abord son contenu dans le fichier sous-jacent?
Jeremy Friesner
2
Lors de l'utilisation read(), les pages dans lesquelles les données sont finalement insérées n'ont aucun rapport avec le fichier dont elles peuvent provenir. Ils ne peuvent donc pas être écrits, sauf pour échanger de l'espace. Si un fichier l'est mmap()edet que le mappage est accessible en écriture (par opposition à la lecture seule) et écrit dans, cela dépend si le mappage était MAP_SHAREDou MAP_PRIVATE. Un mappage partagé peut / doit être écrit dans le fichier, mais pas un privé.
TrentP