¿Cuándo debo usar mmap para acceder a archivos?
Los entornos POSIX proporcionan al menos dos formas de acceder a archivos. Están las llamadas al sistema estándar open()
, read()
, write()
y amigos, pero también existe la opción de usar mmap()
para asignar el archivo a la memoria virtual.
¿Cuándo es preferible utilizar uno u otro? ¿Cuáles son sus ventajas individuales que merecen incluir dos interfaces?
mmap
es genial si tienes múltiples procesos accediendo a datos en modo de solo lectura desde el mismo archivo, lo cual es común en el tipo de sistemas de servidor que escribo. mmap
permite que todos esos procesos compartan las mismas páginas de memoria física, ahorrando mucha memoria.
mmap
También permite que el sistema operativo optimice las operaciones de paginación. Por ejemplo, considere dos programas; programa A
que lee un 1MB
archivo en un búfer creado con malloc
y programa B que guarda mmaps
el archivo de 1 MB en la memoria. Si el sistema operativo tiene que intercambiar parte de A
la memoria, debe escribir el contenido del búfer para intercambiar antes de poder reutilizar la memoria. En B
el caso de, mmap
las páginas no modificadas se pueden reutilizar inmediatamente porque el sistema operativo sabe cómo restaurarlas desde el archivo existente del que se obtuvieron mmap
. (El sistema operativo puede detectar qué páginas no están modificadas marcando inicialmente mmap
las páginas en las que se puede escribir como de solo lectura y detectando fallas de segmentación , similar a la estrategia Copiar en escritura ).
mmap
También es útil para la comunicación entre procesos . Puede mmap
leer/escribir un archivo en los procesos que necesitan comunicarse y luego usar primitivas de sincronización en la mmap'd
región (para esto es la MAP_HASSEMAPHORE
bandera).
Un lugar mmap
que puede resultar incómodo es si necesita trabajar con archivos muy grandes en una máquina de 32 bits. Esto se debe a que mmap
tiene que encontrar un bloque contiguo de direcciones en el espacio de direcciones de su proceso que sea lo suficientemente grande como para caber en todo el rango del archivo que se está asignando. Esto puede convertirse en un problema si su espacio de direcciones se fragmenta, donde puede tener 2 GB de espacio de direcciones libres, pero ningún rango individual puede caber en una asignación de archivos de 1 GB. En este caso, es posible que tenga que asignar el archivo en fragmentos más pequeños de los que le gustaría que quepan.
Otra posible incomodidad al mmap
reemplazar la lectura/escritura es que debe comenzar el mapeo en desplazamientos del tamaño de la página. Si solo desea obtener algunos datos en el desplazamiento, X
deberá arreglar ese desplazamiento para que sea compatible con mmap
.
Y finalmente, la lectura/escritura es la única forma de trabajar con algunos tipos de archivos. mmap
No se puede utilizar en cosas como tuberías y ttys .
Un área en la que encontré que mmap() no era una ventaja fue al leer archivos pequeños (menos de 16K). La sobrecarga de errores de página para leer el archivo completo era muy alta en comparación con hacer una sola llamada al sistema read(). Esto se debe a que el kernel a veces puede satisfacer una lectura por completo en su intervalo 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 hacía que la operación del archivo tuviera una mayor latencia.
mmap
tiene la ventaja cuando tienes acceso aleatorio a archivos grandes. Otra ventaja es que puedes acceder a él con operaciones de memoria (memcpy, aritmética de punteros), sin preocuparte por el almacenamiento en búfer. La E/S normal a veces puede resultar bastante difícil cuando se utilizan búferes cuando se tienen estructuras más grandes que el búfer. El código de manejo a menudo es difícil de hacer bien, mmap generalmente es más fácil. Dicho esto, existen ciertas trampas al trabajar con mmap
. Como ya se ha mencionado, mmap
su configuración es bastante costosa, por lo que vale la pena utilizarla sólo para un tamaño determinado (que varía de una máquina a otra).
Para accesos puramente secuenciales al archivo, tampoco siempre es la mejor solución, aunque una llamada adecuada madvise
puede mitigar el problema.
Debe tener cuidado con las restricciones de alineación de su arquitectura (SPARC, itanium), con E/S de lectura/escritura los buffers suelen estar correctamente alineados y no se bloquean al eliminar la referencia a un puntero convertido.
También hay que 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 está llena con 0 (el área asignada siempre tiene un tamaño múltiplo del tamaño de su página).
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 varias ventajas sobre las llamadas estándarread( )
ywrite( )
del sistema. Entre ellos están:
Leer y escribir en un archivo asignado en memoria evita la copia superflua que se produce cuando se utilizan llamadas al sistema
read( )
owrite( )
, donde los datos deben copiarse hacia y desde un búfer de espacio de usuario.Aparte de posibles errores de página, leer y escribir en un archivo asignado en memoria no genera ninguna llamada al sistema ni sobrecarga de cambio de contexto. Es tan sencillo 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 compartidas y de solo lectura se comparten en su totalidad; Las asignaciones de escritura privadas tienen sus páginas aún no COW (copia en escritura) compartidas.
La búsqueda alrededor del mapeo implica manipulaciones triviales del puntero. No es necesaria la
lseek( )
llamada al sistema.Por estos motivos,
mmap( )
es una elección inteligente para muchas aplicaciones.Desventajas de
mmap( )
Hay algunos puntos a tener en cuenta al utilizar
mmap( )
:
Las asignaciones de memoria siempre tienen un tamaño de número entero de páginas. 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, es posible que se desperdicie un porcentaje significativo del mapeo. Por ejemplo, con páginas de 4 KB, una asignación de 7 bytes desperdicia 4089 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 distintos tamaños puede provocar la fragmentación del espacio de direcciones, lo que dificulta encontrar grandes regiones contiguas libres. Este problema, por supuesto, es mucho menos evidente con un espacio de direcciones de 64 bits.
Existe una sobrecarga al crear y mantener las asignaciones de memoria y las estructuras de datos asociadas dentro del kernel. 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 a los que se accede con frecuencia.
Por estas razones, los beneficios de
mmap( )
se obtienen en mayor medida cuando el archivo mapeado 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 uniformemente por el tamaño de la página ( y así no se desperdicia espacio).