Un artículo para comprender | Bloqueos secuenciales del kernel

El kernel de Linux tiene muchos mecanismos de bloqueo, como bloqueos de giro, bloqueos de lectura y escritura, semáforos y bloqueos de RCU. Este artículo presenta un mecanismo de bloqueo similar a los bloqueos de lectura y escritura: bloqueo secuencial (seqlock).

Los bloqueos secuenciales, como los bloqueos de lectura y escritura, son mecanismos de bloqueo para más lectura, menos escritura y procesamiento rápido. La diferencia entre bloqueos secuenciales y bloqueos de lectura y escritura es que el bloqueo de lectura de un bloqueo de lectura y escritura bloquea el bloqueo de escritura, pero el bloqueo de lectura de un bloqueo secuencial no bloquea el bloqueo de escritura.

Leer principio de bloqueo

Para evitar que el bloqueo de lectura bloquee el bloqueo de escritura, el bloqueo de lectura en realidad no realizará la operación de bloqueo. Entonces, ¿cómo evita el bloqueo de lectura que otros procesos modifiquen los datos al leer datos de secciones críticas?

Para solucionar este problema, los bloqueos secuenciales utilizan un mecanismo similar a los números de versión: 序号. El número de serie es un contador que solo aumenta pero no disminuye, como se puede ver en la definición del objeto de bloqueo secuencial, como se muestra en el siguiente código:

typedef struct {
     struct seqcount seqcount; // 序号
     spinlock_t lock;          // 自旋锁,写锁上锁时使用
} seqlock_t;

Antes de leer los datos de la sección crítica, primero debe llamar  read_seqbegin() a una función para obtener el bloqueo de lectura. read_seqbegin() La lógica central de la función es leer el número de secuencia del bloqueo secuencial. El código se ve así:

static inline unsigned read_seqbegin(const seqlock_t *sl)
{
    unsigned ret;

repeat:
    // 读取顺序锁的序号
    ret = sl->sequence;

    // 如果序号是单数,需要重新获取
    if (unlikely(ret & 1)) {
        ...
        goto repeat;
    }
    ...
    return ret;
}

Como se puede ver en el código anterior, read_seqbegin() la función solo obtiene el número de secuencia del bloqueo secuencial y no realiza ninguna operación de bloqueo, por lo que el bloqueo de lectura no bloquea el bloqueo de escritura.

Nota: La razón por la que es necesario volver a adquirir el número de serie cuando es un número impar se explicará al analizar el principio de implementación del bloqueo de escritura.

Dado que el bloqueo de lectura no realiza una operación de bloqueo, ¿qué sucede si los datos se modifican al leer los datos de la sección crítica? La respuesta es: al salir de la sección crítica, compare el número de secuencia del bloqueo de secuencia actual con el número de secuencia leído anteriormente. Si es consistente significa que los datos no han sido modificados, en caso contrario significa que los datos han sido modificados. Si se modifican los datos, es necesario volver a leer los datos de la sección crítica.

Puede usar  read_seqretry() funciones para comparar si los números de serie son consistentes, por lo que el uso correcto del bloqueo de lectura se muestra en el siguiente código:

do {
    // 获取顺序锁序号
    unsigned seq = read_seqbegin(&seqlock);
    // 读取临界区数据
    ...
} while (read_seqretry(&seqlock, seq)); // 对比序号是否一致?

read_seqretry() La implementación de la función es muy simple y se ve así:

static inline unsigned 
read_seqretry(const seqlock_t *sl, unsigned start)
{
    ...
    return sl->sequence != start;
}

Como se puede ver en el código anterior, read_seqretry() la función simplemente compara si el número de serie actual es consistente con el número de serie leído anteriormente.

Principio de bloqueo de escritura

Del análisis anterior, se puede ver que el bloqueo de lectura determina si los datos se han modificado comparando si los números de serie antes y después son consistentes. Entonces, ¿cuándo se modificó el número de serie? La respuesta es: al adquirir el bloqueo de escritura.

La obtención del bloqueo de escritura se  write_seqlock() implementa mediante una función, y su implementación es relativamente simple, el código es el siguiente:

static inline void write_seqlock(seqlock_t *sl)
{
    spin_lock(&sl->lock);

    sl->sequence++;
    ...
}

write_seqlock() La función primero adquiere el bloqueo de giro (por lo que el bloqueo de escritura y el bloqueo de escritura son mutuamente excluyentes) y luego agrega uno al número de secuencia. Por lo tanto, antes de modificar los datos de la sección crítica, el bloqueo de escritura primero aumentará el valor del número de secuencia, lo que hará que los números de secuencia adquiridos dos veces antes y después del bloqueo de lectura sean inconsistentes. Podemos ilustrar esta situación con el siguiente diagrama:

principio de bloqueo secuencial

 Information Direct: ruta de aprendizaje de tecnología del código fuente del kernel de Linux + video tutorial sobre el código fuente del kernel

Learning Express: Código fuente del kernel de Linux Ajuste de memoria Sistema de archivos Gestión de procesos Controlador de dispositivo/Pila de protocolo de red

Se puede ver que cuando los valores del número de secuencia obtenidos antes y después de leer la sección crítica son inconsistentes, significa que los datos se han modificado y es necesario volver a leer los datos modificados.

Desbloquear el bloqueo de escritura también es muy sencillo, el código es el siguiente:

static inline void write_sequnlock(seqlock_t *sl)
{
  ...
 s->sequence++;
 spin_unlock(&sl->lock);
}

El desbloqueo también requiere agregar uno al número de serie y luego liberar el bloqueo de giro.

Dado que  write_seqlock() las funciones y  write_sequnlock() funciones agregarán uno al número de serie, después del desbloqueo, el valor del número de serie debe ser un número par.

Cuando analizamos el bloqueo de lectura, vimos que si el número de serie es un número impar, el número de serie se volverá a adquirir hasta que el número de serie sea un número par. Esto se debe a que cuando el número de secuencia es impar, significa que los datos se están actualizando. No tiene sentido leer el valor de la sección crítica en este momento, por lo que debe esperar hasta que se complete la actualización antes de leer.

Autor original: Cosas sobre el kernel de Linux

Supongo que te gusta

Origin blog.csdn.net/youzhangjing_/article/details/132693265
Recomendado
Clasificación