GNU/Linux >> Tutoriales Linux >  >> Linux

Kernel UNIX:Kernels reentrantes, sincronización y secciones críticas

Este artículo es parte de nuestra serie actual de descripción general del kernel de UNIX.

En el artículo anterior de esta serie, hablamos sobre la descripción general del proceso UNIX.

Este artículo explica a alto nivel los núcleos reentrantes, la sincronización y las secciones críticas de la arquitectura del núcleo UNIX.

Núcleos reentrantes

Como sugiere el nombre, un kernel reentrante es el que permite que múltiples procesos se ejecuten en el modo kernel en un momento dado y eso también sin causar problemas de coherencia entre las estructuras de datos del kernel.

Bueno, sabemos que en un sistema de un solo procesador solo se puede ejecutar un proceso en un instante dado, pero podría haber otros procesos bloqueados en modo kernel esperando ser ejecutados.

Por ejemplo, en un kernel reentrante, un proceso que espera una llamada de lectura () puede decidir liberar la CPU a un proceso que está esperando su ejecución en modo kernel.

Ahora, uno podría tener una pregunta en mente sobre por qué un núcleo se vuelve reentrante. Bueno, comencemos con un ejemplo en el que un kernel no es reentrante y veamos qué sucede si permite que se ejecuten múltiples procesos en modo kernel.

Supongamos que un proceso se ejecuta en modo kernel y accede a una estructura de datos del kernel y algunos valores globales asociados con ella.

  • Suponga que el nombre del proceso es 'A'.
  • Ahora 'A' accede a una variable global para ver si el valor es distinto de cero (para que pueda hacer algunos cálculos, etc.) y justo antes de que intente usar este valor en parte de su lógica, un cambio de contexto para procesar ' B' sucede.
  • Ahora este proceso 'B' intenta acceder al valor de la misma variable global y lo decrementa.
  • Ocurre otro cambio de contexto y el proceso 'A' vuelve a ejecutarse.
  • Como 'A' no sabe que 'B' ya ha disminuido el valor, intenta usar este valor nuevamente.
  • Así que aquí está el truco, el proceso 'A' ve dos valores diferentes de la variable global ya que el valor fue cambiado por otro proceso 'B'.

Entonces, ahora sabemos por qué un kernel necesita ser reentrante. Otra pregunta que puede surgir es ¿cómo hacer un kernel reentrante?

En una nota básica, se podrían considerar los siguientes puntos para hacer un kernel reentrante:

  • Escriba funciones del kernel que modifiquen solo las variables locales (pila) y no alteren las variables globales ni las estructuras de datos. Este tipo de funciones también se conocen como funciones reentrantes.
  • Adherirse estrictamente al uso de solo funciones reentrantes en un kernel no es una solución factible. Entonces, otra técnica utilizada son los "mecanismos de bloqueo" que aseguran que solo un proceso pueda usar una función no reentrante en un momento dado.

De los puntos anteriores, queda claro que el uso de funciones reentrantes y mecanismos de bloqueo para funciones no reentrantes es el núcleo para hacer un kernel reentrante. Dado que implementar funciones reentrantes está más relacionado con una buena programación, los mecanismos de bloqueo están relacionados con el concepto de sincronización.

Sincronización y Secciones Críticas

Como se discutió anteriormente en un ejemplo, un kernel reentrante requiere acceso sincronizado a las estructuras de datos y variables globales del kernel.

El fragmento de código que opera en estas variables globales y estructuras de datos se conoce como sección crítica.

Si se suspende una ruta de control del kernel (mientras se usa un valor global o una estructura de datos) debido a un cambio de contexto, ninguna otra ruta de control debería poder acceder al mismo valor global o estructura de datos. De lo contrario, podría tener efectos desastrosos.

Si miramos hacia atrás y vemos por qué necesitamos sincronización? La respuesta es utilizar de forma segura las variables globales del kernel y las estructuras de datos. Bueno, esto también se puede lograr a través de operaciones atómicas. Una operación atómica es una operación que siempre se ejecutará sin que ningún otro proceso pueda leer o cambiar el estado que se lee o cambia durante la operación. Desafortunadamente, las operaciones atómicas no se pueden aplicar en todas partes. Por ejemplo, eliminar un elemento de una lista enlazada dentro del kernel no puede convertirse en una operación atómica.

Ahora volvamos a centrarnos en cómo sincronizar las rutas de control del kernel.

Desactivación de preferencia del kernel

La preferencia del kernel es un concepto en el que el kernel permite la suspensión/interrupción forzosa de una tarea y trae a la ejecución otra tarea de alta prioridad que ha estado esperando los recursos del kernel.

