¿Cuándo debo usar mmap para acceder a los archivos?


276

Los entornos POSIX proporcionan al menos dos formas de acceder a los archivos. Ahí está el estándar de llamadas al sistema open(), read(), write(), y amigos, pero también existe la opción de usar mmap()para mapear el archivo en la memoria virtual.

¿Cuándo es preferible usar uno sobre el otro? ¿Cuáles son sus ventajas individuales que merecen incluir dos interfaces?


16
Ver también mmap () vs. bloques de lectura y esta publicación de Linus Torvalds referenciada en una de las respuestas allí.
MvG

Respuestas:


299

mmapes excelente si tiene múltiples procesos que acceden a los datos en forma de solo lectura desde el mismo archivo, lo cual es común en el tipo de sistemas de servidor que escribo. mmappermite que todos esos procesos compartan las mismas páginas de memoria física, ahorrando mucha memoria.

mmapTambién permite que el sistema operativo optimice las operaciones de paginación. Por ejemplo, considere dos programas; programa Aque lee en un 1MBarchivo en un búfer creando con malloc, y programa B que mmapsel archivo de 1 MB en la memoria. Si el sistema operativo tiene que intercambiar parte de Ala memoria, debe escribir el contenido del búfer para intercambiar antes de que pueda reutilizar la memoria. En Bel caso, cualquier página mmap'd' no modificada se puede reutilizar de inmediato porque el sistema operativo sabe cómo restaurarla desde el archivo existente del que mmapprovino. (El sistema operativo puede detectar qué páginas no se modifican marcando inicialmente mmaplas páginas 'd' editables como de solo lectura y detectando fallas seg , similar a la estrategia Copiar en escritura ).

mmapTambién es útil para la comunicación entre procesos . Puede mmapcrear un archivo como lectura / escritura en los procesos que necesitan comunicarse y luego usar primitivas de sincronización en la mmap'dregión (para eso está la MAP_HASSEMAPHOREbandera).

Un lugar mmappuede ser incómodo si necesita trabajar con archivos muy grandes en una máquina de 32 bits. Esto se debe a que mmaptiene que encontrar un bloque contiguo de direcciones en el espacio de direcciones de su proceso que sea lo suficientemente grande como para adaptarse al rango completo del archivo que se está mapeando. Esto puede convertirse en un problema si su espacio de direcciones se fragmenta, donde puede tener 2 GB de espacio de direcciones libre, pero ningún rango individual puede caber en una asignación de archivos de 1 GB. En este caso, es posible que deba asignar el archivo en fragmentos más pequeños de los que desea para que encaje.

Otra posible incomodidad mmapcomo reemplazo de lectura / escritura es que debe comenzar su mapeo en los desplazamientos del tamaño de página. Si solo desea obtener algunos datos en el desplazamiento X, necesitará corregir ese desplazamiento para que sea compatible mmap.

Y, por último, leer / escribir son la única forma en que puede trabajar con algunos tipos de archivos. mmapno se puede usar en cosas como tuberías y ttys .


10
¿Se puede usar mmap () en archivos que están creciendo? ¿O se fija el tamaño en el punto cuando asigna la memoria / archivo mmap ()?
Jonathan Leffler el

29
Cuando realiza la llamada mmap, debe especificar un tamaño. Entonces, si desea hacer algo como una operación de cola, no es muy adecuado.
Don Neufeld el

55
Afaik MAP_HASSEMAPHOREes específico de BSD.
Patrick Schlüter

66
@JonathanLeffler Ciertamente puede usar mmap () en archivos que están creciendo, pero debe llamar a mmap () nuevamente con el nuevo tamaño cuando el archivo alcanza el límite del espacio que asignó inicialmente. PosixMmapFile de LevelDB le da un buen ejemplo. Pero dejó de usar mmap desde 1.15. Puedes obtener la versión anterior de Github
baotiao

44
mmap también podría ser útil en caso de que un archivo deba procesarse en varias pasadas: el costo de asignar páginas de memoria virtual solo se paga una vez.
Jib

69

Un área donde encontré que mmap () no era una ventaja era cuando leía archivos pequeños (menos de 16K). La sobrecarga de la página que no pudo leer el archivo completo fue muy alta en comparación con solo hacer una sola llamada al sistema read (). Esto se debe a que el núcleo a veces puede satisfacer una lectura por completo en su segmento de tiempo, lo que significa que su código no cambia. Con un error de página, parecía más probable que se programara otro programa, lo que hace que la operación del archivo tenga una latencia más alta.


