Notas del estudio de desarrollo impulsado por Linux [7]: concurrencia y competencia de Linux

Tabla de contenido

1. Concurrencia y competencia

2. Protección de recursos compartidos

1, manipulación atómica

1. Función API de operación de conformación atómica

2. Función API de manipulación de bits atómicos

2. Bloqueo giratorio

1. Función API de bloqueo de giro

2. Punto muerto con rotación automática

3. Precauciones para el bloqueo de giro

3. Semáforo

1. Características del semáforo

2. Función API de semáforo

4. Mutex

1. Características de Mutex

2. Función de API Mutex


1. Concurrencia y competencia

El sistema Linux es un sistema operativo multitarea. Habrá múltiples tareas que accedan a la misma área de memoria al mismo tiempo, y habrá competencia. Estas tareas pueden sobrescribir los datos en esta sección de memoria y causar confusión en los datos de la memoria. Este problema debe solucionarse. En casos graves, el sistema puede fallar. Las razones de la concurrencia en el sistema Linux actual son muy complicadas. En resumen, hay varias razones principales:

①. Acceso concurrente multiproceso. Linux es un sistema multitarea (subproceso), por lo que el acceso multiproceso es el factor más básico.

② Acceso concurrente preventivo: a partir del kernel 2.6, el kernel de Linux admite la preferencia, lo que significa que el programador puede adelantarse al hilo en ejecución en cualquier momento para ejecutar otros hilos.

③. Interrumpir el acceso simultáneo al programa. No hace falta decir que los estudiantes que han estudiado STM32 deben saber que el derecho de interrupción del hardware es excelente.

④, SMP (multi-core) acceso concurrente entre núcleos, SOC multi-core de la arquitectura ARM es muy común ahora, CPU multi-core tiene acceso concurrente entre núcleos.

2. Protección de recursos compartidos

Para proteger los recursos compartidos, el kernel de Linux proporciona varios métodos de procesamiento de concurrencia y competencia

1, manipulación atómica

Las operaciones atómicas se refieren a operaciones que no se pueden dividir más. Generalmente, las operaciones atómicas se utilizan para operaciones de bits o variables.

1. Función API de operación de conformación atómica

El kernel de Linux define una estructura llamada atomic_t para completar la operación atómica de dar forma a los datos. En uso, las variables atómicas se utilizan para reemplazar las variables de forma. Esta estructura se define en el archivo include / linux / types.h

typedef struct {
    int counter;
} atomic_t;

/*针对64位的整形变量*/
typedef struct { 
    long long counter; 
} atomic64_t;

Existen variables atómicas, el siguiente paso es operar sobre variables atómicas, como leer, escribir, aumentar, disminuir, etc. El kernel de Linux proporciona una gran cantidad de funciones API de operación atómica, como se muestra en la siguiente tabla:

 

2. Función API de manipulación de bits atómicos

La manipulación de bits también es una operación muy común. El kernel de Linux también proporciona una serie de funciones API de manipulación de bits atómicos, pero la manipulación de bits atómicos no tiene una estructura de datos atomic_t como las variables enteras atómicas. La manipulación de bits atómicos opera directamente en la memoria.

 

2. Bloqueo giratorio

Las operaciones atómicas solo pueden proteger variables enteras o bits, pero ¿cómo puede haber solo regiones críticas tan simples como variables enteras o bits en el entorno de uso real? Las operaciones atómicas en datos de estructura compleja no son competentes y se necesita el mecanismo de bloqueo en el kernel de Linux.

Cuando un hilo quiere acceder a un recurso compartido, primero debe adquirir el bloqueo correspondiente. El bloqueo sólo puede ser retenido por un hilo. Mientras este hilo no libere el bloqueo retenido, otros hilos no podrán adquirir el bloqueo. Para el bloqueo de giro, si el bloqueo de giro está retenido por el hilo A y el hilo B quiere adquirir el bloqueo de giro, entonces el hilo B estará en un estado ocupado de bucle-giro-espera , y el hilo B no entrará en el estado de suspensión ni dirá Para hacer otro procesamiento, pero siempre estará esperando tontamente a que el candado esté disponible. El "giro" del bloqueo de giro también significa "giro en el lugar". El propósito de "giro en el lugar" es esperar a que el bloqueo de giro esté disponible y acceder a los recursos compartidos . Desde aquí podemos ver una desventaja del bloqueo de giro: el hilo que espera el bloqueo de giro siempre estará en el estado de giro, lo que desperdiciará el tiempo del procesador y reducirá el rendimiento del sistema, por lo que el tiempo de retención del bloqueo de giro no puede ser demasiado largo. . Por lo tanto, el bloqueo giratorio es adecuado para un bloqueo ligero a corto plazo. Si se encuentra en un escenario que requiere mucho tiempo para mantener el bloqueo, debe cambiar otros métodos.

El kernel de Linux usa la estructura spinlock_t para representar el bloqueo de giro, y la definición de la estructura es la siguiente:

typedef struct spinlock {
union {
    struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
    struct {
        u8 __padding[LOCK_PADSIZE];
        struct lockdep_map dep_map;
    };
#endif
	};
} spinlock_t;

