Programando módulos para el Kernel de Linux. Simulaciones de casos de concurrencia.

Programando módulos para el Kernel de Linux. Simulaciones de casos de concurrencia.
Facebook Twitter Flipboard E-mail

En el último artículo de la serie hablábamos sobre la concurrencia en el Kernel de Linux. Hoy, en programando módulos para el Kernel de Linux vamos a zambullirnos de lleno en unas simulaciones de lo que podrían ser casos reales de protección de secciones críticas.

Vamos a presentar varios escenarios con plataformas y configuraciones del Kernel diferentes incrementando la complejidad conforme vayamos avanzando en el artículo.

El único caso que no vamos a tratar es el de una sección crítica en el contexto de procesos con un único procesador en un Kernel configurado para no ser interrumpible (non preemtible) al no ser necesario ningún tipo de bloqueo.

Caso 1: Sección crítica presente en contextos de proceso e interrupción con un único procesador ejecutando un Kernel no interrumpible

En este escenario es necesario deshabilitar solo las interrupciones para proteger la sección crítica. Para escenificar este supuesto vamos a tratar A y B como hilos del contexto de procesos. C es un hilo del contexto de interrupciones.

Los tres hilos intentan entrar a la sección crítica. Como el hilo C esta siendo ejecutado en el contexto de interrupciones y siempre se ejecutará hasta el final antes de liberar la sección para cualquier otro hilo, no tiene que preocuparse por la protección de los datos.

Por otro lado, el hilo A no tiene por qué conocer datos sobre el hilo B y vice versa puesto que el Kernel no es interrumpible. Por lo tanto, los hilos A y B necesitan protegerse contra la posibilidad de que el hilo C entre en la sección crítica mientras ellos están dentro.

La forma de protegerse es mediante la desactivación de las interrupciones antes de entrar en la sección crítica:

Punto Critico:
/* desactiva las interrupciones en la CPU local*/
local_irq_disable();
/* ... sección crítica ... */
local_irq_enable();
Si las interrupciones estaban ya deshabilitadas cuando la ejecución alcanza el Punto Critico, local_irq_enable() produce el efecto adverso y no deseado de rehabilitar las interrupciones en lugar de restaurar el estado de interrupciones. Esto puede arreglarse de la siguiente forma:
unsigned long flags;
Punto Critico:
/* desactiva las interrupciones */
local_irq_save(flags);
/* ... sección crítica ... */
local_irq_enable(); /* restaura el estado previo al Punto Critico */
El código anterior funciona de la forma esperada independientemente del estado antes de alcanzarse el Punto Critico.

Caso 2: Sección crítica presente en contexto de procesos e interrupciones con un único procesador ejecutando un Kernel interrumpible

Si el Kernel está configurado para ser interrumpible, desactivar solo las interrupciones no protege nuestra sección crítica de sufrir corrupciones. En este escenario existe la posibilidad de que múltiples hilos del contexto de procesos entre en la sección crítica a la vez.

Por lo tanto, en este escenario los hilos A y B necesitan protegerse el uno del otro contra intrusiones en la sección crítica cuando uno de ellos está dentro de la sección además de protegerse de la irrupción del hilo C en la misma.

La solución más aparente es deshabilitar la posibilidad de interrupción del Kernel antes de entrar en la sección crítica y su reactivación al salir de ella igual que hacemos con las interrupciones:

unsigned long flags;
Punto Critico:
/* guarda el estado de las interrupciones
    desactiva las interrupciones y la posibilidad de interrumpir al Kernel (preemption) */
spin_lock_irqsave(&klock, flags);
/* ... sección crítica ... */
/* restaura el estado de las interrupciones al estado antes del Punto Critico */
spin_unlock_irqrestore(&klock, flags);
El estado de las interrupciones del Kernel (preemption) no necesita ser restaurado de forma explícita ya que el Kernel se ocupa de ello por nosotros gracias a una variable llamada preemption_counter.

El contador se incrementa siempre que desactivamos la interrupción del Kernel y decrementa cuando volvemos a activarla. La posibilidad de interrumpir al Kernel solo se reactiva cuando el valor llega a cero.

Caso 3: Sección crítica presente en contexto de procesos e interrupciones con mútiples procesadores ejecutando un Kernel interrumpible

En este escenario, la sección crítica se ejecuta en una máquina con capacidad de multi procesamiento simétrico. El Kernel ha sido configurado con las opciones CONFIG_SMP y CONFIG_PREEMPT habilitados.

En presencia de multi procesamiento simétrico, las primitivas spinlock deben de tener un comportamiento SMP-safe. La semántica SMP es como sigue:

unsigned long flags;
Punto Critico:
/*
 * guarda el estado de interrupciones en la CPU local
 * desactiva las interrupciones y la interrupción del Kernel en la CPU local
 * adquiere la sección crítica para regular el acceso por otras CPUs
 */
spin_lock_irqsave(&klock, flags);
/* ... sección crítica ... */
/*
 * restaura el estado de las interrupciones y la interrupción del Kernel
 * al punto en el que estaba la CPU local al llegar a Punto Critico
 * libera la sección crítica
 */
spin_unlock_irqrestore(&klock, flags);
En sistemas con multi procesamiento simétrico, solo se desactivan las interrupciones en la CPU local cuando se adquiere un spinlock. Así que un hilo en el contexto de procesos puede ejecutarse en la CPU local mientras que un hilo del contexto de interrupciones puede ejecutarse en otra CPU.

Un manejador de interrupciones o un procesador no local necesitan esperar al spin hasta que el hilo del contexto de procesos abandona la sección crítica. Esto se consigue de la siguiente manera:

spin_lock(&klock);
/* ... sección crítica ... */
spin_unlock();

Conclusión

El Kernel nos provee de primitivas de bloqueo y sincronización especializadas que nos ayudan a mejorar el rendimiento en ciertas circunstancias. Hoy hemos visto tres posibles escenarios y como se protege una sección crítica en cada uno de ellos. En el siguiente artículo hablaremos sobre las operaciones atómicas.


En Genbeta Dev | Programación a pecho descubierto (Linux Kernel)

Comentarios cerrados
Inicio