44
+1 Puedo confirmar eso. Para archivos pequeños es más rápido convertir mallocun trozo de memoria y convertir 1 readen él. Esto permite tener el mismo código que maneja los mapas de memoria manejar malloc'ed.
Patrick Schlüter

35
Dicho esto, su justificación no es correcta. El planificador no tiene nada que ver con la diferencia. La diferencia proviene de los accesos de escritura a las tablas de páginas, que es una estructura global del núcleo que contiene qué procesos contienen qué página de memoria y sus derechos de acceso. Esta operación puede ser muy costosa (puede invalidar las líneas de caché, puede eliminar TLB, la tabla es global, por lo que debe protegerse contra el acceso concurrente, etc.). Necesita un cierto tamaño de mapa para que la sobrecarga de los readaccesos sea mayor que la sobrecarga de la manipulación de la memoria virtual.
Patrick Schlüter

1
@ PatrickSchlüter Bien, entiendo que hay una sobrecarga al comienzo de mmap () que implica modificar la tabla de páginas. Digamos que asignamos 16K de un archivo a la memoria. Para un tamaño de página de 4K, mmaptiene que actualizar 4 entradas en la tabla de páginas. Pero usar readpara copiar en un búfer de 16K también implica actualizar las entradas de la tabla de 4 páginas, sin mencionar que necesita copiar el 16K en el espacio de dirección del usuario. Entonces, ¿podría explicar las diferencias de operaciones en la tabla de páginas y cómo es más costoso mmap?
flow2k

45

mmaptiene la ventaja cuando tiene acceso aleatorio en archivos grandes. Otra ventaja es que accede a él con operaciones de memoria (memcpy, aritmética de puntero), sin molestarse con el almacenamiento en búfer. La E / S normal a veces puede ser bastante difícil cuando se usan buffers cuando tienes estructuras más grandes que tu buffer. El código para manejar que a menudo es difícil de corregir, mmap es generalmente más fácil. Dicho esto, hay ciertas trampas cuando se trabaja con mmap. Como la gente ya ha mencionado, mmapes bastante costoso configurarlo, por lo que vale la pena usarlo solo para un tamaño determinado (que varía de una máquina a otra).

Para los accesos secuenciales puros al archivo, tampoco es siempre la mejor solución, aunque una llamada adecuada madvisepuede mitigar el problema.

Debe tener cuidado con las restricciones de alineación de su arquitectura (SPARC, itanium), con IO de lectura / escritura, los buffers a menudo están correctamente alineados y no se atrapan al desreferenciar un puntero fundido.

También debe tener cuidado de no acceder fuera del mapa. Puede suceder fácilmente si usa funciones de cadena en su mapa, y su archivo no contiene un \ 0 al final. Funcionará la mayor parte del tiempo cuando el tamaño de su archivo no sea un múltiplo del tamaño de la página, ya que la última página se llena con 0 (el área asignada siempre tiene el tamaño de un múltiplo del tamaño de su página).


30

Además de otras buenas respuestas, una cita de la programación del sistema Linux escrita por el experto de Google Robert Love:

Ventajas de mmap( )

La manipulación de archivos a través de mmap( )tiene un puñado de ventajas sobre las llamadas estándar read( )y del write( )sistema. Entre ellos están:

  • Leer y escribir a un archivo asignado en memoria evita la copia extraña que se produce cuando se utiliza el read( )o write( )llamadas al sistema, donde los datos deben ser copiadas y de una memoria intermedia de espacio de usuario.

  • Además de posibles fallas en la página, leer y escribir en un archivo mapeado en memoria no genera ninguna llamada al sistema ni sobrecarga de cambio de contexto. Es tan simple como acceder a la memoria.

  • Cuando varios procesos asignan el mismo objeto a la memoria, los datos se comparten entre todos los procesos. Las asignaciones de escritura de solo lectura y compartidas se comparten en su totalidad; las asignaciones de escritura privadas tienen sus páginas aún no COW (copia en escritura) compartidas.

  • Buscar alrededor del mapeo implica manipulaciones de puntero triviales. No hay necesidad de lseek( )llamar al sistema.

Por estas razones, mmap( )es una opción inteligente para muchas aplicaciones.

Desventajas de mmap( )

