prot_write prot_read linux macos mmap

linux - prot_read - Cómo extender de manera portátil un archivo accedido usando mmap()



mmap 2 (3)

  1. Creo que # 2 es la mejor solución actualmente disponible. Además de esto, en los sistemas de 64 bits puede crear su asignación explícitamente en una dirección que el sistema operativo nunca elegiría para una asignación (por ejemplo, 0x6000 0000 0000 0000 en Linux) para evitar el caso de que el sistema operativo no pueda colocar la nueva asignación inmediatamente después de la primera uno.

  2. Siempre es seguro deseleccionar múltiples mappinsg con una sola llamada munmap. Incluso puede desasignar una parte de la asignación si lo desea.

Estamos experimentando con el cambio de SQLite, un sistema de base de datos integrado, para usar mmap () en lugar de las llamadas de lectura () y escritura () habituales para acceder al archivo de base de datos en el disco. Usando una sola asignación grande para todo el archivo. Supongamos que el archivo es lo suficientemente pequeño como para que no tengamos problemas para encontrar espacio para esto en la memoria virtual.

Hasta ahora tan bueno. En muchos casos, utilizar mmap () parece ser un poco más rápido que leer () y escribir (). Y en algunos casos mucho más rápido.

Cambiar el tamaño de la asignación para cometer una transacción de escritura que extienda el archivo de base de datos parece ser un problema. Para extender el archivo de base de datos, el código podría hacer algo como esto:

ftruncate(); // extend the database file on disk munmap(); // unmap the current mapping (it''s now too small) mmap(); // create a new, larger, mapping

luego copie los nuevos datos al final de la nueva asignación de memoria. Sin embargo, munmap / mmap no es deseable, ya que significa que la próxima vez que se acceda a cada página del archivo de base de datos, se producirá un fallo menor en la página y el sistema deberá buscar en la memoria caché de la página del SO el marco correcto para asociarlo con la dirección de la memoria virtual. En otras palabras, ralentiza las lecturas de base de datos posteriores.

En Linux, podemos usar la llamada al sistema no estándar mremap () en lugar de munmap () / mmap () para cambiar el tamaño de la asignación. Esto parece evitar los fallos menores de la página.

PREGUNTA: ¿Cómo debe tratarse esto en otros sistemas, como OSX, que no tienen mremap ()?

Tenemos dos ideas en la actualidad. Y una pregunta sobre cada uno:

1) Crear asignaciones más grandes que el archivo de base de datos. Luego, cuando extienda el archivo de la base de datos, simplemente llame a ftruncate () para extender el archivo en el disco y continúe usando la misma asignación.

Esto sería ideal, y parece funcionar en la práctica. Sin embargo, estamos preocupados por esta advertencia en la página de manual:

"El efecto de cambiar el tamaño del archivo subyacente de una asignación en las páginas que corresponden a regiones agregadas o eliminadas del archivo no está especificado".

PREGUNTA: ¿Es esto algo de lo que deberíamos estar preocupados? ¿O un anacronismo en este punto?

2) Al extender el archivo de la base de datos, use el primer argumento para mmap () para solicitar una asignación correspondiente a las nuevas páginas del archivo de la base de datos que se encuentra inmediatamente después de la asignación actual en la memoria virtual. Extendiendo efectivamente el mapeo inicial. Si el sistema no puede cumplir con la solicitud de colocar la nueva asignación inmediatamente después de la primera, vuelva a munmap / mmap.

En la práctica, hemos encontrado que OSX es bastante bueno posicionando las asignaciones de esta manera, por lo que este truco funciona allí.

PREGUNTA: si el sistema asigna la segunda asignación inmediatamente después de la primera en la memoria virtual, ¿es seguro deshacer la asignación de ambos usando una sola llamada grande a munmap ()?


  1. Use fallocate () en lugar de ftruncate () cuando esté disponible. De lo contrario, simplemente abra el archivo en modo O_APPEND e incremente el archivo escribiendo una cantidad de ceros. Esto reduce enormemente la fragmentación.

  2. Use "Páginas enormes" si están disponibles, esto reduce considerablemente los gastos generales en las grandes asignaciones.

  3. pread () / pwrite () / pwritev () / preadv () con un tamaño de bloque no tan pequeño no es realmente lento. Mucho más rápido que IO se puede realizar realmente.

  4. Los errores de IO al usar mmap () generarán solo segfault en lugar de EIO o algo así.

  5. La mayoría de los problemas de rendimiento de SQLite WRITE se concentran en un buen uso transaccional (es decir, debe depurar cuando COMMIT realmente se realizó).


2 funcionará pero no tiene que confiar en que el sistema operativo tenga espacio disponible, puede reservar su espacio de direcciones de antemano para que sus mmapings fijos siempre tengan éxito.

Por ejemplo, para reservar un gigabyte de espacio de direcciones. Hacer un

mmap(NULL, 1U << 30, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);

Que reservará un gigabyte de espacio continuo de direcciones sin asignar realmente memoria o recursos. A continuación, puede realizar futuros mmapings en este espacio y tendrán éxito. Entonces, coloque el archivo en el principio del espacio devuelto, y luego haga mmap en las secciones adicionales del archivo según sea necesario con el indicador fijo. Los mmaps tendrán éxito porque su espacio de dirección ya está asignado y reservado por usted.

Nota: Linux también tiene el indicador MAP_NORESERVE, que es el comportamiento que desearía para la asignación inicial si estuviera asignando RAM, pero en mis pruebas se ignora, ya que PROT_NONE es suficiente para decir que no desea que se asignen recursos.