1. Función API de bloqueo de giro

2. Punto muerto con rotación automática

Las funciones de la API de la tabla anterior se utilizan para el acceso simultáneo entre subprocesos. Si la interrupción también debe insertarse en este momento, y la interrupción también quiere acceder a recursos compartidos, ¿qué se debe hacer? En primer lugar, es seguro que el bloqueo de giro se puede usar en la interrupción , pero cuando se usa el bloqueo de giro en la interrupción, la interrupción local debe deshabilitarse antes de adquirir el bloqueo (es decir, la interrupción de CPU, que es más para un SOC de varios núcleos. Núcleos de CPU), de lo contrario, puede causar bloqueo

 

El subproceso A se ejecuta primero y adquiere el bloqueo de bloqueo. Cuando el subproceso A ejecuta la función functionA, se produce una interrupción y la interrupción quita los derechos de uso de la CPU. La función de servicio de interrupción de la derecha también adquiere el bloqueo de bloqueo, pero este bloqueo está ocupado por el hilo A, y la interrupción continuará girando, esperando que el bloqueo sea válido. Pero antes de que se ejecute la función de servicio de interrupción, es imposible ejecutar el subproceso A. El subproceso A dice "Soltar primero" y la interrupción dice "Soltar primero" ¡La escena está tan estancada y se produce un punto muerto! La mejor solución es cerrar la interrupción local antes de adquirir el bloqueo El kernel de Linux proporciona las funciones API correspondientes, como se muestra en la siguiente tabla. Generalmente use spin_lock_irqsave / spin_unlock_irqrestore en subprocesos y spin_lock / spin_unlock en interrupciones

3. Precauciones para el bloqueo de giro

①. Debido a que está en el estado de "giro" mientras espera el bloqueo de giro, el tiempo de retención del bloqueo no puede ser demasiado largo, debe ser corto, de lo contrario reducirá el rendimiento del sistema. Si la sección crítica es relativamente grande y el tiempo de ejecución es relativamente largo, se deben seleccionar otros métodos de procesamiento concurrente, como semáforos y mutex que se discutirán más adelante.

② No se puede llamar a ninguna función de API que pueda hacer que el hilo entre en suspensión en el área crítica protegida por el bloqueo de giro; de lo contrario, puede causar un interbloqueo.

③. No puede solicitar un bloqueo de giro de forma recursiva, porque una vez que solicita un bloqueo que está manteniendo a través de la recursividad, debe "girar" y esperar a que se libere el bloqueo. Sin embargo, se encuentra en un estado de "giro" y no hay Método para liberar la cerradura. ¡El resultado es que me encerré muerto!

④. Debemos considerar la portabilidad del controlador al escribir el controlador. Por lo tanto, no importa si utiliza un SOC de un solo núcleo o de varios núcleos, utilícelo como un SOC de varios núcleos para escribir el controlador.

3. Semáforo

1. Características del semáforo

①. Debido a que el semáforo puede hacer que el hilo de recursos en espera entre en estado de suspensión, es adecuado para aquellas ocasiones que ocupan recursos durante mucho tiempo.

② Por lo tanto, el semáforo no se puede usar en la interrupción, porque el semáforo provocará el sueño y la interrupción no puede dormir.

③. Si el tiempo de retención de los recursos compartidos es relativamente corto, no es adecuado usar semáforos, porque la sobrecarga causada por la suspensión frecuente y el cambio de hilos es mucho mayor que la ventaja de los semáforos.

2. Función API de semáforo

El kernel de Linux utiliza la estructura del semáforo para representar el semáforo. El contenido de la estructura es el siguiente:

struct semaphore { 
    raw_spinlock_t lock; 
    unsigned int count; 
    struct list_head wait_list; 
};

 

4. Mutex

1. Características de Mutex

Establezca el valor del semáforo en 1 para usar el semáforo para el acceso exclusivo mutuo. Aunque la exclusión mutua se puede lograr a través del semáforo, Linux proporciona un mecanismo más profesional para la exclusión mutua que el semáforo. Es un mutex mutex. El acceso mutex significa que solo un subproceso puede acceder a los recursos compartidos a la vez y no puede solicitar un mutex de forma recursiva. Cuando escribimos controladores de Linux, recomendamos usar mutex en lugares donde se requiere acceso exclusivo mutuo.

struct mutex { 
    /* 1: unlocked, 0: locked, negative: locked, possible waiters */ 
    atomic_t count; 
    spinlock_t wait_lock; 
};

Antes de usar mutex, defina una variable mutex. Preste atención a los siguientes puntos cuando utilice mutex:

① Mutex puede causar suspensión, por lo que no se puede usar mutex en interrupción, y solo se puede usar bloqueo de giro en interrupción.

②. Como el semáforo, la sección crítica protegida por mutex puede llamar a la función API que causa el bloqueo.

③. Debido a que solo un subproceso puede contener un mutex a la vez, el titular del mutex debe liberar el mutex. Y mutex no se puede bloquear y desbloquear de forma recursiva.

2. Función de API Mutex

 

Supongo que te gusta

Origin blog.csdn.net/m0_37845735/article/details/106989140
Recomendado
Clasificación