Aprenderemos más sobre la administración de procesos en Sistemas Operativos. Hablamos de procesos, hilos, regiones críticas, mutex y semáforos.
Un proceso es la “imagen en memoria de un programa” (Wolf, 2014). Un proceso es una entidad activa que emplea un programa (lista de instrucciones) para definir cómo actuará el sistema.
Un proceso pasa por diferentes estados:
Nuevo. Cuando se crean las estructuras y recursos que utilizará un proceso.
Listo. Cuando está listo para iniciar, pero aún no tiene asignado un procesador.
En ejecución. Cuando sus instrucciones están siendo ejecutadas por un procesador.
Bloqueado. Si está esperando que algo ocurra a fin de poder continuar su ejecución.
Terminado. Cuando el proceso terminó de ejecutarse y se está a la espera de que el sistema operativo limpie las estructuras que utilizó.
Debido a la complejidad que conlleva el manejo de procesos, se diseñó una solución que involucra el uso de procesos ligeros también conocidos como hilos de ejecución.
¿Qué diferencia hay? En el caso de los procesos, el sistema operativo se encarga de ofrecerle cierta exclusividad sobre la computadora. Por otra parte, los hilos de un proceso comparten un solo espacio de direccionamiento (memoria), archivos y dispositivos.
La programación de hilos puede hacerse sin involucrar al sistema operativo, por lo que incluso un sistema operativo que no maneje multiprocesamiento, puede crear procesos que manejen multitarea interna.
Los hilos pueden trabajar siguiendo alguno de éstos patrones:
Existe un hilo jefe que recopila las tareas y se las encarga a hilos trabajadores. Un ejemplo de ésto es un servidor web, que tiene un hilo que está escuchando las peticiones, y cuando ocurre una, le asigna la tarea a otro hilo, permitiéndose como jefe permanecer atento a cualquier otra petición.
Un conjunto de hilos idénticos realizan la misma tarea sobre conjuntos diferentes de datos. Este modelo es empleado, por ejemplo, para realizar cálculos matemáticos complejos.
Una tarea larga es dividida en diferentes pasos. Cada hilo se enfoca en un solo paso y cuando termina le pasa los datos al siguiente hilo.
El manejo de hilos en multitarea requiere conocimiento de los problemas de concurrencia que pudieran surgir. Esto ocurre principalmente cuando dos procesos comparten información o dependen uno del otro.
Piense en un hermoso jardín al que muchos visitantes desean visitar. Para entrar a él, se requiere que los visitantes pasen por uno de dos torniquetes. Se desea llevar un conteo general de los visitantes que pasan por los torniquetes. Un programa que hiciese esto sería así (pseudocódigo):
int cuenta; proceso torniquete1() { int i; for(i=0;i<20;i++) { cuenta = cuenta + 1; } } proceso torniquete2() { int i; for(i=0;i<20;i++) { cuenta = cuenta + 1; } } cuenta = 0; concurrentemente { torniquete1(); torniquete2(); } /* Esperar a que ambos finalicen */ print("Cuenta: %d\n", cuenta);
Ahora bien, la instrucción
cuenta = cuenta + 1;
internamente realiza tres operaciones:
Como los procesos ocurren concurrentemente, puede darse el siguiente conjunto de operaciones:
Torniquete1: Leer
Torniquete1: Incrementar
Torniquete2: Leer
Torniquete2: Incrementar
Torniquete1: Escribir
Torniquete2: Escribir
De modo que ambos leyeron el mismo valor (por ejemplo, 1), y ambos escribieron el nuevo valor de 2. (Aunque el resultado debería ser 3, pues cada uno incrementó en uno el valor ‘anterior’).
Podemos observar que la instrucción que genera el problema es:
cuenta=cuenta+1
A esa parte del código se le conoce como Región Crítica.
¿Qué es una región crítica? Una región crítica es el área de código que debe ser protegida de accesos simultáneos, pues en ella se realizan modificaciones de datos compartidos.
int b1, b2; // Uso de banderas para decidir quién entra int turno; // Turno para desempatar /* Proceso 1: */ b1=1; quien=2; if ( b2 && (quien==2)) { esperar(); } cuenta = cuenta + 1; // Sección crítica b1=0; /* Proceso 2: */ b2=1; quien=1; if ( b1 && quien==1) { esperar(); } cuenta = cuenta + 1; // Sección crítica b1=0;
Éste algoritmo tiene el problema de la espera activa.
¿Qué es la espera activa? Ésta situación ocurre cuando un proceso está consumiendo tiempo del procesador y lo único que hace es esperar a que otro proceso cambie una bandera. En algunas ocasiones hacer algo así afecta el desempeño global.
Una alternativa para evitar la espera activa es el uso de mutex.
¿Qué es mutex? Esa palabra proviene del término en inglés Mutual Exclusión, que podría traducirse como Exclusión Mutua. Una exclusión mutua es una forma de conseguir que cierta parte del código se ejecute como si fuera atómica. (Una operación atómica ocurre cuando el sistema garantiza que ejecutará la operación como una sola unidad, o bien fallará sin resultados parciales. No es que evite retirar el flujo de ejecución en medio de la operación, sino que aún si retirara el flujo, la operación no caerá en un resultado inconsistente).
Los mutex permiten su uso a través de una variable conocida como semáforo.
¿Qué es un semáforo? Una variable de tipo entera que debe ser inicializada con un número, se puede decrementar para bloquearse en espera (wait o acquire), o se puede incrementar, despertando hilos en espera (release).
Éstos fueron algunos de los principales conceptos que se requiere conocer para entender cómo el sistema operativo realiza la administración de procesos.
Reconocimiento por uso de ilustraciones: