Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)

Campo de lectura original Song Baohua Linux2019-12-23


Prefacio

Hay más de decenas de millones de artículos sobre el proceso de lectura y escritura de BIO y dispositivos de bloqueo en Internet, pero es difícil encontrar artículos que puedan hacerte comprender y comprender completamente. Se puede decir que cuanto más lees, ¡más confundido estás!

Solía ​​cruzar las montañas y el mar y cruzar el mar de gente

Le pregunté al mundo entero y nunca obtuve la respuesta.

Este artículo utiliza el código de lectura más simple (fd, buf, 4096) para analizar su magnífica vida en todo el sistema Linux desde el principio hasta el final. El código involucrado en este artículo es el siguiente:

#include <unistd.h>

#include <fcntl.h>

main()

{
        int fd;

        char buf[4096];

        sleep(30); //run ./funtion.sh to trace vfs_read of this process

        fd = open("file", O_RDONLY);

        read(fd, buf, 4096);

        read(fd, buf, 4096);

}

El propósito de redacción de este artículo es: ¡nunca pretendas ser forzado, debe ser simple, simple y simple!


Este artículo es adecuado para lectores que han leído muchos materiales de bloques desordenados, pero no han abierto el contexto;

Este artículo no es adecuado para: lectores que no saben qué es el subsistema de bloques y lectores que saben exactamente qué es el subsistema de bloques.


Caché de páginas y lectura previa

En Linux, la memoria actúa como el caché de página del disco duro. Por lo tanto, cada vez que lea, primero verificará si los datos del archivo del disco duro que lee están afectados por la memoria. Si no hay ningún hit, irá al disco duro; si ha sido afectado, entonces Léelo directamente de la memoria. Si está escribiendo, si la aplicación está escrita en un modo no SYNC, los datos escritos solo ingresarán a la memoria, y luego el kernel ayudará a escribir en el disco duro en el momento apropiado.
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
Hay dos líneas de lectura (fd, buf, 4096) en el código. Cuando ocurre la primera línea de lectura (fd, buf, 4096), obviamente los datos en el archivo "archivo" no están en la memoria. En este momento, se debe realizar una lectura real del disco duro , La aplicación solo quiere leer 4096 bytes (una página), pero el kernel no solo leerá una página, sino que leerá más, leerá con anticipación y leerá primero lo que los usuarios no leen ahora, porque el kernel sospecha que usted lee una página , Y luego lea continuamente, sospechando que quiere leer más tarde. En lugar de esperar a que envíe instrucciones, es mejor cortar y jugar con anticipación (el medio de almacenamiento realiza una lectura de bloque grande más rápido que varias lecturas de bloque pequeño). En este momento, realizará una lectura previa, como leer 4 páginas directamente, de modo que cuando continúe leyendo en segundo lugar Cuando hay 4 páginas de datos en el disco duro, en realidad llega directamente.

Entonces, la ruta del código es ahora:
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
cuando ejecuta la primera lectura (fd, buf, 4096), se ingresan 0 ~ 16 KB del archivo "archivo" en el caché de página, y el kernel marcará la segunda página con una etiqueta PageReadahead, lo que significa Es decir, si la aplicación continúa leyendo la segunda página, puede predecir que la aplicación está haciendo una lectura secuencial, de modo que cuando la aplicación lee la segunda página, el kernel puede realizar una lectura previa asincrónica.

Antes de la primera lectura (fd, buf, 4096), el caché de la página llega (ninguno): después de la
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
primera lectura (fd, buf, 4096), el caché de la página llega:
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
llegamos a la segunda inmediatamente read (fd, buf, 4096), necesita leer el contenido de la página 2 del archivo del disco duro. En este momento, la caché de la página llega a la página 2. Esta vez, debido a que la página 2 tiene la marca PageReadahead, el kernel cree que la aplicación es Al leer archivos de forma secuencial, el kernel realizará una lectura previa asíncrona más agresiva, como leer los 16 KB ~ 48 KB del archivo.

Entonces, la ruta del código de la segunda lectura (fd, buf, 4096) es ahora: antes de la
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
segunda lectura (fd, buf, 4096), la situación de acierto de la caché de página: después de la
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
segunda lectura (fd, buf, 4096), página Situación de golpe de caché:
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)

Conversión de memoria a disco duro

Como mencionamos anteriormente, la primera lectura (fd, buf, 4096) se convierte en la lectura de datos de 16KB en el disco duro a 4 páginas de memoria (correspondientes a los 0 ~ 16KB de datos de archivo en el disco duro). ¿Pero todavía no sabemos dónde se encuentran los 0 ~ 16 KB de los datos del archivo en el disco duro en el disco duro? Debemos convertir las páginas de la memoria a la ubicación real en el disco duro a leer.

En Linux, la estructura de datos utilizada para describir la relación de mapeo entre la ubicación de la operación real en el disco duro y la página de caché de la página es bio. Creo que has visto la biografía 10.000 veces, pero no coincide con el caso real.

La definición de bio es la siguiente (incluya / linux / blk_types.h):

struct bio_vec {

       struct page    *bv_page;

       unsigned int  bv_len;

       unsigned int  bv_offset;

};

struct bio {

       struct bio              *bi_next; /* request queue link */

       struct block_device      *bi_bdev;

       …

       struct bvec_iter     bi_iter;

       /* Number of segments in this BIO after

        * physical address coalescing is performed.

        */

       unsigned int         bi_phys_segments; 

       …

       bio_end_io_t         *bi_end_io;

       void               *bi_private;

       unsigned short            bi_vcnt;  /* how many bio_vec's */

       atomic_t        bi_cnt;           /* pin count */

       struct bio_vec       *bi_io_vec;     /* the actual vec list */

       …

};

Es una estructura de datos que describe la relación correspondiente entre la posición en el disco duro y la página de caché de página. Cada biografía corresponde a una posición continua en el disco duro. La posición continua en cada disco duro puede corresponder a varias páginas en la caché de página, o una Page, por lo que habrá una tabla bio_vec * bi_io_vec en ella.

Ahora asumimos 2 situaciones

El primer caso es que los datos de 0 ~ 16KB que debe leer page_cache_sync_readahead () están ordenados en secuencia en el disco duro (si están ordenados, verifique el sistema de archivos, como ext3, ext4), Linux leerá 4 páginas esta vez, Asignar 1 biografía es suficiente, y dejar que esta biografía asigne 4 bi_io_vec, apuntando a 4 páginas de memoria diferentes:
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
el segundo caso son los datos de 0 ~ 16KB que debe leer page_cache_sync_readahead (), que resulta ser completamente discontinuo en el disco duro Los 4 bloques (ya sea que estén dispuestos en orden, verifique el sistema de archivos, como ext3, ext4), Linux asignará 4 BIOS para esta lectura de 4 páginas y permitirá que cada uno de estos 4 BIOS asigne 1 bi_io_vec, apuntando a 4 páginas de memoria diferentes: por
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
supuesto, también puede tener la tercera situación, como 0 ~ 8KB continuos en el disco duro, 8 ~ 16KB discontinuos, puede ser así:
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
otras situaciones por favor razonamiento similar ... para completar este trabajo El código épico es mpage_readpages ().
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
mpage_readpages () llamará indirectamente a ext4_get_block () para averiguar realmente la posición de 0 ~ 16KB de datos en el disco duro y, en base a esta información, convertir una biografía.

Tres dentro y tres fuera de bio y solicitud

La vida, al final, es tan simple como la vida o la muerte. Pero debido a los altibajos del destino y la calidez del mundo, el proceso simple se ha convertido en altibajos y complicado. Xiaoping tuvo tres altibajos y finalmente estableció una hazaña inmortal. Mandela fue tratado de manera inhumana y pasó décadas en prisión y finalmente se convirtió en un líder reconocido en el mundo. El camino hacia la libertad no será fácil, la lucha es la vida. Luchar contra el cielo es una alegría sin fin; luchar contra la tierra es una alegría sin fin; ¡luchar contra Linux es un dolor sin fin!

Después de que se produjo la biografía, hasta su finalización, también experimentó una cola de tres en tres. Las dificultades y los dolores de este proceso hicieron que la gente quisiera detenerse.

Los tres pasos son:

1. Reunión en el lugar

Convierta la biografía en solicitud, coloque la solicitud en la cola de conexión local del proceso; después de acumular varias solicitudes, libere la inundación.

2. Clasificación de ascensores

La solicitud de la cola de conexión local del proceso ingresa al elevador, se fusiona y ordena nuevamente, ejecuta la cola de QoS y luego se distribuye al controlador del dispositivo de bloque de acuerdo con el resultado de QoS. Puede haber varias colas para la realización interna del ascensor.

3. Ejecución de distribución

Las solicitudes distribuidas por el elevador son eliminadas una a una por request_fn () del controlador del dispositivo, y los comandos de lectura y escritura del hardware real se envían al disco duro. Esta cola despachada es generalmente la request_queue que vemos en el controlador del dispositivo de bloque.
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)

A continuación presentamos uno a uno, estos tres adentro y tres afuera.

Reuniéndose en su lugar

En Linux, cada task_struct (correspondiente a un proceso, o subproceso de proceso ligero), habrá una lista de complementos. ¿Qué es el enchufe? Al igual que en Gezhouba y Three Gorges, el agua se almacena primero. Cuando la aplicación necesita enviar múltiples solicitudes de biografía, la mejor manera es ganar impulso primero, en lugar de enviarlas al disco duro final una por una.

Esto es similar a cómo ahora tiene 10 maestros, y todos estos 10 maestros aceptan el registro de estudiantes cuando comienza la escuela. Luego hay una gran cola de estudiantes. Si cada profesor visita la única cola de estudiantes cuando un estudiante se registra, entonces el funcionamiento de esta cola se convertirá en un importante cuello de botella de bloqueo:
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
si cambiamos el método, dejemos que cada Cuando el maestro tiene estudiantes registrándose, los estudiantes que se registran todos los días se quedan en la cola del maestro. Después de que muchos estudiantes se cuelgan en la cola del maestro, la inundación se liberará un día después y se mantendrá en la fila final de estudiantes para evitar este problema. Cuando la cola se fusiona en una cola grande, es bueno controlar el tiempo.

Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
Verá que la ruta del código es así: la
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
función read_pages () primero levanta la puerta, luego inicia una serie de BIOS y luego libera la inundación a través de la llamada de blk_finish_plug ().
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
En el proceso de ganar impulso, se debe completar una tarea importante, que es hacer una solicitud. Esta función de nivel épico para completar la "solicitud" generalmente es nula blk_queue_bio (struct request_queue q, struct bio bio), ubicada en block / blk-core.c.

Intentará fusionar la biografía en una solicitud en la lista de complementos locales de un proceso. Si no se puede fusionar, creará una nueva solicitud. La solicitud contiene una lista de biografía, y la ubicación del disco duro correspondiente a la biografía en esta lista se almacena finalmente de forma continua en el disco duro.

A continuación asumimos que la ubicación de almacenamiento de 0 ~ 16KB del "archivo" en el disco duro es:
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
De acuerdo con el ejemplo que dimos en la sección "Conversión de memoria a disco duro", este pertenece al "caso 2" que es completamente discontinuo en el disco duro, por lo que este 4 piezas de datos se convertirán en 4 bio por épico mpage_readpages ().
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
Cuando ingresan a la lista de complementos local del proceso, dado que la lista de complementos está vacía al principio, obviamente 100 no se puede fusionar con alguien, formando así una nueva solicitud0.

Bio1 no se puede fusionar con request0, por lo que obtiene una nueva request1.

Bio2 se puede fusionar en request1, por lo que Bio1 se fusiona en request1.

Bio3 corresponde a 200 bloques del disco duro y no se puede combinar, por lo que se obtiene una nueva solicitud2.

Ahora, las solicitudes en la lista de conexiones locales del proceso se organizan de la siguiente manera:
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)

Cuando se libera la inundación, la solicitud de la lista de conexiones locales del proceso se agregará a la cola del elevador llamando a la función de devolución de llamada lift_add_req_fn () del algoritmo de programación del elevador.

Clasificación de ascensores

Cuando la solicitud en la lista de enchufes locales de cada proceso se inunda y entra de manera abrumadora, no es el controlador del dispositivo final (que no se matará directamente en la playa), sino un algoritmo de cola de ascensor para volver a hacer cola. . Este despacho de ascensor tiene tres propósitos:

  1. Solicitud de fusión adicional
  2. Hacer que la solicitud de acceso al disco duro sea secuencial
  3. Realizar QoS

La implementación interna del ascensor puede ser muy flexible, pero la entrada es ascensor_add_req_fn () y la salida es ascensor_dispatch_fn ().

Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
Tanto la combinación como la clasificación son fáciles de entender A continuación, nos enfocamos en QoS (Calidad de servicio). Imagine la banda ancha en su hogar, hay Thunder, hay películas en línea y la caja superior orgánica se usa para ver la televisión.

Cuando solo usa Thunder para descargar películas, por supuesto que puede descargar películas a toda velocidad, pero cuando todavía está viendo televisión y películas en línea, en este momento, puede limitar la corriente de Thunder para garantizar la calidad del servicio de las películas relacionadas con TV Box.

La misma lógica también se ejecuta en la programación de ascensores, como el algoritmo de programación CFQ, que puede ajustar la prioridad de diferentes procesos al acceder al disco duro de acuerdo con la iónica del proceso. Por ejemplo, los siguientes dos dds con diferentes prioridades

# ionice-c 2 -n 0 cat /dev/sda > /dev/null&

