Red de conocimiento informático - Problemas con los teléfonos móviles - [Original] Análisis en profundidad del principio mmap: a partir de tres preguntas clave

[Original] Análisis en profundidad del principio mmap: a partir de tres preguntas clave

Con respecto a mmap, ¿puedes analizar en principio las siguientes tres preguntas?

Para resolver estas preguntas, es posible que necesites saber más sobre el nivel del sistema operativo. Este artículo intentará analizar estos temas en profundidad. Espero que a través de este artículo todos puedan tener una comprensión más profunda de mmap y también utilizarlo como referencia en el diseño de motores de almacenamiento.

Recientemente, estoy desarrollando un sistema de almacenamiento de registros distribuido. Este es un sistema de almacenamiento de registros distribuido de desarrollo propio basado en el protocolo Raft como motor de almacenamiento subyacente.

En Logstore, mmap se utiliza para leer y escribir archivos de datos. La estructura de almacenamiento de Logstore se simplifica como se muestra a continuación:

Logstore utiliza archivos de índice de archivos de segmentos para almacenar el archivo de registro. Es el cuerpo de almacenamiento principal y se utiliza para almacenar datos de registro. Utiliza un método de longitud fija. y el valor predeterminado es 512 M cada uno. El archivo de índice se utiliza principalmente para la recuperación de contenido del archivo de segmento.

Logstore utiliza mmap para leer y escribir archivos de segmentos. La cantidad de archivos de segmentos depende principalmente del espacio en disco o de los requisitos comerciales. Generalmente, Logstore almacenará entre 1 T y 5 T de datos.

Veamos primero qué es mmap.

En el libro "Comprensión profunda de los sistemas informáticos", mmap se define como: Linux inicializa un área de memoria virtual asociándola con un objeto en el disco. El contenido de esta área de memoria virtual, esto. El proceso se llama mapeo de memoria.

En Logstore, el objeto de mapeo es un archivo normal (archivo de segmento).

Primero echemos un breve vistazo al mapeo de un archivo y lo que hace mmap. Como se muestra en la siguiente figura:

Supongamos que el archivo que mmap es FileA. Después de llamar a mmap, se asignará el espacio de direcciones en la memoria virtual del proceso y se creará una relación de mapeo.

Vale la pena señalar aquí que mmap solo asigna espacio de direcciones en la memoria virtual. Por ejemplo, suponga que el archivo A anterior tiene un tamaño de 2G.

Después de mmap, verifique el proceso donde mmap. se encuentra la descripción del mapa, puede ver

Como puede ver en lo anterior, después de mmap, el espacio de direcciones del proceso 7f35eea8d000-7f366ea8d000 se asigna y se asigna a FileA, 7f366ea8d000 menos 7f35eea8d000, que es exactamente 2147483648. (ps: aquí se asigna el archivo completo)

En Linux, el sistema VM maneja los datos del disco (capa inferior) y de la capa superior dividiendo la memoria virtual en bloques de tamaño fijo llamados páginas virtuales (VP). transmisión, en circunstancias normales, el tamaño predeterminado de cada página es 4096 bytes. De manera similar, la memoria física también se divide en páginas físicas (Physical Page, PP), también de 4096 bytes.

El ejemplo anterior, después de mmap, se muestra a continuación:

Después de mmap, el contenido del archivo no se carga en la página física, solo se asigna el espacio de direcciones en la memoria virtual . Cuando el proceso accede a esta dirección (ArchivoA al escribir o leer a través de mmap), si la página correspondiente a la memoria virtual no está almacenada en caché en la memoria física, se produce una "falla de página", que es manejada por el controlador de excepciones de falla de página del kernel. , cargue el contenido correspondiente del archivo en la memoria física en unidades de páginas (4096). Tenga en cuenta que solo se cargan las páginas que faltan, pero también se verá afectado por algunas políticas de programación del sistema operativo y cargará más de lo necesario, por lo que. No lo ampliaré aquí.

(PD: para ser más específico, cuando el proceso accede a la dirección virtual del proceso 7f35eea8d000, la MMU busca en la tabla de páginas y descubre que el contenido correspondiente no está almacenado en caché en la memoria física, lo que resulta en una "página fallo")

Después del procesamiento de fallos de página, como se muestra a continuación:

Creo que, en principio, mmap tiene dos tipos, uno con backend y otro sin backend.

Este modo realiza un mapeo de memoria en archivos normales (no MAP_ANONYMOUS), por lo que cuando se llama al sistema mmap, se debe pasar el fd del archivo. Hay dos métodos comunes de este modo, MAP_SHARED y MAP_PRIVATE, pero sus comportamientos son diferentes.

1) MAP_SHARED

Creo que este método se puede ver desde dos ángulos:

2) MAP_PRIVATE

Esta es una copia - método de mapeo en escritura. Aunque también tiene un backend, al escribir datos, copiará una copia de los datos en la memoria física (en páginas) y los datos no se volverán a escribir en el archivo. Cabe señalar aquí que debido a que los datos actualizados son una copia y no se reescribirán, esto significa que si el programa no los libera activamente cuando se está ejecutando, si los datos actualizados exceden el espacio de intercambio de memoria física disponible, se generará un OOM. Se encontrará con el asesino.

Sin backend, generalmente es MAP_ANONYMOUS, que asigna un área a un archivo anónimo. El archivo anónimo lo crea el kernel. Debido a que no hay backend, si los datos se escriben/actualizan y no se liberan activamente, la memoria física ocupada no se puede liberar y también aparecerá OOM Killer.