En términos más simples, se trata de un cambio de contexto de los procesos en modo kernel, donde el kernel suspende el proceso en ejecución a la fuerza y ​​el otro proceso se pone en ejecución.

Si nos atenemos a la definición, nos damos cuenta de que es esta misma capacidad del kernel (para adelantarse cuando los procesos están en modo kernel) lo que causa problemas de sincronización. Una solución al problema es deshabilitar la preferencia del kernel. Esto asegura que el cambio de contexto en el modo kernel solo ocurra cuando un proceso que se está ejecutando actualmente en el modo kernel libera voluntariamente la CPU y asegura que todas las estructuras de datos del kernel y las variables globales estén en un estado consistente.

Claramente, deshabilitar la preferencia del kernel no es una solución muy elegante y esta solución fracasa cuando usamos sistemas multiprocesador, ya que dos CPU pueden acceder simultáneamente a una misma sección crítica.

Deshabilitación de interrupciones

Otro mecanismo que se puede aplicar para lograr la sincronización dentro del núcleo es un proceso que deshabilite todas las interrupciones de hardware antes de ingresar a una región crítica y las habilite después de salir de esa región muy crítica. Nuevamente, esta solución no es una solución elegante, ya que en los casos en que la región crítica es grande, las interrupciones pueden deshabilitarse durante mucho tiempo, lo que anula su propio propósito de ser una interrupción y puede causar que las actividades del hardware se congelen.

Semáforos

Este es el método más popular para proporcionar sincronización dentro del kernel.

Es efectivo tanto en sistemas monoprocesador como multiprocesador. De acuerdo con este concepto, se puede pensar en un semáforo como un contador asociado con cada estructura de datos y verificado por todos los subprocesos del kernel cuando intentan acceder a esa estructura de datos en particular.

Un semáforo contiene información sobre el valor del contador, una lista de procesos que esperan adquirir el semáforo (para acceder a la estructura de datos) y dos métodos para aumentar o disminuir el valor del contador asociado con este semáforo.

La lógica de trabajo es la siguiente:

  • Supongamos que un proceso quiere acceder a una estructura de datos en particular, primero verificará el contador asociado con el semáforo de la estructura de datos.
  • Si el contador es positivo, el proceso adquirirá Semaphore, disminuirá el valor del contador, ejecutará la región crítica e incrementará el contador Semaphore.
  • Pero si un proceso encuentra el valor del contador como cero, entonces el proceso se agrega a la lista (asociada con el semáforo) de procesos que esperan adquirir el semáforo.
  • Ahora, cada vez que el contador se vuelve positivo, todos los procesos que esperan el semáforo intentan adquirirlo.
  • El que vuelve a adquirir disminuye el contador, ejecuta la región crítica y luego vuelve a aumentar el contador mientras los otros procesos vuelven al modo de espera.

Evitar interbloqueos

Trabajar con un esquema de sincronización como Semaphores viene con un efecto secundario de "bloqueos".

Tomemos un ejemplo:

  • Supongamos que un proceso A adquiere un semáforo para una estructura de datos particular mientras que el proceso B adquiere un semáforo para otra estructura de datos.
  • Ahora, en el siguiente paso, ambos procesos quieren adquirir Semaphore para las estructuras de datos que adquieren entre sí, es decir, el Proceso A quiere adquirir Semaphore que ya adquirió el Proceso B y viceversa.
  • Este tipo de situación en la que un proceso está esperando que otro proceso libere un recurso mientras el otro espera que el primero libere un recurso se conoce como interbloqueo.
  • Los interbloqueos pueden provocar la congelación completa de las rutas de control del kernel.

Este tipo de interbloqueos son más frecuentes en diseños donde se utilizan grandes cantidades de bloqueos de kernel. En estos diseños, resulta extremadamente difícil determinar que nunca se produciría una condición de interbloqueo. En sistemas operativos como Linux, los interbloqueos se evitan adquiriéndolos en orden.


Linux
  1. Kernels personalizados en Ubuntu/Debian:cómo, cuándo y por qué

  2. Linux:¿comprensión de los permisos y tipos de archivos de Unix?

  3. ¿Obtener tiempo de usuario y kernel de un proceso en ejecución?

  4. Ukuu Kernel Manager:instale y actualice kernels de Linux en Ubuntu

  5. pila de kernel y pila de espacio de usuario

Cómo instalar y administrar múltiples kernels en Arch Linux

¿Cuál es la diferencia entre Linux y Unix?

Kernel de Linux y sus funciones

Cómo instalar Rclone en Linux y Unix

Cómo configurar la dirección IP estática en Linux y Unix

Historia de Unix y Linux