Hay algunos puntos a tener en cuenta al usar mmap( ):

  • Las asignaciones de memoria son siempre un número entero de páginas de tamaño. Por lo tanto, la diferencia entre el tamaño del archivo de respaldo y un número entero de páginas se "desperdicia" como espacio libre. Para archivos pequeños, se puede desperdiciar un porcentaje significativo de la asignación. Por ejemplo, con páginas de 4 KB, una asignación de 7 bytes desperdicia 4.089 bytes.

  • Las asignaciones de memoria deben caber en el espacio de direcciones del proceso. Con un espacio de direcciones de 32 bits, una gran cantidad de asignaciones de varios tamaños puede dar lugar a la fragmentación del espacio de direcciones, lo que dificulta la búsqueda de grandes regiones contiguas libres. Este problema, por supuesto, es mucho menos evidente con un espacio de direcciones de 64 bits.

  • Hay una sobrecarga en la creación y mantenimiento de las asignaciones de memoria y las estructuras de datos asociadas dentro del núcleo. Esta sobrecarga generalmente se evita mediante la eliminación de la doble copia mencionada en la sección anterior, particularmente para archivos más grandes y de acceso frecuente.

Por estas razones, los beneficios de se mmap( )obtienen más ampliamente cuando el archivo asignado es grande (y, por lo tanto, cualquier espacio desperdiciado es un pequeño porcentaje del mapeo total), o cuando el tamaño total del archivo mapeado es divisible por el tamaño de la página ( y así no hay desperdicio de espacio).


13

El mapeo de memoria tiene el potencial de una gran ventaja de velocidad en comparación con las E / S tradicionales. Permite que el sistema operativo lea los datos del archivo fuente a medida que se tocan las páginas en el archivo mapeado de memoria. Esto funciona creando páginas con fallas, que el sistema operativo detecta y luego el sistema operativo carga automáticamente los datos correspondientes del archivo.

Esto funciona de la misma manera que el mecanismo de paginación y, por lo general, está optimizado para E / S de alta velocidad mediante la lectura de datos sobre los límites y tamaños de las páginas del sistema (generalmente 4K), un tamaño para el que se optimizan la mayoría de las memorias caché del sistema de archivos.


15
Tenga en cuenta que mmap () no siempre es más rápido que read (). Para lecturas secuenciales, mmap () no le dará una ventaja medible, esto se basa en evidencia empírica y teórica. Si no me crees, escribe tu propia prueba.
Tim Cooper

1
Puedo dar números provenientes de nuestro proyecto, una especie de índice de texto para una base de datos de frases. El índice es de varios Gigabytes grandes y las claves se guardan en un árbol ternario. El índice sigue creciendo en paralelo al acceso de lectura, el acceso fuera de las partes mapeadas se realiza a través de pread. En Solaris 9 Sparc (V890), el acceso de lectura previa es entre 2 y 3 veces más lento que el memcpydel mmap. Pero tiene razón en que el acceso secuencial no es necesariamente más rápido.
Patrick Schlüter

19
Solo un pequeño truco. No funciona como el mecanismo de búsqueda, es el mecanismo de búsqueda. La asignación de un archivo es asignar un área de memoria a un archivo en lugar del archivo de intercambio anónimo.
Patrick Schlüter

2

Una ventaja que aún no figura en la lista es la capacidad de mmap()mantener una asignación de solo lectura como páginas limpias . Si se asigna un búfer en el espacio de direcciones del proceso, luego se usa read()para llenar el búfer desde un archivo, las páginas de memoria correspondientes a ese búfer ahora están sucias desde que se escribieron.

El kernel no puede quitar páginas sucias de la RAM. Si hay espacio de intercambio, entonces se pueden localizar para intercambiar. Pero esto es costoso y en algunos sistemas, como pequeños dispositivos integrados con solo memoria flash, no hay intercambio en absoluto. En ese caso, el búfer estará atascado en la RAM hasta que el proceso salga, o tal vez lo devuelva madvise().

No escrito en las mmap()páginas están limpias. Si el kernel necesita RAM, simplemente puede soltarlos y usar la RAM en la que se encontraban las páginas. Si el proceso que tenía el mapeo accede de nuevo, causa un error de página, el kernel vuelve a cargar las páginas del archivo original. . De la misma manera que se poblaron en primer lugar.

Esto no requiere más de un proceso usando el archivo mapeado para ser una ventaja.


¿No puede el núcleo descartar una página mmap'd 'sucia' escribiendo primero su contenido en el archivo subyacente?
Jeremy Friesner

2
Cuando se usa read(), las páginas en las que se colocan los datos finalmente no tienen relación con el archivo del que provienen. Por lo tanto, no se pueden escribir, excepto para intercambiar espacio. Si un archivo es mmap()ed, y el mapeo es grabable (en lugar de solo lectura), y escrito, entonces depende de si el mapeo fue MAP_SHAREDo no MAP_PRIVATE. Un mapeo compartido puede / debe escribirse en el archivo, pero un privado no puede ser.
TrentP
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.