Llegados a este punto, este problema es más fácil de analizar. Podemos separar este problema en:

-- ¿La memoria virtual causará problemas?

Volviendo a lo anterior "¿Qué hace mmap en el proceso de memoria virtual?", sabemos que mmap asignará espacio de direcciones en la memoria virtual del proceso. Por ejemplo, para un archivo de 1G, asigne 1G de espacio de direcciones continuo. Entonces, ¿cuánto se puede mapear? En un sistema operativo de 64 bits, el rango de direccionamiento es 2 ^ 64. Excepto por algunos segmentos de direcciones, como los datos del núcleo y del proceso, básicamente se puede considerar que se pueden asignar infinitos datos (una declaración menos rigurosa).

-- ¿Habrá problemas con la memoria física?

Volviendo a la "clasificación mmap" anterior, para mmap con backend, se puede volver a escribir en el archivo, el mapeo es. mejor que el intercambio de memoria. No hay problema si hay mucho espacio. Sin embargo, si no puede volver a escribir en el archivo, debe tener mucho cuidado y liberarlo de forma proactiva.

MAP_NORESERVE es un parámetro de mmap. La descripción de MAN es "No reservar espacio de intercambio para este mapeo. Cuando el espacio de intercambio está reservado, uno tiene la garantía de que es posible modificar el mapeo".

Hagamos una prueba:

Escenario A: espacio de intercambio de memoria física: 16G, archivo de mapeo 30G, use un proceso para mmap y continúe escribiendo datos después de un mapeo exitoso

Escenario B: Espacio de intercambio de memoria física: 16G, archivo mapeado 15G, use dos procesos para mmap y continúe escribiendo datos después de un mapeo exitoso

Como se puede ver en la prueba anterior, desde el Fenómeno Mira, NORESERVE omite la verificación de mmap, lo que le permite realizar mmap con éxito. Pero, de hecho, en el caso de RESERVA (secuencia 4), a juzgar por los resultados de la prueba, no hay garantía.

El rendimiento de mmap a menudo se compara con las llamadas al sistema (escritura/lectura).

Veamos la lectura y la escritura por separado, primero intentemos analizar las diferencias entre las dos en principio y luego verifíquelas mediante pruebas.

Primero hablemos brevemente sobre el proceso de escritura de archivos usando la llamada al sistema de escritura:

A continuación, hablemos brevemente sobre el proceso de escritura de archivos cuando usamos mmap:

Llamada al sistema Tendrá un impacto en el rendimiento, así que analícelo teóricamente:

Realicemos una prueba de rendimiento en ambos:

Escenario: escritura secuencial de archivos 2G (escritos en lenguaje Go )

Tamaño de cada escritura | consumo de tiempo mmap | consumo de tiempo de escritura

--------------- | | -------- | --------

| 22,14s | >300s

|

| 512 bytes | 2,51s | 5,43s

| 1024s | 3,48s

| p>

| 4096 bytes | 2,48s | 1,74s

| 8192 bytes | 1,67s

|

Se puede ver que mmap básicamente alcanzó el rendimiento de escritura máximo al escribir 100 bytes, y la llamada de escritura debe ser 4096 (es decir, un tamaño de página) para lograr el rendimiento de escritura máximo.

Se puede ver en los resultados de la prueba que cuando se escriben datos pequeños, mmap es más rápido que la llamada de escritura, pero cuando se escriben datos grandes, no es tan rápido (pero no estoy seguro de si el rendimiento de La copia parcial de go es un problema, no tengo tiempo para probar C).

Los resultados de la prueba son consistentes con la derivación teórica.

Analicemos brevemente el proceso de llamada de lectura y mmap:

Como se puede ver en la figura, la llamada de lectura requiere una copia más que mmap. Debido a la llamada de lectura, el proceso no puede acceder directamente al espacio del kernel, por lo que antes de que regrese la llamada del sistema de lectura, el kernel necesita copiar los datos del kernel al búfer especificado por el proceso. Pero después de mmap, el proceso puede acceder directamente a los datos de mmap (caché de página).

En principio, el rendimiento de lectura será más lento que mmap.

A continuación, midamos la diferencia de rendimiento:

Escenario: lectura secuencial de archivos 2G (escritos en lenguaje Go)

(ps: para evitar el disco emparejamiento Para probar el impacto, dejé que los archivos 2G se almacenen en caché en pagecache)

Cada tamaño de lectura | mmap consume mucho tiempo | escribe mucho tiempo

-------- - ------ | ------- | -------- -------- 1 byte |

| 100 bytes | 86,4 ms | 8100,9 ms

| 512 bytes | 992,71 ms

p >

| 2048 bytes | 4,09 ms | 636,85 ms

| 4096 bytes | 444,83 ms

| 10240 bytes | 867.88?s | 475.28ms

Como se puede ver en lo anterior, en términos de lectura, la diferencia de rendimiento entre mmap y escritura sigue siendo muy grande. Los resultados de la prueba son consistentes con la derivación teórica.

Un conocimiento profundo de mmap puede ayudarnos a tomar mejores decisiones a la hora de diseñar sistemas de almacenamiento.

Por ejemplo, suponga que necesita diseñar un motor de almacenamiento independiente cuya estructura de datos subyacente es el árbol B y las operaciones de nodo están en unidades de página. Según la inferencia anterior, la escritura utiliza llamadas al sistema y la lectura. utiliza mmap, que puede lograr los mejores resultados. Excelente rendimiento. Así es como se implementa LMDB.