# ionice -c 2 -n 7 cat /dev/sda >/dev/null&

La velocidad de acceso final del disco duro es diferente, una es 371M y la otra solo 72M.
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)
Entonces, cuando comienza la descarga de la inundación, el río está lleno de amargura y los cientos de ríos luchan por la corriente. ¿Quién puede golpear el agua en el medio del arroyo y las olas detendrán el bote volador? QoS es una historia sobre el éxito de todo .

Los algoritmos de programación de ascensores IO que se utilizan habitualmente en la actualidad son: cfq, noop, deadline. La diferencia detallada no es el tema central de este artículo Se recomienda leer "Liu Zhengyuan: Programador de E / S de línea de tiempo límite de capa de bloque general de Linux" para comprender la implementación de los plazos.

Ejecución de distribución

Cuando se trata de la última vez que se cruza la brecha, request_fn () del controlador del dispositivo llama a lift_dispatch_fn () del algoritmo de programación del ascensor para recuperar la solicitud ordenada de QoS y enviar el comando al dispositivo de almacenamiento final para realizar acciones de E / S.

static void xxx_request_fn(struct request_queue *q)

{

        struct request *req;

        struct bio *bio;

        while ((req = blk_peek_request(q)) != NULL) {

                struct xxx_disk_dev *dev = req->rq_disk->private_data;

                if (req->cmd_type != REQ_TYPE_FS) {

                        printk (KERN_NOTICE "Skip non-fs request\n");

                        blk_start_request(req);

                        __blk_end_request_all(req, -EIO);

                        continue;

                }

                blk_start_request(req);

                __rq_for_each_bio(bio, req)

                        xxx_xfer_bio(dev, bio);

        }

}

request_fn () solo envía eventos y comandos de lectura y escritura, y la finalización final generalmente se realiza en otro contexto, no en el proceso de iniciar IO. Una vez que se completa el procesamiento de la solicitud, el contexto que detecta la finalización del IO notificará al proceso que espera la finalización de la solicitud IO en forma de blk_end_request (). La secuencia de código del proceso que inicia activamente IO es generalmente:

  • submit_bio ()
  • io_schedule (), abandona la CPU.

blk_end_request () generalmente despierta procesos que abandonaron la CPU después de io_schedule (). El tiempo de espera de io_schedule () se calculará en el tiempo de espera del proceso. Para obtener más detalles, consulte: "Zhu Hui (té): principio de código de tiempo de espera del kernel de Linux".

Capture todos los procesos con Ftrace

Todos los procesos involucrados en este artículo se pueden rastrear con ftrace. De esta forma, puede conocer más y detalles más profundos.


        char buf[4096];

        sleep(30); //run ./funtion.sh to trace vfs_read of this process

        fd = open("file", O_RDONLY);

        read(fd, buf, 4096);

En medio del código anterior, dejé deliberadamente un retraso de 30 segundos. Durante este retraso, puede iniciar el siguiente script para rastrear el gráfico de funciones de todo el proceso. Después de que el proceso de captura comience a vfs_read () La pila de llamadas:

#!/bin/bash

debugfs=/sys/kernel/debug

echo nop > $debugfs/tracing/current_tracer

echo 0 > $debugfs/tracing/tracing_on

echo `pidof read` > $debugfs/tracing/set_ftrace_pid

echo function_graph > $debugfs/tracing/current_tracer

echo vfs_read > $debugfs/tracing/set_graph_function

echo 1 > $debugfs/tracing/tracing_on

El autor también utilizó el resultado de ftrace, lo abrió con vim y lo analizó frase por frase. Para conocer el método detallado utilizado por ftrace, puede leer "Song Baohua: Un caso completo sobre Ftrace".
Song Baohua: una vida magnífica para la lectura y escritura de archivos de Linux (BIO)

Ultimas palabras

Este artículo describe el tronco, muchos detalles y ramas de código no están involucradas, porque en este artículo se describen demasiadas ramas, lo que hará que los lectores no puedan captar el tronco. Muchas ramas no se han introducido, como la inundación de desconexiones. Además de la inundación artificial blk_finish_plug (), la inundación automática también ocurre cuando la cola de enchufes está llena y cuando el proceso está inactivo schedule (). Además, con respecto a la escritura, el siguiente proceso de tres entradas y tres salidas es básicamente similar a la lectura, pero la escritura tiene un mecanismo de inicio de escritura y apilamiento de caché de página, que la lectura no tiene.

(Terminar)

Supongo que te gusta

Origin blog.51cto.com/15015138/2555506
Recomendado
Clasificación