sistemasoperativos—administraciónde...

50
Sistemas Operativos — Administración de procesos Gunnar Wolf 27 de julio de 2013 Índice 1. Concepto y estados de un proceso 1 1.1. Estados de un proceso ........................ 2 1.2. El bloque de control de proceso (PCB) ............... 2 2. Procesos e hilos 4 2.1. Los hilos y el sistema operativo ................... 4 2.2. Patrones de trabajo con hilos .................... 5 3. Concurrencia 7 3.1. Problemas clásicos de exclusión mutua y sincronización ..... 7 3.2. Conceptos y primitivas básicas ................... 9 3.3. Soluciones a los problemas clásicos ................. 21 3.4. Otros mecanismos .......................... 29 4. Bloqueos mutuos 35 4.1. Prevención de bloqueos ....................... 37 4.2. Evasión de bloqueos ......................... 40 4.3. Detección y recuperación de bloqueos ............... 44 4.4. Algoritmo del avestruz ........................ 48 5. Otros recursos 50 1. Concepto y estados de un proceso En un sistema multiprogramado o de tiempo compartido, un proceso es la imagen en memoria de un programa, junto con la información relacionada con el estado de su ejecución. 1

Upload: others

Post on 13-Jun-2020

7 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Sistemas Operativos — Administración deprocesos

Gunnar Wolf

27 de julio de 2013

Índice1. Concepto y estados de un proceso 1

1.1. Estados de un proceso . . . . . . . . . . . . . . . . . . . . . . . . 21.2. El bloque de control de proceso (PCB) . . . . . . . . . . . . . . . 2

2. Procesos e hilos 42.1. Los hilos y el sistema operativo . . . . . . . . . . . . . . . . . . . 42.2. Patrones de trabajo con hilos . . . . . . . . . . . . . . . . . . . . 5

3. Concurrencia 73.1. Problemas clásicos de exclusión mutua y sincronización . . . . . 73.2. Conceptos y primitivas básicas . . . . . . . . . . . . . . . . . . . 93.3. Soluciones a los problemas clásicos . . . . . . . . . . . . . . . . . 213.4. Otros mecanismos . . . . . . . . . . . . . . . . . . . . . . . . . . 29

4. Bloqueos mutuos 354.1. Prevención de bloqueos . . . . . . . . . . . . . . . . . . . . . . . 374.2. Evasión de bloqueos . . . . . . . . . . . . . . . . . . . . . . . . . 404.3. Detección y recuperación de bloqueos . . . . . . . . . . . . . . . 444.4. Algoritmo del avestruz . . . . . . . . . . . . . . . . . . . . . . . . 48

5. Otros recursos 50

1. Concepto y estados de un procesoEn un sistema multiprogramado o de tiempo compartido, un proceso es la

imagen en memoria de un programa, junto con la información relacionada conel estado de su ejecución.

1

Page 2: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Un programa es una entidad pasiva, una lista de instrucciones; un procesoes una entidad activa, que –empleando al programa– define la actuación quetendrá el sistema.

En contraposición con proceso, hablaríamos de tareas en un sistema porlotes. Una tarea requiere mucha menos información, típicamente bastaría conguardar información relacionada con la contabilidad de los recursos empleados.Una tarea no es interrupmida en el transcurso de su ejecución. Ahora bien, estadistinción no es completamente objetiva — Y encontraremos muchos textos queemplean indistintamente una u otra nomenclatura.

Pero si bien el sistema nos da la ilusión de que muchos procesos se estánejecutando al mismo tiempo, la mayor parte de ellos típicamente están esperandoestar listos para su ejecución — en un momento determinado sólo puede estarseejecutando un número de procesos igual o menor al número de CPUs que tengael sistema.

En esta sección del curso nos ocuparemos de los conceptos relacionados conprocesos, hilos, concurrencia y sincronización — Abordaremos las técnicas yalgoritmos que emplea el sistema operativo para determinar cómo y en quéórden hacer los cambios de proceso que nos brindan la ilusión de simultaneidaden la sección Planificación de procesos.

1.1. Estados de un procesoUn proceso, a lo largo de su vida, alterna entre diferentes estados de ejecu-

ción. Estos son:

Nuevo Se solicitó al sistema operativo la creación de un proceso, y sus recursosy estructuras están siendo creadas

Listo Está listo para ser asignado para su ejecución en un procesador

En ejecución El proceso está siendo ejecutado en este momento

Bloqueado En espera de algún evento para poder continuar ejecutándose

Terminado El proceso terminó de ejecutarse; sus estructuras están a la esperade ser limpiadas por el sistema operativo

1.2. El bloque de control de proceso (PCB)La información que debe manipular el sistema operativo relativa a cada uno

de los procesos en ejecución (sea cual sea su estado) se compone de:

Estado del proceso El estado actual del proceso

Contador de programa Cuál es la siguiente instrucción a ser ejecutada porel proceso.

2

Page 3: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Figura 1: Diagrama de transición entre los estados de un proceso

Registros del CPU La información específica del estado del CPU mientrasel proceso está en ejecución debe ser respaldada y restaurada cuando seregistra un cambio de estado

Información de planificación (scheduling) La prioridad del proceso, la co-la en que está agendado, y demás información que puede ayudar al sistemaoperativo a agendar al proceso; profundizaremos en el tema en la unidadPlanificación de procesos.

Información de administración de memoria Las tablas de mapeo de me-moria (páginas o segmentos, dependiendo del sistema operativo), inclu-yenod la pila (stack) de llamadas. Abordaremos el tema en la unidadAdministración de memoria.

Información de contabilidad Información de la utilización de recursos queha tenido este proceso — Puede incluir el tiempo total empleado (de usua-rio, cuando el CPU va avanzando sobre las instrucciones del programapropiamente, de sistema cuando el sistema operativo está atendiendo lassolicitudes realizadas por él), uso acumulado de memoria y dispositivos,etc.

Estado de E/S Listado de dispositivos y archivos asignados que el procesotiene abiertos en un momento dado.

3

Page 4: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

2. Procesos e hilosComo vimos, la cantidad de información que el sistema operativo debe ma-

nejar acerca de cada proceso es bastante significativa. Si cada vez que el plani-ficador elige qué proceso pasar de Listo a En ejecución debe considerar buenaparte de dicha información, la simple transferencia de todo esto entre la me-moria y el CPU podría llevar a un desperdicio burocrático de recursos. Unarespuesta a esta problemática fue la de los hilos de ejecución, a veces conocidoscomo procesos ligeros (Lightweight processes, LWP).

Cuando consideramos procesos basados en un modelo de hilos, podríamosproyectar en sentido inverso que todo proceso es como un sólo hilo de ejecución.Un sistema operativo que no ofreciera soporte expreso a los hilos lo agendaríaexactamente del mismo modo.

Pero visto desde la perspectiva del proceso hay una gran diferencia: Si bienel sistema operativo se encarga de que cada proceso tenga una visión de virtualexclusividad sobre la computadora, todos los hilos de un proceso comparten unsólo espacio de direccionamiento en memoria y lista de descriptores de archivos ydispositivos abiertos. Cada uno de los hilos se ejecuta de forma (aparentemente)secuencial y maneja su propio contador de programa (y algunas estructurasadicionales, aunque mucho más ligeras que el PCB).

2.1. Los hilos y el sistema operativoFormalmente, una programación basada en hilos puede hacerse completa-

mente y de forma transparente en espacio de usuario (sin involucrar al sistemaoperativo). Estos hilos se llaman hilos de usuario (user threads), y muchos len-guajes de programación los denominan hilos verdes (green threads). Un caso deuso interesante es en sistemas operativos mínimos (p.ej. para dispositivos em-bebidos) capaces de ejecutar una máquina virtual de alguno de estos lenguajes:Si bien el sistema operativo no maneja multiprocesamiento, a través de los hilosde usuario sí podemos crear procesos con multitarea interna.

Los procesos que implementan hilos ganan un poco en el rendimiento graciasa no tener que reemplazar al PCB activo, pero además de esto, ganan muchomás por la ventaja de compartir espacio de memoria sin tener que establecerloexplícitamente a través de mecanismos de comunicación entre procesos (IPC— Inter Process Communications). Dependiendo de la plataforma, a veces loshilos de usuario inclusive utilizan multitarea cooperativa para pasar el controldentro de un mismo proceso. Cualquier llamada al sistema bloqueante (comoobtener datos de un archivo para utilizarlos inmediatamente) interrumpirá laejecución de todos los hilos, dado que el control de ejecución es entregado alsistema operativo.

El siguiente paso fue la creación de hilos informando al sistema operativo, tí-picamente denominados hilos de kernel (kernel threads). A través de bibliotecasde sistema que los implementan de forma estándar para los diferentes sistemasoperativos (p.ej. pthreads para POSIX o Win32_Thread para Windows) oarquitecturas. Estas bibliotecas aprovechan la comunicación con el sistema ope-

4

Page 5: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

rativo tanto para solicitudes de recursos (p.ej. un proceso basado en hilos puedebeneficiarse de una ejecución verdaderamente paralela en sistemas multiproce-sador) como para una gestión de recursos más comparable con una situación demultiproceso estándar.

2.2. Patrones de trabajo con hilosHay tres patrones en los que caen generalmente los modelos de hilos; podemos

emplear a más de uno de estos patrones en diferentes áreas de nuestra aplicación,e incluso podemos anidarlos (esto es, podríamos tener una línea de ensambladodentro de la cual uno de los pasos sea un equipo de trabajo):

Jefe / trabajador Un hilo tiene una tarea distinta de todos los demás: El hilojefe genera o recopila tareas que requieren ser cubiertas, las separa y selas entrega a los hilos trabajadores.Este modelo es el más común para procesos que implementan servidores(es el modelo clásico del servidor Web Apache) y para aplicaciones gráficas(GUIs), en que hay una porción del programa (el hilo jefe) esperando a queocurran eventos externos. El jefe realiza poco trabajo, se limita a invocara los trabajadores a hacer el trabajo de verdad ; como mucho, puede llevarcontabilidad de los trabajos realizados.Típicamente, los hilos realizan su operación, posiblemente notifican al jefede su trabajo, y finalizan su ejecución.

Figura 2: Patrón de hilos jefe/trabajador

Equipo de trabajo Al iniciar la porción multihilos del proceso, se crean mu-chos hilos idénticos, que realizarán las mismas tareas sobre diferentes da-tos. Este modelo es muy frecuentemente utilizado para cálculos matemá-ticos (p.ej. criptografía, render). Puede combinarse con un estilo jefe/tra-bajador para irle dando al usuario una previsualización del resultado desu cálculo, dado que éste se irá ensamblando progresivamente, pedazo porpedazo.

5

Page 6: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Sus principales diferencias con el patrón jefe/trabajador consisten en queel trabajo a realizar por cada uno de los hilos se plantea en un principio,esto es, el paso de división de trabajo no es un hilo más, sino que preparalos datos para que éstos sean lanzados en paralelo. Estos datos no sonresultado de eventos independientes (como en el caso anterior), sino quepartes de un sólo cálculo. Por consecuencia, resulta natural que en estemodelo los resultados generados por los diferentes hilos son agregados ototalizados al terminar su procesamiento. Los hilos no terminan, sino queson esperados y continúa la ejecución lineal.

Figura 3: Patrón de hilos Equipo de trabajo

Línea de ensamblado Si una tarea larga puede dividirse en pasos sobre blo-ques de la información total a procesar, cada hilo puede enfocarse a hacersólo una tarea y pasarle los datos a otro hilo conforme vaya terminando.Una de las principales ventajas de este modelo es que nos ayuda a man-tener rutinas simples de comprender, y permite que el procesamiento dedatos continúe incluso si parte del programa está bloqueado esperandoE/S.

Un punto importante a tener en cuenta en una línea de ensamblado es que,si bien los hilos trabajan de forma secuencial, pueden estar ejecutándoseparalelamente sobre bloques consecutivos de información, eventos, etc.

Este patrón es claramente distinto de los dos anteriormente presentados;si bien en los anteriores los diferentes hilos (a excepción del hilo jefe)eran casi siempre idénticos, aunque operando sobre distintos conjuntos dedatos, en este caso son completamente distintos.

6

Page 7: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Figura 4: Patrón de hilos Línea de ensamblado

3. ConcurrenciaPara el estudio de este tema, recomiendo fuertemente referirse al libro «The

little book of semaphores» de Allen Downey (2008).Pueden descargar (legalmente) el libro desde el sitio Web del curso o desde

Green Tea Press.

Formalmente y desde las ciencias de la computación, concurrencia no nece-sariamente se refiere a dos o más eventos que ocurran a la vez, sino que a dos omás eventos cuyo órden es no determinista, esto es, eventos acerca de los cua-les no podemos predecir el órden relativo en que ocurrirán. Esto puede ocurrirporque hablamos de dos hilos ejecutándose en conjunto, dos procesos indepen-dientes en el mismo equipo, o incluso procesos independientes en computadorasseparadas geográficamente; el estudio de situaciones derivadas de la concurren-cia es uno de los campos de estudio clásico (y más abstracto) de las ciencias dela computación.

Si bien una de las tareas principales de los sistemas operativos es dar a cadaproceso la ilusión de que se está ejecutando en una computadora dedicada, demodo que el programador no tenga que pensar en la competencia por recursos,a veces esta ilusión sencillamente no puede presentarse — Parte del desarrollode un programa puede depender de datos obtenidos en fuentes externas a éste,y la cooperación con hilos o procesos externos es fundamental.

Para algunos de los ejemplos a continuación, presentaremos ejemplos usandola semántica de la interacción entre hilos del mismo proceso, sincronización entreprocesos independientes, asignación de recursos por parte del núcleo a procesossimultáneos, o incluso entre usuarios de diferentes equipos de una red — Entodos estos casos, los conceptos presentados pueden generalizarse a los demás, yson situaciones en que se presenta compartición (o competencia) por estructurasentre entes independientes.

3.1. Problemas clásicos de exclusión mutua y sincroniza-ción

Enunciaremos a continuación algunos planteamientos que ilustran situacio-nes que se pueden resolver empleando semáforos. Por ahora haremos únicamenteel planteamiento, y después de presentar las estructuras de sincronización, ve-remos cómo pueden resolverse.

Conviene ir pensando en qué estrategias podrían seguir para resolver losproblemas.

7

Page 8: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Problema productor-consumidor En un entorno multihilos es común quehaya una división de tareas tipo línea de ensamblado, que se puede gene-ralizar a que un grupo de hilos van produciendo ciertas estructuras, a serconsumidas por otro.

Un ejemplo de este problema puede ser un programa orientado a eventos,en que eventos de distinta naturaleza pueden producirse, y causan quese disparen los mecanismos que los puedan atender. Los eventos puedenapilarse en un buffer que será procesado por los hilos encargados conformese vayan liberando. Esto impone ciertos requisitos, como:

Agregar o retirar un elemento del buffer tiene que ser hecho de formaatómica. Si más de un proceso intentara hacerlo al mismo tiempo,correríamos riesgo de que se corrompan los datos.

Si un consumidor está listo y el buffer está vacío, debe bloquearse (¡norealizar espera activa!) hasta que un productor genere un elemento.

Problema lectores-escritores Una estructura de datos puede ser accesadasimultáneamente por muchos procesos lectores, pero si algún proceso es-tá escribiendo, debemos evitar que cualquier otro lea (dado que podríaencontrarse con los datos en un estado inconsistente). Los requisitos desincronización son

Cualquier cantidad de lectores puede estar leyendo al mismo tiempo.

Los escritores deben tener acceso exclusivo a la sección crítica.

Como refinamiento al planteamiento: Debemos evitar que un influjoconstante de procesos lectores dejen a un escritor en situación deinanición.

La cena de los filósofos Cinco filósofos se dan cita para comer arroz en unamesa redonda. En la mesa, cada uno de ellos se sienta frente a un plato.A su derecha, tiene un palito chino, y a su izquierda tiene otro.

Los filósofos sólo saben pensar() y comer(). Cada uno de ellos va apensar() un tiempo arbitrario, hasta que le da hambre. El hambre esmala consejera, por lo que intenta comer(). Los requisitos son:

Sólo un filósofo puede sostener un palito a la vez.

Debe ser imposible que un filósofo muera de inanición estando a laespera de un palito.

Debe ser imposible que se presente un bloqueo mutuo.

Debe ser posible que más de un filósofo pueda comer al mismo tiempo.

Los fumadores compulsivos Hay tres fumadores empedernidos y un agenteque, de tiempo en tiempo, consigue ciertos insumos. Los ingredientes nece-sarios para fumar son tabaco, papel y cerillos. Cada uno de los fumadorestiene una cantidad infinita de alguno de los ingredientes, pero no les gusta

8

Page 9: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

compartir. Afortunadamente, del mismo modo que no comparten, no sonacaparadores1.

De tiempo en tiempo, el agente consigue una dosis de dos de los ingre-dientes — Por ejemplo, si deja en la mesa un papel y tabaco, el que traelos cerillos educadamente tomará los ingredientes, se hará un cigarro, y lofumará.

Suhas Patil (1971) planteó este problema buscando demostrar que haysituaciones que no se pueden resolver con el uso de semáforos. Las condi-ciones planteadas son

No podemos modificar el código del agente. Si el agente es un siste-ma operativo, ¡tiene sentido la restricción de no tenerle que notificaracerca de los flujos cada uno de los programas que corre!

El planteamiento original de Patil menciona que no debe emplearsearreglos de semáforos o usar condicionales en el flujo. Esta segundarestricción haría efectivamente irresoluble al problema, por lo quepodemos ignorarlo.

Nuevamente, recomiendo al libro «The little book of semaphores» de AllenDowney (2008) para profundizar en este tema. En este libro, además de otrosmuchos ejemplos que ilustran el uso de semáforos no sólo para resolver proble-mas de sincronización, sino como un mecanismo simple de comunicación entreprocesos, podemos encontrar una detallada explicación del por qué de diversasimplementaciones propuestas para cada uno de ellos.

3.2. Conceptos y primitivas básicasPara plantear la resolución de los problemas antes mencionados, debemos

partir del vocabulario básico de las situaciones que crean la problemática. Enesta sección presentaremos también las estructuras básicas para enfrentarlas.

3.2.1. Secciones críticas y operaciones atómicas

Varios hilos pueden avanzar en su trabajo de forma concurrente sin entorpe-cerse mutuamente siempre y cuando estén trabajando únicamente con variableslocales, esto es, valores independientes para cada uno de los hilos. Sin embargo,cuando dos hilos tienen que sincronizarse (asegurar un ordenamiento dado entreflujos independientes de ejecución), o cuando tienen que transmitirse informa-ción, el uso de variables globales y de recursos externos requiere tener en menteque el planificador puede interrumpir el flujo de un hilo en cualquier momento.Esto implica, por ejemplo, que el siguiente código en Ruby puede llevarnos adistintos resultados:

1Esto es, no buscan obtener y conservar los recursos preventivamente, sino que los tomansólo cuando satisfacen por completo sus necesidades.

9

Page 10: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

1 class EjemploHilos2 def initialize3 @x = 04 end5

6 def f17 sleep 0.18 print ’+’9 @x += 310 end11

12 def f213 sleep 0.114 print ’*’15 @x *= 216 end17 def run18 t1 = Thread.new {f1}19 t2 = Thread.new {f2}20 sleep 0.121 print ’%d ’ % @x22 end23 end

En este ejemplo, inserté un tiempo de espera largo, de una décima de segundo(sleep 0.1) para obligar al planificador a elegir a alguno de los hilos tras unperiodo de espera (en caso contraio, las funciones son tan simples que, bajo laimplementación de Ruby, se ejecutaría simplemente en forma secuencial.

La variable de instancia @x es compartida entre los dos hilos de ejecución,y en este ejemplo tenemos tres hilos compitiendo por ella. En algunas ejecucio-nes, run ejecutará primero la multiplicación, resultando en (@x * 2) + 3,en otras (@x + 3) * 2 (siendo hilos diferentes, no vale la precedencia de losoperadores). Algunas veces imprimirá el resultado antes de ambas operaciones(el @x original, en el estado de entrada de los hilos), en otros a medio camino,y en otras más después de ambas modificaciones. Es más, a veces el valor resul-tante de @x puede /aparentar que una de las operaciones no ocurrió, dado queun hilo fue interrumpido a media operación:

1 e = EjemploHilos.new;10.times{e.run}2 0 *+3 *+9 *+21 +*48 *+99 +*204 *+411 +*828 *+16593

4 e = EjemploHilos.new;10.times{e.run}5 +0 *+6 *+*18 42 +*+90 **186 +375 +**756 ++1515 *3036

Y si bien este pequeño programa fue hecho explícitamente para ilustrar esteproblema, en un programa real con hilos de ejecución complejos, el no saberdónde será interrumpido el flujo presenta un problema mayor: ¿cómo puedendos hilos manipular un recurso compartido si no hay garantía de que una ope-

10

Page 11: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

ración no será interrumpida? Y recordemos que las instrucciones que le damosal sistema no tienen por qué traducirse a una sóla instrucción ante el sistema— Una instrucción en C tan simple como x++ implica por lo menos:

Obtener la dirección en memoria de x

Traer el valor de x a un registro del procesador

Incrementar ese valor en 2

Almacenar el valor del registro en la memoria

Al haber dos accesos a memoria (¡y estamos hablando de un lenguaje demucho más bajo nivel que el del ejemplo!), el CPU puede tener que esperar aque el valor le sea transferido, y al planificador puede aprovechar para cambiarel hilo en ejecución. Claro está, con un lenguaje de tan alto nivel como Ruby, elnúmero de instrucciones resultante puede ser mucho mayor.

Operación atómica Operación que tenemos la garantía que se ejecutará o nocomo una sóla unidad de ejecución. Esto no necesariamente implica queel sistema no retirará el flujo de ejecución de su hilo, sino que el efecto deque se le retire el flujo no llevará a comportamiento inconsistente.

Condición de carrera (Race condition) Categoría de errores de programa-ción que implica a dos procesos fallando al comunicarse su estado mutuo,llevando a resultados inconsistentes. Es uno de los problemas más fre-cuentes y difíciles de depurar, y ocurre típicamente por no considerar lano atomicidad de una operación

Sección crítica El área de código que requiere ser protegida de accesos simul-táneos, donde se realiza la modificiación de datos compartidos.

Dado que el sistema no tiene forma de saber cuáles instrucciones (o áreas delcódigo) requerimos que funcionen de forma atómica, nosotros debemos indicár-selo de forma explícita, sincronizando nuestros hilos (o procesos). Es necesarioasegurarnos que la sección crítica no permitirá la entrada de dos hilos de formacasi-simultánea.

Un error muy común es utilizar mecanismos no atómicos para señalizar alrespecto. Consideremos que estamos haciendo un sistema de venta de boletosde autobús en Perl, y queremos hacer la siguiente función segura ante la concu-rrencia. El programador aquí ya hizo un primer intento:

1 my ($proximo_asiento :shared, $capacidad :shared, $bloq:shared);

2 $capacidad = 40;3

4 sub asigna_asiento {5 while ($bloq) { sleep 0.1; }6 $bloq = 1;

11

Page 12: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Figura 5: Sincronización al intentar ejecutar concurrentemente una sección crí-tica (imagen: Prof. Samuel Oporto Díaz)

7 if ($proximo_asiento < $capacidad) {8 $asignado = $proximo_asiento;9 $proximo_asiento += 1;10 print "Asiento asignado: $asignado\n";11 } else {12 print "No hay asientos disponibles\n";13 return 1;14 }15 $bloq = 0;16 return 0;17 }

El programador identificó correctamente la sección crítica como las líneascomprendidas entre la 7 y la 9 (pero, al ser parte de un bloque condicional,protegió hasta la 14). Sin embargo, tenemos aún una situación de carrera (aun-que mucho más contenida) entre la 5 y la 6: Podría un hilo entrar2 al whiley evaluar a un $bloq aún falso, y –justo antes de modificarlo– el control setransfiere a otro hilo entrando al mismo lugar, y vendiendo dos veces el mismoasiento.

Para señalizar la entrada a una sección crítica no podemos hacerlo desde elflujo susceptible a ser interrumpido, tenemos que hacerlo a través de instruccio-nes de las que el planificador pueda asegurar su atomicidad.

3.2.2. Bloqueos mutuos e inanición

Cuando nos enfrentamos a la concurrencia, además de asegurar la atomici-dad de ciertas operaciones, debemos evitar dos problemas que son consecuencianatural de la existencia de la asignación de recursos de forma exclusiva:

2Este ejemplo utiliza además el mal ejemplo de una espera activa (busy wait), requiriendodel tiempo del procesador periódicamente mientras espera a que se satisfaga una condicióndada. Veremos cómo evitar esto más adelante.

12

Page 13: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Bloqueo mutuo (o interbloqueo; en inglés, deadlock) Situación que ocurrecuando dos o más procesos poseen determinados recursos, y cada unoqueda detenido, a la espera de alguno de los que tiene el otro. El sis-tema puede seguir operando normalmente, pero ninguno de los procesosinvolucrados podrán avanzar.

Inanición (en inglés resource starvation): Situación en que un proceso no esagendado para su ejecución dado que los recursos por los cuales está es-perando son asignados a otros procesos.

El que presentemos estos conceptos aquí no significa que están exclusivamen-te relacionados con esta sección: Son conceptos con los que nos enfrentaremosuna y otra vez al hablar de asignación exclusiva a recursos — Temática recu-rrente en el campo de los sistemas operativos.

3.2.3. Mutexes

La palabra mutex nace de la frecuencia con que se habla de las regionesde exclusión mutua (en inglés, mutual exclusion). Es un mecanismo que nosasegura que cierta región del código será ejecutada como si fuera atómica.

Hay que tener en cuenta que un mutex no significa que el código no se vaa interrumpir mientras está dentro de esta región — Eso sería muy peligroso,dado que permitiría que el sistema operativo perdiera el control del planificador,volviendo para propósitos prácticos a un esquema de multitarea cooperativa. Elmutex es un mecanismo de prevención que mantiene en espera a cualquier hiloo proceso que quiera entrar a la sección crítica hasta que el proceso que la estáejecutando en un momento dado salga de ella.

Como vimos en el ejemplo anterior, para que una mutex sea efectiva tieneque ser implementada a través de una primitiva a un nivel superior, implicandoal planificador.

El código del ejemplo anterior podría reescribirse de la siguiente maneraempleando un mutex:

1 my ($proximo_asiento :shared, $capacidad :shared);2 $capacidad = 40;3

4 sub asigna_asiento {5 lock($proximo_asiento);6 if ($proximo_asiento < $capacidad) {7 $asignado = $proximo_asiento;8 $proximo_asiento += 1;9 print "Asiento asignado: $asignado\n";10 } else {11 print "No hay asientos disponibles\n";12 return 1;13 }14 return 0;15 }

13

Page 14: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Tomemos en cuenta que en este caso estamos hablando de una implemen-tación de hilos — Y como lo mencionamos previamente, esto nos hace depen-dientes del lenguaje específico de implementación. En este caso, en Perl, al serproximo_asiento una variable compartida tiene algunas propiedades adicio-nales — Como, en este caso, la de poder operar como un mutex. La implemen-tación en Perl resulta muy limpia, dado que nos evita el uso de una variablede condición explícita — Podríamos leer la línea 5 como exclusión mutua sobre$proximo_asiento.

En la implementación de hilos de Perl, la función lock() implementa unmutex delimitado por el ámbito léxico de su invocación: El área de exclusiónmutua abarca desde la línea 5 en que es invocada hasta la 15 en que termina elbloque en que se invocó.

Un área de exclusion mutua debe:

Ser mínima Debe ser tan corta como sea posible, para evitar que otros hilosqueden bloqueados fuera del área crítica. Si bien en este ejemplo es dema-siado simple, si hiciéramos cualquier llamada a otra función (o al sistema)estando dentro de un área de exclusión mutua, detendríamos la ejecuciónde todos los demás hilos por demasiado tiempo.

Ser comprehensiva Debemos analizar bien cuál es el área a proteger y noarriesgarnos a proteger de menos. En este ejemplo, podríamos haber pues-to lock($asignado) dentro del if, dado que sólo dentro de su evalua-ción positiva modificamos la variable $proximo_asiento. Sin embargo,si la ejecución de un hilo se interrumpiera entre las líneas 7 y 8, la condicióndel if se evaluaría incorrectamente.

Como comparación, una rutina equivalente en Bash (entre procesos indepen-dientes y usando los archivos /tmp/proximo_asiento y /etc/capacidad/como un mecanismo para compartir datos) sería:

1 asigna_asiento() {2 lockfile /tmp/asigna_asiento.lock3 PROX=$(cat /tmp/proximo_asiento || echo 0)4 CAP=$(cat /etc/capacidad || echo 40)5 if [ $PROX -lt $CAP ]6 then7 ASIG=$PROX8 echo $(($PROX+1)) > /tmp/proximo_asiento9 echo "Asiento asignado: $ASIG"10 else11 echo "No hay asientos disponibles"12 return 1;13 fi14 rm -f /tmp/asigna_asiento.lock15 }

Un mutex es, pues, una herramienta muy sencilla, y podría verse como la pie-za básica para la sincronización entre procesos. Lo fundamental para emplearlos

14

Page 15: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

es identificar las regiones críticas de nuestro código, y proteger el acceso con unmecanismo apto de sincronización, que garantice atomicidad.

3.2.4. Semáforos

La interfaz ofrecida por los mutexes es muy sencilla, pero no permite resol-ver algunos problemas de sincronización. Edsger Dijkstra (1968) propuso a lossemáforos.

Un semáforo es una variable de tipo entero que tiene definida la siguienteinterfaz:

Inicialización Se puede inicializar el semáforo a cualquier valor entero, perodespués de esto, su valor no puede ya ser leído.

Decrementar Cuando un hilo decrementa el semáforo, si el valor es negativo,el hilo se bloquea y no puede continuar hasta que otro hilo incrementeel semáforo. Según la implementación, esta operación puede denominarsewait, down, acquire o incluso P (por ser la inicial de proberen te ver-lagen, intentar decrementar en holandés, del planteamiento original en elartículo de Dijkstra).

Incrementar Cuando un hilo incrementa al semáforo, si hay hilos esperando,uno de ellos es despertado. Los nombres que recibe esta operación sonsignal, up, release, post o V (de verhogen, incrementar).

La interfaz de hilos POSIX (pthreads) presenta estas primitivas con lasiguiente definición:

1 int sem_init(sem_t *sem, int pshared, unsigned int value);2 int sem_post(sem_t *sem);3 int sem_wait(sem_t *sem);4 int sem_trywait(sem_t *sem);

La variable pshared indica si el semáforo puede ser compartido entre pro-cesos o únicamente entre hilos. sem_trywait extiende la intefaz sugerida porDijkstra: Verifica si el semáforo puede ser decrementado y, en caso de que no,en vez de bloquearse, indica al proceso que no puede continuar. El proceso de-be tener la lógica necesaria para no entrar en las secciones críticas (digamos,intentar otra estrategia).

sem_trywait se sale de la definición clásica de semáforo, por lo cual no loconsideraremos en esta sección.

Un semáforo permite la implementación de varios patrones, entre los cualestenemos a:

Señalizar Un hilo debe informar a otro que cierta condición está ya cumplida— Por ejemplo, un hilo prepara una conexión en red mientras que otrocalcula lo que tiene que enviar. No podemos arriesgarnos a comenzar aenviar antes de que la conexión esté lista. Inicializamos el semáforo a 0, y:

15

Page 16: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

1 # Antes de lanzar los hilos2 senal = Semaphore(0)3

4 def envia_datos():5 calcula_datos()6 senal.acquire()7 envia_por_red()8

9 def prepara_conexion():10 crea_conexion()11 senal.release()

No importa si prepara_conexion() termina primero — En el momentoen que termine, senal valdrá 1 y envia_datos() podrá proceder.

Rendezvous Así se denomina en francés (y ha sido adoptado al inglés) a que-dar en una cita. Este patrón busca que dos hilos se esperen mutuamente encierto punto para continuar en conjunto — Por ejemplo, en una aplicaciónGUI, un hilo prepara la interfaz gráfica y actualiza sus eventos mientrasotro efectúa cálculos para mostrar. Queremos mostrar al usuario la simu-lación desde el principio, así que no debe empezar a calcular antes de queel GUI esté listo, pero preparar los datos del cálculo toma tiempo, y noqueremos esperar doblemente. Para esto, implementamos dos semáforosseñalizándose mutuamente:

1 guiListo = Semaphore(0)2 calculoListo = Semaphore(0)3

4 threading.Thread(target=maneja_gui, args=[]).start()5 threading.Thread(target=maneja_calculo, args=[]).start()6

7 def maneja_gui():8 inicializa_gui()9 guiListo.release()10 calculoListo.acquire()11 recibe_eventos()12

13 def maneja_calculo():14 inicializa_datos()15 calculoListo.release()16 guiListo.acquire()17 procesa_calculo()

Mutex El uso de un semáforo inicializado a 1 puede implementar fácilmenteun mutex. En Python:

1 mutex = Semaphore(1)2 # ...Inicializamos estado y lanzamos hilos

16

Page 17: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

3 mutex.acquire()4 # Estamos en la region de exclusion mutua5 x = x + 16 mutex.release()7 # Continua la ejecucion paralela

Multiplex Permite la entrada de no más de n procesos a la región crítica. Si lovemos como una generalización de Mutex, basta con inicializar al semáforoal número máximo de procesos deseado.

Su construcción es idéntica a la de un mutex, pero es inicializado al númerode procesos que se quiere permitir que ejecuten de forma simultánea.

Torniquete Una construcción que por sí sóla no hace mucho, pero resulta útilpara patrones posteriores. Esta construcción garantiza que un grupo dehilos o procesos pasa por un punto determinado de uno en uno (incluso enun ambiente multiprocesador):

1 torniquete = Semaphore(0)2 # (...)3 if alguna_condicion():4 torniquete.release()5 # (...)6 torniquete.acquire()7 torniquete.release()

En este caso, vemos primero una señalización que hace que todos losprocesos esperen frente al torniquete hasta que alguno marque quealguna_condicion() se ha cumplido y libere el paso. Posteriormente,los procesos que esperan pasarán ordenadamente por el torniquete.

El torniquete por sí sólo no es tan útil, pero su función se hará clara acontinuación.

Apagador Cuando tenemos una situación de exclusión categórica (basada encategorías y no en procesos individuales — Varios procesos de la mismacategoría pueden entrar a la sección crítica, pero procesos de dos catego-rías distintas deben tenerlo prohibido), un apagador nos permite evitar lainanición de una de las categorías ante un flujo constante de procesos dela otra.

El apagador usa, como uno de sus componentes, a un torniquete. Paraver una implementación ejemplo de un apagador, refiéranse al ejemplo desolución presentado a continuación para el problema lectores-escritores.

Barrera Una barrera es una generalización de rendezvous que permite la sin-cronización entre varios hilos (no sólo dos), y no requiere que el rol decada uno de los hilos sea distinto.

17

Page 18: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Esta construcción busca que ninguno de los hilos continúe ejecutando hastaque todos hayan llegado a un punto dado.

Para implementar una barrera, es necesario que ésta guarde algo de in-formación adicional además del semáforo, particularmente, el número dehilos que se han lanzado (para esperarlos a todos). Esta será una variablecompartida y, por tanto, requiere de un mutex. Veamos la inicialización(que se ejecuta antes de iniciar los hilos):

1 require random2 n = random.randint(1,10) # Numero de hilos3 cuenta = 04 mutex = Semaphore(1)5 barrera = Semaphore(0)

Ahora, Supongamos que todos los hilos tienen que realizar, por separa-do, la inicialización de su estado, y ninguno de ellos debe comenzar elprocesamiento hasta que todos hayan efectuado su inicialización:

1 inicializa_estado()2

3 mutex.acquire()4 count = count + 15 mutex.release()6

7 if count == n:8 barrera.release()9

10 barrera.acquire()11 barrera.release()12

13 procesamiento()

Las barreras son una construcción suficientemente útil como para que seacomún encontrarlas “prefabricadas”. En los hilos POSIX (pthreads), porejemplo, la interfaz básica es:

1 int pthread_barrier_init(pthread_barrier_t *barrier,2 const pthread_barrierattr_t

*restrict attr,3 unsigned count);4 int pthread_barrier_wait(pthread_barrier_t *barrier);5 int pthread_barrier_destroy(pthread_barrier_t *barrier);

Cola Podemos emplear una cola cuando tenemos dos clases de hilos que debenproceder en pares. Este patrón es a veces referido como baile de salón:Para que una pareja baile, hace falta que haya un líder y un seguidor.Cuando llega una persona al salón, verifica si hay uno de la otra clase

18

Page 19: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

esperando bailar. En caso de haberlo, bailan, y en caso contrario, esperaa que llegue su contraparte.

El código para implementar esto es muy simple:

1 colaLideres = Semaphore(0)2 colaSeguidores = Semaphore(0)3 # (...)4 def lider():5 colaSeguidores.release()6 colaLideres.acquire()7 baila()8 def seguidor():9 colaLideres.release()10 colaSeguidores.acquire()11 baila()

El patrón debe resultar ya familiar: Es un rendezvous. La distinción esmeramente semántica: En el rendezvous hablábamos de dos hilos explíci-tamente, aquí hablamos de dos clases de hilos.

Sobre de este patrón base se pueden refinar muchos comportamientos. Porejemplo, asegurar que sólo una pareja esté bailando al mismo tiempo, oasegurar que los hilos en espera vayan bailando en el órden en que llegaron.

3.2.5. Variables condicionales

Las variables condicionales presentan una extensión sobre el comportamientode las mutexes, buscando darles la “inteligencia” de responder ante determinadoseventos. Una variable condicional siempre opera en conjunto con un mutex, yen algunas implementaciones es necesario indicar cuál será dicho mutex desde lamisma inicialización del objeto.3 Mantienen hasta cierto punto una semánticacomparable con la de los semáforos.

Una variable condicional presenta las siguientes operaciones:

Espera Se le indica una condición y un mutex. El mutex tiene que haber sido yaadquirido. Esta operación libera al mutex, y se bloquea hasta recibir unanotificación. Una vez que la notificación es recibida, y antes de devolverla ejecución al hilo, re-adquiere el mutex.

Espera medida Tiene una semántica igual a la de la espera, pero recibe unargumento adicional, indicando el tiempo de expiración. Si pasado el tiem-po de expiración no ha sido notificado, despierta al hilo regresándole unerror (y sin re-adquirir el mutex).

Señaliza Requiere que el mutex ya haya sido adquirido. Despierta a uno omás hilos (algunas implementaciones permiten indicar como argumento a

3Mientras que otras implementaciones permiten que se declaren por separado, pero siempreque se invoca a una variable condicional, debe indicársele qué mutex estará empleando.

19

Page 20: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

cuántos hilos). No libera al mutex — Esto significa que el flujo de ejecuciónse mantiene en el invocante, quien tiene que salir de su sección crítica(entregar el mutex) antes de que otro de los hilos continúe ejecutando.

Señaliza a todos Indica a todos los hilos que estén esperando a esta condición.

La interfaz de hilos POSIX (pthreads) presenta la siguiente definición:

1 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;2 int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t

*cond_attr);3 int pthread_cond_signal(pthread_cond_t *cond);4 int pthread_cond_broadcast(pthread_cond_t *cond);5 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t

*mutex);6 int pthread_cond_timedwait(pthread_cond_t *cond,

pthread_mutex_t *mutex, const struct timespec *abstime);7 int pthread_cond_destroy(pthread_cond_t *cond);

3.2.6. La solución de Peterson

Todas las soluciones que hemos discutido hasta el momento requieren deun sistema que ejecute a un nivel inferior al de nuestro código — Sean lasbibliotecas de sistema a través del sistema operativo, o la máquina virtual queimplemente a nuestro lenguaje, las estructuras que establecen los candados queel código de usuario emplea para la sincronización están siempre a un nivelinferior.

Ahora, ¿cómo podemos asegurar la sincronización entre dos procesos que nopueden acudir a un nivel inferior? Ejemplos de esta situación pueden incluircuando estamos implementando al sistema operativo mismo, o estamos traba-jando sobre un sistema operativo que no nos ofrece las construcciones antesdescritas.

Si podemos compartir estructuras de memoria entre los procesos implica-dos, podemos emplear la solución de Peterson. Esta implica que los procesoscooperen explícitamente, y que conozcan algunos datos que hemos mayormen-te obviado (como no sólo la cantidad de procesos que emplearán determinadasección crítica, sino una numeración que permita distinguir entre ellos). Cabemencionar, como señala Silberschatz (p. 223), que algunos detalles de imple-mentación de las computadoras actuales pueden impedir que este mecanismofuncione adecuadamente.

Peterson observa que, dados dos procesos i y j que compiten por determinadasección crítica y alternan entre su ejecución paralelizable y la ejecución de dichasección, contando con las estructuras compartidas:

1 int turno;2 boolean listo[2];

20

Page 21: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

El arreglo listo indica qué procesos están listos para entrar a la seccióncrítica, y turno indica a cuál corresponde a ejecución.

Para que el proceso i entre a su sección crítica, debe hacer lo siguiente:

1 do {2 listo[i] = TRUE;3 turno = j;4 while (listo[j] and turno == j) ;5 /* Ejecutando en secci n cr tica */6 listo[i] = FALSE;7 /* Ejecutando en secci n no cr tica */8 } while TRUE;

El código para j sería el complementario.La solución de Peterson se basa en que, si ambos procesos intentan entrar a

la sección crítica justo al mismo tiempo, ambos modificarán listo y turno,pero uno de estos cambios será sobreescrito de inmediato. Al ceder el turnoantes de entrar, en caso de que j esté listo, éste continuará ejecutando. Si j noestá listo, i podrá proceder, y al marcar listo[i] como falso libera el avancede j. Es necesario apuntar que i podría ejecutar incluso si =turno= es j, porquej se quedaría esperando cuando llegue a la entrada de la sección crítica porquelisto[i] es verdadero.

Esta solución, como ya mencionamos, no es apta para muchas condicionesactuales. En particular, no es segura en un entorno de multiprocesamiento real.Hace uso de la espera activa, pero en una situación como la descrita en que nohay paso de señales, es inevitable.

3.3. Soluciones a los problemas clásicosA continuación presentamos una forma de resolver los problemas propuestos

anteriormente. Para todos estos problemas hay más de una implementaciónválida de solución. Algunos de estos problemas fueron originalmente planteadoscomo justificación del desarrollo de las estructuras de control presentadas, y hayamplios cuerpos de bibliografía desarrollándolos, tanto como fueron planteadoscomo con diversas modificaciones a sus premisas de resolución.

Cabe mencionar que la resolución de la cena de los filósofos y los fumadorescompulsivos fue realizada de modo que sea flexible, permitiendo variar ya sea elnúmero de huéspedes a la mesa o el número de ingredientes necesario.

3.3.1. Problema productor-consumidor

Si no tuviéramos en cuenta a la sincronización, podríamos presentar códigotan simple como el siguiente:

1 import threading2 buffer = []3 threading.Thread(target=productor,args=[]).start()4 threading.Thread(target=consumidor,args=[]).start()

21

Page 22: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

5

6 def productor():7 while True:8 event = genera_evento()9 buffer.append(event)10

11 def consumidor():12 while True:13 event = buffer.pop()14 procesa(event)

Pero el acceso a buffer no está protegido para garantizar la exclusión mu-tua, y podría quedar en un estado inconsistente si append() y pop() intentanmanipular sus estructuras al mismo tiempo. Además, si bien en este ejemplo asu-mimos un sólo hilo productor y un sólo hilo consumidor, podríamos extender elprograma para que fueran varios en cualquiera de estos roles.

evento no requiere de protección, dado que es una variable local a cadahilo.

Para resolver este problema, usaremos dos semáforos: mutex, que prote-ge el acceso a nuestra sección crítica, y elementos. El valor almacenado enelementos indica, cuando es positivo, cuántos eventos tenemos pendientes porprocesar, y cuando es negativo, cuántos consumidores están listos y esperandoun evento.

Una solución a este problema puede ser:

1 import threading2 mutex = threading.Semaphore(1)3 elementos = threading.Semaphore(0)4 buffer = []5 threading.Thread(target=productor, args=[]).start()6 threading.Thread(target=consumidor, args=[]).start()7

8 def productor():9 while True:10 event = genera_evento()11 mutex.acquire()12 buffer.append(event)13 mutex.release()14 elementos.release()15

16 def consumidor():17 while True:18 elementos.acquire()19 mutex.acquire()20 event = buffer.pop()21 mutex.release()22 event.process()

22

Page 23: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Podemos ver que la misma construcción, un semáforo, es utilizada de formamuy distinta por mutex y elementos. mutex implementa una exclusión mu-tua clásica, delimitando tanto como es posible (a una sóla línea en nuestro ejem-plo) al área crítica, y siempre apareando un acquire() con un release().elementos, en cambio, es empleado como un verdadero semáforo del mun-do real: Como una estructura para la sincronización. Sólo los hilos productoresincrementan (sueltan) el semáforo, y sólo los consumidores lo decrementan (ad-quieren). Esto es, ambos comunican al planificador cuándo es posible despertara algún consumidor.

Si asumimos que genera_evento() es eficiente y no implementa algunaespera activa, esta implementación es óptima: Deja en manos del planificadortoda la espera necesaria, y no desperdicia recursos de cómputo esperando a queel siguiente elemento esté listo.

Como nota al pie, la semántica del módulo threading de Python incluye ala declaración de contexto with. Todo objeto de threading que implementeacquire() y release() puede ser envuelto en un bloque with, aumentandola legibilidad y con exactamente la misma semántica; no lo hicimos en este ejem-plo para ilustrar el uso tradicional de los semáforos para implementar regionesde exclusión mutua, sin embargo y sólo para concluir con el ejemplo, la funciónconsumidor() final podría escribirse así, y ser semánticamente idéntica:

1 def consumidor():2 while True:3 elementos.acquire()4 with mutex:5 event = buffer.pop()6 event.process()

A pesar de ser más clara, no emplearemos esta notación por dos razones.La primera es para mantener la conciencia de semántica de las operacionesacquire() y release(), y la segunda es mantener la consistencia a travésde las distintas implementaciones, ya que encontraremos varios casos en que noes el mismo hilo el que adquiere un semáforo y el que lo libera (esto es, lossemáforos no siempre son empleados como mutexes).

3.3.2. Problema lectores-escritores

Este problema puede ser generalizado como una exclusión mutua categórica:Se debe separar el uso de un recurso según la categoría del proceso. La presenciade un proceso en la sección crítica no lleva a la exclusión de otros, pero sí haycategorías de procesos que tienen distintas reglas — Para los escritores sí hacefalta una exclusión mutua completa.

Un primer acercamiento a este problema nos permite una resolución librede bloqueos mutuos, empleando sólo tres estructuras globales: Un contador queindica cuántos lectores hay en la sección crítica, un mutex protegiendo a dichocontador, y otro mutex indicando que el cuarto está vacío. Implementamos losmutexes con semáforos.

23

Page 24: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

1 import threading2 lectores = 03 mutex = threading.Semaphore(1)4 cuarto_vacio = threading.Semaphore(1)5

6 def escritor():7 cuarto_vacio.acquire()8 escribe()9 cuarto_vacio.release()10

11 def lector():12 mutex.acquire()13 lectores = lectores + 114 if lectores == 1:15 cuarto_vacio.acquire()16 mutex.release()17

18 lee()19

20 mutex.acquire()21 lectores = lectores - 122 if lectores == 0:23 cuarto_vacio.release()24 mutex.release()

El semáforo cuarto_vacio sigue un patrón llamado apagador. El escritorutiliza al apagador como a cualquier mutex: Lo utiliza para rodear a su seccióncrítica. El lector, sin embargo, lo emplea de otra manera. Lo primero que hacees verificar si la luz está prendida, esto es, si hay algún otro lector en el cuarto(si lectores es igual a 1). Si es el primer lector en entrar, prende la luzadquiriendo cuarto_vacio (lo cual evitará que un escritor entre). Cualquiercantidad de lectores puede entrar, actualizando su número. Cuando el últimosale (lectores es igual a 0), apaga la luz.

El problema con esta implementación es que un flujo constante de lecto-res puede llevar a la inanición de un escritor, que está pacientemente paradoesperando a que alguien apague la luz.

Para evitar esta condición de inanición, podemos agregar un torniquete evi-tando que lectores adicionales se cuelen antes del escritor. Reescribamos pues:

1 import threading2 lectores = 03 mutex = threading.Semaphore(1)4 cuarto_vacio = threading.Semaphore(1)5 torniquete = threading.Semaphore(1)6

7 def escritor():8 torniquete.acquire()9 cuarto_vacio.acquire()

24

Page 25: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

10 escribe()11 cuarto_vacio.release()12 torniquete.release()13

14 def lector():15 torniquete.acquire()16 torniquete.release()17

18 mutex.acquire()19 lectores = lectores + 120 if lectores == 1:21 cuarto_vacio.acquire()22 mutex.release()23

24 lee()25

26 mutex.acquire()27 lectores = lectores - 128 if lectores == 0:29 cuarto_vacio.release()30 mutex.release()

En la implementación de los escritores, esto puede parecer inútil: Únicamenteagregamos un mutex redundante alrededor de lo que ya teníamos. Sin embargo,al obligar a que el lector pase por un torniquete antes de actualizar lectores,lo cual obligaría a que se mantenga encendida la luz, obligamos a que esperea que el escritor suelte este mutex exterior. Vemos nuevamente cómo la mismaestructura es tratada de dos diferentes maneras: Para el lector es un torniquete,y para el escritor es un mutex.

3.3.3. La cena de los filósofos

En este caso, el peligro no es, como en el ejemplo anterior, que una estruc-tura de datos sea sobreescrita por ser accesada por dos hilos al mismo tiempo,sino que, en caso de presentar una resolución simplista del caso, se presentensituaciones en el curso normal de la operación que lleven a un bloqueo mutuoindefinido.

A diferencia del caso antes escrito, en este caso emplearemos a los semáforosno como una herramienta para indicar al planificador cuándo despertar a unode los hilos, sino como una herramienta de comunicación entre los propios hilos.

Podemos representar a los palillos como un arreglo de semáforos, asegurandola exclusión mutua (esto es, sólo un filósofo puede sostener un palillo al mismotiempo), pero eso no nos salva de un bloqueo mutuo. Por ejemplo, si nuestrarespuesta fuera:

1 import threading2 num = 53 palillos = [threading.Semaphore(1) for i in range(num)]

25

Page 26: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

4 filosofos = [threading.Thread(target=filosofo,args=[i]).start() for i in range(num)]

5

6 def filosofo(id):7 while True:8 piensa(id)9 levanta_palillos(id)10 come(id)11 suelta_palillos(id)12

13 def piensa(id):14 # (...)15 print "%d - Tengo hambre..." % id16

17 def levanta_palillos(id):18 palillos[(id+1) % num].acquire()19 print "%d - Tengo el palillo derecho" % id20 palillos[id].acquire()21 print "%d - Tengo ambos palillos" % id22

23 def suelta_palillos(id):24 palillos[(id+1) % num].release()25 palillos[id].release()26 print "%d - Sigamos pensando..." % id27

28 def come(id):29 print "%d - A comer!" % id30 # (...)

Eventualmente todos los filósofos van a querer comer al mismo tiempo, y elplanificador dejará suspendidos a todos con el palillo derecho en la mano.

Ahora, ¿qué pasa si hacemos que algunos filósofos sean zurdos? Esto es, quelevanten primero el palillo izquierdo y luego el derecho:

1 def levanta_palillos(id):2 if (id % 2 == 0): # Zurdo3 palillo1 = palillos[id]4 palillo2 = palillos[(id+1) % num]5 else: # Diestro6 palillo1 = paltos[(id+1) % num]7 palillo2 = palillos[id]8 palillo1.acquire()9 print "%d - Tengo el primer palillo" % id10 palillo2.acquire()11 print "%d - Tengo ambos palillos" % id

Al asegurarnos que dos filósofos contiguos no intenten levantar el mismopalillo, tenemos la certeza de que no caeremos en bloqueos mutuos. De hecho,incluso si sólo uno de los filósofos es zurdo, podemos demostrar que no habrábloqueos:

26

Page 27: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

1 def levanta_palillos(id):2 if id == 0: # Zurdo3 palillos[id].acquire()4 print "%d - Tengo el palillo izquierdo" % id5 palillos[(id+1) % num].acquire()6 else: # Diestro7 palillos[(id+1) % num].acquire()8 print "%d - Tengo el palillo derecho" % id9 palillos[id].acquire()10 print "%d - Tengo ambos palillos" % id

Cabe apuntar que ninguno de estos mecanismos asegura que en la mesa nohaya inanición, sólo que no haya bloqueos mutuos.

3.3.4. Los fumadores compulsivos

Al haber tres distintos ingredientes, tiene sentido que empleemos tres dis-tintos semáforos, para señalizar a los fumadores respecto a cada uno de losingredientes. Un primer acercamiento podría ser:

1 import random2 import threading3 ingredientes = [’tabaco’, ’papel’, ’cerillo’]4 semaforos = {}5 semaforo_agente = threading.Semaphore(1)6 for i in ingredientes:7 semaforos[i] = threading.Semaphore(0)8

9 threading.Thread(target=agente, args=[]).start()10 fumadores = [threading.Thread(target=fumador,

args=[i]).start() for i in ingredientes]11

12 def agente():13 while True:14 semaforo_agente.acquire()15 mis_ingr = ingredientes[:]16 mis_ingr.remove(random.choice(mis_ingr))17 for i in mis_ingr:18 print "Proveyendo %s" % i19 semaforos[i].release()20

21 def fumador(ingr):22 mis_semaf = []23 for i in semaforos.keys():24 if i != ingr:25 mis_semaf.append(semaforos[i])26 while True:27 for i in mis_semaf:28 i.acquire()

27

Page 28: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

29 fuma(ingr)30 semaforo_agente.release()31

32 def fuma(ingr):33 print ’Fumador con %s echando humo...’ % ingr

El problema en este caso es que, al tener que cubrir un número de ingredientesmayor a uno, utilizar sólo un semáforo ya no nos funciona: Si agente() decideproveer papel y cerillos, nada garantiza que no sea el fumador[’cerillo’]el que reciba la primer señal o que fumador[’tabaco’] reciba la segunda —Para que este programa avance hace falta, más que otra cosa, la buena suertede que las señales sean recibidas por el proceso indicado.

Una manera de evitar esta situación es la utilización de intermediarios en-cargados de notificar al hilo adecuado. Partiendo de que, respetando la primerrestricción impuesta por Patil, no podemos modificar el código del agente, imple-mentamos a los intermediarios y reimplementamos a los fumadores, y agregamosalgunas variables globales, de esta manera:

1 que_tengo = {}2 semaforos_interm = {}3 for i in ingredientes:4 que_tengo[i] = False5 semaforos_interm[i] = threading.Semaphore(0)6 interm_mutex = threading.Semaphore(1)7 intermediarios = [threading.Thread(target=intermediario,

args=[i]).start() for i in ingredientes]8

9 def fumador(ingr):10 while True:11 semaforos_interm[ingr].acquire()12 fuma(ingr)13 semaforo_agente.release()14

15 def intermediario(ingr):16 otros_ingr = ingredientes[:]17 otros_ingr.remove(ingr)18 while True:19 semaforos[ingr].acquire()20 interm_mutex.acquire()21 for i in otros_ingr:22 if que_tengo[i]:23 que_tengo[i] = False24 semaforos_interm[i].release()25 break26 que_tengo[i] = True27 interm_mutex.release()

Si bien vemos que el código de fumador() se simplifica (dado que ya notiene que efectuar ninguna verificación), intermediario() tiene mayor com-

28

Page 29: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

plejidad. El elemento clave de su lógica es que, si bien el agente() (el sistemaoperativo) seguirá enviándonos una señal por cada ingrediente disponible, lostres intermediarios se sincronizarán empleando al arreglo que_tengo (prote-gido por interm_mutex), y de este modo cada hilo (independientemente delórden en que fue invocado) señalizará a los otros intermediarios qué ingredienteshay en la mesa, y una vez que sepa a qué fumador notificar, dejará el estadolisto para recibir una nueva notificación.

3.4. Otros mecanismosMás allá de los mecanismos basados en mutexes y semáforos, existen otros

mecanismos que emplean diferentes niveles de encapsulamiento para proteger lasabstracciones. Si bien no entraremos más en detalles al respecto, los presentamosa continuación.

3.4.1. Monitores

El principal problema con los mecanismos anteriormente descritos es que nosólo hace falta encontrar un mecanismo que permita evitar el acceso simultáneoa la sección crítica sin caer en bloqueos mutuos o inanición, sino que hay que im-plementarlo correctamente, empleando una semántica que requiere de bastanteentrenamiento para entender correctamente.

Además, al hablar de procesos que compiten por recursos de una formahostil, nuestra implementación basada en semáforos puede resultar insuficiente.Ilustremos, por ejemplo, por qué en el modelo original de Djikstra (así comoen los ejemplos que presentamos anteriormente) sólo existen las operaciones deincrementar y decrementar, y no se permite verificar el estado (como lo ofrecesem_trywait() en pthreads):

1 while (sem_trywait(semaforo) != 0) {}2 seccion_critica();3 sem_post(semaforo);

El código presentado es absolutamente válido — Pero cae en una esperaactiva que desperdicia innecesariamente y constantemente tiempo de procesador(y no tiene garantía de tener más éxito que una espera pasiva, como sería el casocon un sem_wait()).

Por otro lado, algún programador puede creer que su código ejecutará sufi-cientemente rápido y con suficientemente baja frecuencia para que la probabili-dad de que usar la sección crítica le cause problemas sea muy baja. Es frecuentever ejemplos como el siguiente:

1 /* Cruzamos los dedos... A fin de cuentas, corremos con bajafrecuencia! */

2 seccion_critica();

Los perjuicios causados por este programador resultan obvios. Sin embargo,es común ver casos como este.

29

Page 30: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Los monitores son estructuras provistas por el lenguaje o entorno de desa-rrollo que encapsulan tanto a los datos como a las funciones que los puedenmanipular, e impiden el acceso directo a las funciones potencialmente peligrosas— En otras palabras, son tipos de datos abstractos (ADTs), clases de objetos,y exponen una serie de métodos públicos, además de los métodos privados queemplean internamente.

Al no presentar al usuario/programador una interfaz que puedan subvertir, elmonitor asegura que todo el código necesario para asegurar el acceso concurrentea los datos está en un sólo lugar.

Figura 6: Vista esquemática de un monitor. No es ya un conjunto de procedi-mientos aislados, sino que una abstracción que permite realizar únicamente lasoperaciones públicas sobre datos encapsulados. (Silberschatz, p.239)

Un monitor puede implementarse utilizando cualquiera de los mecanismosde sincronización que presentamos anteriormente — La diferencia radica en queesto es hecho en un sólo lugar. Los programas que quieran emplear el recursoprotegido lo hacen incluyendo a nuestro código como módulo / biblioteca, locual fomenta la reutilización de código.

Como ejemplo, el lenguaje de programación Java implementa sincronizaciónvía monitores entre hilos como una propiedad de la declaración de método, y loimplementa directamente en la JVM. Si declaramos un método de la siguientemanera (Silberschatz):

1 public class SimpleClass {2 // . . .3 public synchronized void safeMethod() {4 /* Implementation of safeMethod() */5 // . . .6 }7 }

E inicializamos a un SimpleClass sc = new SimpleClass(), cuandollamemos a sc.safeMethod(), la máquina virtual verificará si ningún otroproceso está ejecutando safeMethod(); en caso de que no sea así, le permitirá

30

Page 31: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

la ejecución obteniendo el candado, y en caso de sí haberlo, el hilo se bloquearáhasta que el candado sea liberado.

El modelo de sincronización basado en monitores no sólo presenta a la ex-clusión mutua. A través variables de condición podemos también emplear unasemántica parecida (aunque no igual) a la de los semáforos, con los métodosvar.wait() y var.signal(). En el caso de los monitores, var.wait()suspende al hilo hasta que otro hilo ejecute var.signal(); en caso de nohaber ningún proceso esperando, var.signal() no tiene ningún efecto (nocambia el estado de var, a diferencia de lo que ocurre con los semáforos)

Presento, a modo de ilustración, la resolución del problema de la cena delos filósofos en C4. Esto demuestra, además, que si bien utilizamos semánticade orientación a objetos, no sólo los lenguajes clásicamente relacionados con laprogramación orientada a objetos nos permiten emplear monitores.

1 /* Implementacion para cinco filosofos */2 pthread_cond_t CV[NTHREADS]; /* Una variable condicional

por filosofo */3 pthread_mutex_t M; /* Mutex para el monitor */4 int state[NTHREADS]; /* Estado de cada filosofo */5

6 void init () {7 int i;8 pthread_mutex_init(&M, NULL);9 for (i = 0; i < 5; i++) {10 pthread_cond_init(&CV[i], NULL);11 state[i] = PENSANDO;12 }13 }14

15 void toma_palillos (int i) {16 pthread_mutex_lock(&M)17 state[i] = HAMBRIENTO;18 actualiza(i);19 while (state[i] == HAMBRIENTO)20 pthread_cond_wait(&CV[i], &M);21 pthread_mutex_unlock(&M);22 }23

24 void suelta_palillos (int i) {25 state[i] = PENSANDO;26 actualiza((i + 4) % 5);27 actualiza((i + 1) % 5);28 pthread_mutex_unlock(&M);29 }30

31 void come(int i) {32 printf("El filosofo %d esta comiendo\n", i);

4Implementación basada en el ejemplo de Ted Baker, que implementa la solución propuestapor Tanenbaum

31

Page 32: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

33 }34

35 /* No incluimos ’actualiza’ en los encabezados, es una funcioninterna */

36 int actualiza (int i) {37 if ((state[(i + 4) % 5] != COMIENDO) &&38 (state[i] == HAMBRIENTO) &&39 (state[(i + 1) % 5] != COMIENDO)) {40 state[i] = COMIENDO;41 pthread_cond_signal(&CV[i]);42 }43 return 0;44 }

Esta implementación evita los bloqueos mutuos señalizando el estado decada uno de los filósofos en una variable de estado. Si uno de los filóso-fos no puede tomar ambos palillos cuando llama a actualiza(i) desdetoma_palillos(i), queda señalizado para que actualiza(i+1) no

3.4.2. Soluciones en hardware

Si bien hay diversos mecanismos como los que hemos visto, otra alternativapara lograr resolver los problemas de sincronización sería contar con asistenciadel hardware para solucionar esta problemática. Algunas maneras de lograrlopueden ser:

Inhabilitación de interrupciones ¿Qué pasaría si un proceso al entrar a unaregión crítica pudiera indicarle al sistema operativo que va a realizar unatarea sensible, y éste se encargara de que el procesos pudiera terminar susección crítica lo antes posible? Esto muy podría lograrse deshabilitandolas interrupciones a nivel hardware.

Esta idea fue manejada con cierta popularidad hace años, pero al día dehoy ha quedado prácticamente enterrada.

Deshabilitar las interrupciones, sin embargo, sería como matar moscas acañonazos: Por un lado, esto revertiría los beneficios de un sistema ope-rativo capaz de multiproceso preventivo. Al no haber interrupciones, elsistema operativo no recibiría ninguna notificación por parte del tempori-zador, y el proceso ejecutante podría seguir ejecutando indefinidamente.Es más, un simple ciclo que no terminara en dicho proceso podría detenerpor completo la ejecución del equipo.

Además de esto, la realidad se encargó de marcar esta solución como in-viable: En un sistema multiprocesador (como hoy en día lo son casi todaslas computadoras de propósito general), el inhabilitar las interrupcionespermitiría que otros procesos siguieran ejecutando en los otros procesado-res del sistema. La inhabilitación de interrupciones afecta a uno sólo delos procesadores — Y si bien también sería posible, detener a los demás

32

Page 33: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

procesadores representa un costo demasiado alto, mucho más que el de losalgoritmos antes descritos.

Instrucciones atómicas En el desarrollo de esta sección hemos asumido quees imposible hacer una verificación y modificar una ubicación de memo-ria de forma atómica. Sin embargo, siendo la sincronización uno de losproblemas más frecuentes de los programadores, ¿por qué sigue siendoasí?

Si asumiéramos la existencia de una instrucción a nivel CPU que verificarael contenido de una localidad en memoria que actuara como la representa-ción de nuestro mutex y, dependiendo de su estado, la modificara, nuestrosprogramas podrían ser mucho más simples. El efecto de esta instrucciónpodría ilustrarse de la siguiente manera:

1 boolean test_and_set(int i) {2 if (i == 0) {3 i = 1;4 return true;5 } else {6 return false;7 }8 }9 void free_lock(int i) {10 if (i == 1) {11 i = 0;12 }13 }

Entonces, el código para obtener un bloqueo exclusivo para realizar deter-minada tarea podría ser tan simple como:

1 enter_region: ; Etiqueta de entrada a laverificaci n

2

3 tsl reg, flag ; Test and Set Lock; ’flag’ es lavariable

4 ; compartida, es cargada al registro’reg’

5 ; y, at micamente, convertida en 1.6 cmp reg, #0 ; Era la bandera igual a 0 al

entrar?7 jnz enter_region ; En caso de que no fuera 0 al

ejecutar el tsl,8 ; vuelve a ejecutar desde enter_region9 ret ; Termina la rutina. Esto significa

que ’flag’10 ; era cero al entrar. Si llegamos

hasta aqu ,

33

Page 34: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

11 ; tsl fue exitoso y flag quedamarcado como

12 ; no-cero. Tenemos acceso exclusivoal recurso

13 ; protegido por flag.14

15 leave_region:16 move flag, #0 ; Guarda 0 en flag, liberando el

recurso17 ret ; Regresa al invocante.

Esto requeriría contar con soporte en hardware en el procesador y en elcontrolador de memoria, pero la simplicidad obtenida justificaría el costomarginal adicional en hardware.

Un problema con esta solución es que fomenta la espera activa, desperdi-ciando tiempo de procesador cada vez que un proceso está esperando alcandado. Seguiría siendo necesaria la existencia de algoritmos para evitarsituaciones de inanición. Esta instrucción existe y es empleada hoy en día,pero su uso está limitado a las estructuras que no pueden ser interrumpi-das por ninguna razón, como las rutinas gestoras de interrupciones en elnúcleo, y sólo se emplea para esperas que se sabe que serán muy cortas.

Hay arquitecturas de cómputo que implementan instrucciones como laaquí propuesta. Sin embargo, el costo asociado (no sólo por la electrónicarequerida, sino que por la demora que esta instrucción requiere del proce-sador — Un mínimo absoluto de dos accesos a memoria, lo cual de propiorompe los principios de diseño de las arquitecturas RISC, y protección adi-cional para arquitecturas multiprocesador) han evitado que se popularicemás.

Un factor adicional que dificulta la implementación a mayor escala de estainstrucción es que, siendo una instrucción en hardware con doble accesoa memoria, implica la complejidad adicional de asegurar la coherencia delcaché cada vez que es invocada.

Para mayores detalles acerca de las razones, ventajas y desventajas del usode spinlocks en sistemas operativos reales, sugiero referirse a Spin Locks& Other Forms of Mutual Exclusion (Theodore P. Baker 2010)

Memoria transaccional Un área activa de investigación hoy en día es la de lamemoria transaccional. La lógica es ofrecer primitivas que protejan a unconjunto de accesos a memoria del mismo modo que ocurre con las basesde datos, en que tras abrir una transacción, podemos realizar una grancantidad (no ilimitada) de tareas, y al terminar con la tarea, confirmarlas(commit) o rechazarlas (rollback) atómicamente — Y, claro, el sistemaoperativo indicará éxito o fracaso de forma atómica al conjunto entero.

Esto facilitaría mucho más aún la sincronización: En vez de hablar desecciones críticas, podremos reintentar la transacción y sólo preocuparnos

34

Page 35: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

de revisar si fue exitosa o no — Por ejemplo:

1 do {2 begin_transaction();3 var1 = var2 * var3;4 var3 = var2 - var1;5 var2 = var1 / var2;6 } while (! commit_transaction());

Si en el transcurso de la transacción algún otro proceso modifica alguna delas variables, la transacción se abortará, pero podemos volver a ejecutarla.Claro está, el ejemplo presentado desperdicia recursos de proceso (lleva acabo los cálculos al tiempo que va modificando las variables), por lo cualsería un mal ejemplo de sección crítica.Hay numerosas implementaciones en software de este principio (Softwa-re Transactional Memory, STM) para los principales lenguajes, aunque elplanteamiento ideal sigue apuntando a una implementación en hardware.Hay casos en que, sin embargo, esta técnica puede aún llevar a resultadosinconsistentes (particularmente si un proceso lector puede recibir los valo-res que van cambiando en el tiempo por parte de un segundo proceso), yel costo computacional de dichas operaciones es elevado, sin embargo, esuna construcción muy poderosa.

4. Bloqueos mutuosUn bloqueo mutuo puede ejemplificarse con la situación que se presenta

cuando cuatro automovilistas llegan al mismo tiempo al cruce de dos avenidasdel mismo rango en que no hay un semáforo, cada uno desde otra dirección. Losreglamentos de tránsito señalan que la precedencia la tiene el automovilista queviene más por la derecha. En este caso, cada uno de los cuatro debe ceder elpaso al que tiene a la derecha — Y ante la ausencia de un criterio humano querompa el bloqueo, deberían todos mantenerse esperando por siempre.

Un bloqueo mutuo se presenta cuando (Condiciones de Coffman) (La Red,p. 185)

1. Los procesos reclaman control exclusivo de los recursos que piden (condi-ción de exclusión mutua).

2. Los procesos mantienen los recursos que ya les han sido asignados mientrasesperan por recursos adicionales (condición de espera por).

3. Los recursos no pueden ser extraídos de los procesos que los tienen hastasu completa utilización (condición de no apropiatividad).

4. Existe una cadena circular de procesos en la que cada uno mantiene a unoo más recursos que son requeridos por el siguiente proceso de la cadena(condición de espera circular).

35

Page 36: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Las primeras tres condiciones son necesarias pero no suficientes para quese produzca un bloqueo; su presencia puede llamar nuestra atención hacia unasituación de riesgo. Sólo cuando se presentan las cuatro podemos hablar de unbloqueo mutuo efectivo.

Otro ejemplo clásico es un sistema con dos unidades de cinta (dispositivosde acceso secuencial y no compartible), en que los procesos A y B requieren deambas unidades. Supongamos siguiente secuencia:

1. A solicita una unidad de cinta y se bloquea

2. B solicita una unidad de cinta y se bloquea

3. El sistema operativo otorga la unidad 1 a A y lo vuelve a poner en ejecu-ción

4. A continúa procesando; termina su periodo de ejecución

5. El sistema operativo otorga la unidad 2 a B y lo vuelve a poner en ejecu-ción

6. B solicita otra unidad de cinta y se bloquea

7. El sistema operativo no tiene otra unidad de cinta por asignar. Mantienea B bloqueado; otorga el control de vuelta a A

8. A solicita otra unidad de cinta y se bloquea

9. El sistema operativo no tiene otra unidad de cinta por asignar. Mantienea B bloqueado; otorga el control de vuelta a otro proceso (o queda enespera)

Sin una política de prevención o resolución de bloqueos mutuos, no hay modode que A o B continúen su ejecución. Veremos, pues, algunas estrategias paraenfrentar a los bloqueos mutuos.

En el apartado de Exclusión mutua, los hilos presentados estaban diseñadospara cooperar explícitamente. El rol del sistema operativo va más allá, tiene queimplementar políticas que eviten, en la medida de lo posible, dichos bloqueos.

Las políticas tendientes a otorgar los recursos lo antes posible cuando sonsolicitadas pueden ser vistas como liberales, en tanto que las que controlan másla asignación de recursos, conservadoras.

Las líneas principales que describen a las estrategias para enfrentar situacio-nes de bloqueo (La Red, p. 188):

Prevención Se centra en modelar el comportamiento del sistema para queelimine toda posibilidad de que se produzca un bloqueo. Resulta en unautilización subóptima de recursos.

Evasión Busca imponer condiciones menos estrictas que en la prevención, paraintentar lograr una mejor utilización de los recursos. Si bien no puedeevitar todas las posibilidades de un bloqueo, cuando éste se produce buscaevitar sus consecuencias.

36

Page 37: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Figura 7: Esquema clásico de un bloqueo mutuo simple: Los procesos A y Besperan mutuamente para el acceso a las unidades de cinta 1 y 2.

Figura 8: Espectro liberal—conservador de esquemas para evitar bloqueos

Detección y recuperación El sistema permite que ocurran los bloqueos, perobusca determinar si ha ocurrido y tomar medidas para eliminarlo.

Busca despejar los bloqueos presentados para que el sistema continúe ope-rando sin ellos.

4.1. Prevención de bloqueosPresentaremos a continuación algunos algoritmos que implementan la pre-

vención de bloqueos.

4.1.1. Serialización

Una manera de evitar bloqueos por completo sería el que un sistema operativojamás asignara recursos a más de un proceso a la vez — Los procesos podríanseguir efectuando cálculos o empleando recursos no rivales (que no requieranacceso exclusivo — Por ejemplo, empleo de archivos en el disco, sin que existaun acceso directo del proceso al disco), pero sólo uno podría obtener recursosde forma exclusiva al mismo tiempo. Este mecanismo sería la serialización, y lasituación antes descrita se resolvería de la siguiente manera:

37

Page 38: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

1. A solicita una unidad de cinta y se bloquea

2. B solicita una unidad de cinta y se bloquea

3. El sistema operativo otorga la unidad 1 a A y lo vuelve a poner en ejecu-ción

4. A continúa procesando; termina su periodo de ejecución

5. El sistema operativo mantiene bloqueado a B, dado que A tiene un recurso

6. A solicita otra unidad de cinta y se bloquea

7. El sistema operativo otorga la unidad 2 a A y lo vuelve a poner en ejecu-ción

8. A libera la unidad de cinta 1

9. A libera la unidad de cinta 2 (y con ello, el bloqueo de uso de recursos)

10. El sistema operativo otorga la unidad 1 a B y lo vuelve a poner en ejecu-ción

11. B solicita otra unidad de cinta y se bloquea

12. El sistema operativo otorga la unidad 2 a B y lo vuelve a poner en ejecu-ción

13. B libera la unidad de cinta 1

14. B libera la unidad de cinta 2

Si bien la serialización resuelve la situación aquí mencionada, el mecanismoempleado es subóptimo dado que puede haber hasta n-1 procesos esperando aque uno libere los recursos.

Un sistema que implementa una política de asignación de recursos basada enla serialización, si bien no caerá en bloqueos mutuos, sí tiene un peligro fuertede caer en inanición.

4.1.2. Retención y espera (advance claim)

Otro ejemplo de política preventiva menos conservadora sería la retención yespera o reserva (advance claim): Que todos los programas declaren al iniciarsu ejecución qué recursos van a requerir. Los recursos son apartados para su usoexclusivo hasta que el proceso termina, pero el sistema operativo puede seguiratendiendo solicitudes que no rivalicen: Si a los procesos A y B anteriores sesuman procesos C y D, pero requieren otro tipo de recursos, podrían ejecutarseen paralelo A, C y D, y una vez que A termine, podrían continuar ejecutandoB, C y D.

El bloqueo resulta ahora imposible por diseño, pero el usuario que inició Btiene una percepción de injusticia dado el tiempo que tuvo que esperar para que

38

Page 39: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

su solicitud fuera atendida — De hecho, si A es un proceso de larga duración(incluso si requiere la unidad de cinta sólo por un breve periodo), esto lleva aque B sufra una inanición innecesariamente prolongada.

Además, la implementación de este mecanismo preventivo requiere que elprogramador sepa por anticipado qué recursos requerirá — Y esto en la reali-dad muchas veces es imposible. Si bien podría diseñarse una estrategia de lanzarprocesos representantes (o proxy) solicitando recursos específicos cuando éstoshicieran falta, esto sólo transferiría la situación de bloqueo por recursos a blo-queo por procesos — y un programador poco cuidadoso podría de todos modosdesencadenar la misma situación.

4.1.3. Solicitud de una vez (one-shot)

Otro mecanismo de prevención de bloqueos sería que los recursos se otorguenexclusivamente a aquellos procesos que no poseen ningún recurso. Esta estrategiarompería la condición de Coffman espera por, haciendo imposible que se presenteun bloqueo.

En su planteamiento inicial, este mecanismo requería que un proceso decla-rara una sóla vez qué recursos requeriría, pero posteriormente la estrategia semodificó, permitiendo que un proceso solicite recursos nuevamente, pero única-mente a condición de que previo a hacerlo renuncien a los recursos que retenganen ese momento — Claro, pueden volver a incluirlos en la operación de solicitud.

Al haber una renuncia explícita, se imposibilita de forma tajante que unconjunto de procesos entre en condición de bloqueo mutuo.

Las principales desventajas de este mecanismo son:

Requiere cambiar la lógica de programación para tener puntos más defi-nidos de adquisición y liberación de recursos.

Muchas veces no basta con la readquisición de un recurso, sino que es nece-sario mantenerlo bloqueado. Volviendo al ejemplo de las unidades de cinta,un proceso que requiera ir generando un archivo largo no puede arriesgar-se a soltarla, pues podría ser entregada a otro proceso y corromperse elresultado.

4.1.4. Asignación jerárquica

Otro mecanismo de evasión es la asignación jerárquica de recursos. Bajoeste mecanismo, se asigna una prioridad o nivel jerárquico a cada recurso oclase de recursos.5 La condición básica es que, una vez que un proceso obtieneun recurso de determinado nivel, sólo puede solicitar recursos adicionales deniveles superiores. En caso de requerir dos dispositivos ubicados al mismo nivel,tiene que hacerse de forma atómica.

5Incluso varios recursos distintos, o varias clases, pueden compartir prioridad, aunque estodificultaría la programación. Podría verse a la solicitud de una vez como un caso extremo deasignación jerárquica con jerarquía plana

39

Page 40: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

De este modo, si las unidades de cinta tienen asignada la prioridad x, P1 sólopuede solicitar dos unidades de cinta por medio de una sóla operación. En casode también requerir dos unidades de cinta el proceso P2 al mismo tiempo, al seratómicas las solicitudes, éstas le serán otorgadas a sólo un de los dos procesos,por lo cual no se presentará bloqueo.

Además, el crear una jerarquía de recursos nos permitiría ubicar los recursosmás escasos o peleados a la cima de la jerarquía, reduciendo las situaciones decontención en que varios procesos compiten por dichos recursos — Sólo llegaríana solicitarlos aquellos procesos que ya tienen asegurado el acceso a los demásrecursos que vayan a emplear.

Sin embargo, este ordenamiento es demasiado estricto para muchas situacio-nes del mundo real. El tener que renunciar a ciertos recursos para adquirir unode menor prioridad y volver a competir por ellos, además de resultar contra-intuitivo para un programador, resulta en esperas frustrantes. Este mecanismollevaría a los procesos a acaparar recursos de baja prioridad, para evitar te-ner que ceder y re-adquirir recursos más altos, por lo que conduce a una altainanición.

4.2. Evasión de bloqueosPara la evasión de bloqueos, el sistema partiría de poseer, además de la

información descrita en el caso anterior, información acerca de cuándo requiereun proceso utilizar cada recurso. De este modo, el planificador puede marcarqué flujos entre dos (o más) procesos son seguros y cuáles son inseguros

Figura 9: Evasión de bloqueos: Los procesos A (horizontal) y B (vertical) re-quieren del acceso exclusivo a un scanner y una impresora. (La Red, p. 200)

El análisis de la interacción entre dos procesos se representa como en la figuraanterior; el avance en cada proceso es marcado con una flecha horizontal (A) overtical (B); en un sistema multiprocesador, podría haber avance mutuo, y loindicaríamos con una flecha diagonal.

Al saber cuándo reclama y libera un recurso cada proceso, podemos marcarcuál es el área segura para la ejecución y cuándo estamos aproximándonos a un

40

Page 41: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

área de riesgo. En el caso mostrado, el bloqueo mutuo se produciría si entráramosa I2—I3 e I6—I7, por lo que –en la situación descrita en esta gráfica– el sistemadebe mantener a B congelado por lo menos hasta que A llegue a I3.

Este mecanismo proveería una mejor respuesta que los vistos en el apartadode prevención de bloqueos, pero es todavía más dificil de aplicar en situacionesreales. Para que pudiéramos implementar un sistema con evasión de bloqueos,tendría que ser posible hacer un análisis estático previo del código a ejecu-tar, y tener un listado total de recursos estático. Estos mecanismos pueden serefectivos en sistemas de uso especializado, pero no en sistemas operativos (oplanificadores) genéricos.

4.2.1. Algoritmo del banquero

Edsger Djikstra propuso un algoritmo de asignación de recursos orientado ala evasión de bloqueos a ser empleado para el sistema operativo THE (desarro-llado entre 1965 y 1968 en la Escuela Superior Técnica de Eindhoven, TechnischeHogeschool Eindhoven), un sistema multiprogramado organizado en anillos deprivilegios. El nombre de este algoritmo proviene de que busca que el sistemaopere cuidando de tener siempre la liquidez (nunca entrar a estados inseguros)para satisfacer los préstamos (recursos) solicitados por sus clientes (quienes asu vez tienen una línea de crédito pre-autorizada por el banco). Este algoritmopermite que el conjunto de recursos solicitado por los procesos en ejecución enel sistema sea mayor a los recursos físicamente disponibles sin poner en riesgola operación correcta del sistema.

Este algoritmo debe ejecutarse cada vez que un proceso solicita recursos; elsistema evita caer en situaciones conducentes a un bloqueo mutuo ya sea de-negando o posponiendo la solicitud. El requisito particular es que, al iniciar,cada proceso debe anunciar su reclamo máximo (llamémosle claim()) al sis-tema el número máximo de recursos de cada tipo que va a emplear a lo largode su ejecución — Esto sería implementado como una llamada al sistema. Unavez que un proceso presentó su reclamo máximo de recursos, cualquier llamadasubsecuente a claim() falla. Claro está, si el proceso anuncia una necesidadmayor al número existente de recursos de algún tipo, también falla dado que elsistema no será capaz de cumplirlo.

Para el algoritmo del banquero:

Estado Matrices de recursos disponibles, reclamos máximos y asignación derecursos a los procesos en un momento dado

Estado seguro Un estado en el cual todos los procesos pueden ejecutar hastael final sin encontrar un bloqueo mutuo.

Estado inseguro Todo estado que no garantice que todos los procesos puedanejecutar hasta el final sin encontrar un bloqueo mutuo.

Este algoritmo típicamente trabaja basado en difrentes categorías de recur-sos, y los reclamos máximos anunciados por los procesos son por cada una delas categorías.

41

Page 42: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

El estado está compuesto, por clase de recursos y por proceso, por:

Reclamado Número de instancias de este recurso que han sido reclamadas

Asignado Número de instancias de este recurso actualmente asignadas a pro-cesos en ejecución

Solicitado Número de instancias de este recurso actualmente pendientes deasignar (solicitudes hechas y no cumplidas)

Además de esto, el sistema mantiene globalmente, por clase de recursos:

Disponibles Número total de instancias de este recurso disponibles al sistema

Libres Número de instancias de este recurso que no están actualmente asigna-das a ningún proceso

Cada vez que un proceso solicita recursos, se calcula cuál sería el estadoresultante de otorgar dicha solicitud, y se otorga siempre que:

No haya reclamo por más discursos que los disponibles

Ningún proceso solicite (o tenga asignados) recursos por encima de sureclamo

La suma de los recursos asignados por cada categoría no sea mayor a lacantidad de recursos disponibles en el sistema para dicha categoría

Formalmente, y volviendo a la definición de un estado seguro: Un estado esseguro cuando hay una secuencia de procesos (denominada secuencia segura)tal que:

1. Un proceso j puede necesariamente terminar su ejecución, incluso si soli-citara todos los recursos que permite su reclamo, dado que hay suficientesrecursos libres para satisfacerlo.

2. Un segundo proceso k de la secuencia puede terminar si j termina y liberatodos los recursos que tiene, porque sumado a los recursos disponiblesahora, con aquellos que liberaría j, hay suficientes recursos libres parasatisfacerlo.

3. El i -ésimo proceso puede terminar si todos los procesos anteriores termi-nan y liberan sus recursos.

En el peor de los casos, esta secuencia segura nos llevaría a bloquear todaslas solicitudes excepto las del proceso único en el órden presentado.

Simplificando para ejemplificar, asumiendo sólo una clase de procesos, e ini-ciando con 2 instancias libres:

42

Page 43: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Proceso Asignado ReclamandoA 4 6B 4 11C 2 7

A puede terminar porque sólo requiere de 2 instancias adicionales para llegara las 6 que indica en su reclamo. Una vez que termine, liberará sus 6 instancias.Se le asignan entonces las 5 que solicita a C, para llegar a 7. Al terminar éste,tendremos 8 disponibles, y asignándole 7 a B garantizamos poder terminar. Lasecuencia (A, C, B) es una secuencia segura.

Sin embargo, el siguiente estado es inseguro (asumiendo también dos instan-cias libres):

Proceso Asignado ReclamadoA 4 6B 4 11C 2 9

A puede terminar, pero no podemos asegurar que B o C puedan hacerlo,porque incluso una vez terminando A, tendríamos sólo 6 instancias no asignadas.

Es necesario apuntar que no hay garantía de que continuar a partir de esteestado lleve a un bloqueo mutuo, dado que B o C pueden no incrementar ya suutilización hasta cubrir su reclamo.

El algoritmo del banquero, en el peor caso, puede tomar O(n!), aunquetípicamente ejecuta en O(n2). Una implementación de este algoritmo podríaser:

1 l = [1, 2, 3, 4, 5]; # Todos los procesos del sistema2 s = []; # Secuencia segura3 while ! l.empty? do4 p = l.select {|id| asignado[id] - reclamado[id] >

libres}.first5 raise Exception, ’Estado inseguro’ if p.nil?6 libres += asignado[p]7 l.delete(p)8 s.push(p)9 end10 puts "La secuencia segura encontrada es: %s" % s

Hay refinamientos sobre este algoritmo que logran resultados similares, re-duciendo su costo de ejecución (recordemos que es un procedimiento que puedeser llamado con muy alta frecuencia), como el desarrollado por Habermann (ref:Finkel, p.136).

El algoritmo del banquero es un algoritmo conservador, dado que evita entraren un estado inseguro a pesar de que dicho estado no lleve con certeza a unbloqueo mutuo. Sin embargo, su política es la más liberal que nos permite estarseguros de que no caeremos en bloqueos mutuos sin conocer el órden y tiempoen que cada uno de los procesos requeriría los recursos que solicita.

43

Page 44: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Una desventaja fuerte de todos los mecanismos de evasión de bloqueos esque requieren saber por anticipado los reclamos máximos de cada proceso, locual puede no ser sabido en el momento de su ejecución.

4.3. Detección y recuperación de bloqueosLa detección de bloqueos es una forma de reaccionar ante una situación de

bloqueo que ya se presentó y de buscar la mejor manera de salir de ella. Ladetección de bloqueos se ejecuta como una tarea periódica, y si bien no puedeprevenir situaciones de bloqueo, puede detectarlas una vez que ya ocurrieron ylimitar su impacto.

Manteniendo una lista de recursos asignados y solicitados, el sistema operati-vo puede saber cuando un conjunto de procesos están esperándose mutuamenteen una solicitud por recursos — Al analizar estas tablas como grafos dirigidos,representamos:

Los procesos, con cuadrados

Los recursos, con círculos

• Puede representarse como un círculo grande a una clase o categoríade recursos, y como círculos pequeños dentro de éste a una serie derecursos idénticos (p.ej. las diversas unidades de cinta)

Las flechas que van de un recurso a un proceso indican que el recurso estáasignado al proceso

Las flechas que van de un proceso a un recurso indican que el procesosolicita al recurso

Cabe mencionar en este momento que, cuando consideramos categorías derecursos, el tener la representación visual de un ciclo no implica que haya ocurri-do un bloqueo — Este sólo se presentaría cuando todos los procesos involucradosestuvieran en espera mutua.

En la figura presentada, si bien P1 y P2 están esperando que se liberenrecursos de tipo R1 y R2, P3 y P4 siguen operando normalmente, y es esperableque lleguen a liberar el recurso por el cual están esperando. En el caso ilustrado,dado que el bloqueo se presenta únicamente al ser imposible que un proceso libererecursos que ya le fueron asignados, tendría que presentarse un caso donde todoslos recursos de una misma categoría estuvieran involucrados en una situaciónde espera circular, como la ilustrada a continuación.

Si tenemos una representación completa de los procesos y recursos en elsistema, la estrategia es reducir la gráfica retirando los elementos que no brin-den información imprescindible, siguiendo la siguiente lógica (recordemos querepresentan una fotografía del sistema en un momento dado):

Retiramos los procesos que no están solicitando ni tienen asignado ningúnrecurso.

44

Page 45: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Figura 10: Al emplear categorías de recursos, un ciclo no necesariamente indicaun bloqueo

Figura 11: Situación en que se presenta espera circular, incluso empleando ca-tegorías de recursos

Para todos los procesos restantes: Si todos los recursos que están soli-citando pueden ser concedidos (esto es, no están actualmente asignadosa otro), reducimos eliminando del grafo al proceso y a todas las flechasrelacionadas con éste.

Si después de esta reducción eliminamos a todos los procesos del grafo,entonces no hay interbloqueos y podemos continuar. En caso de permane-cer procesos en el grafo, los procesos “irreducibles” constituyen la serie deprocesos interbloqueados de la gráfica.

De la gráfica Detección de ciclos denotando bloqueos, podríamos proceder:

Reducimos por B, dado que actualmente no está esperando a ningún re-curso

Reducimos por A y F, dado que los recursos por los cuales están esperandoquedarían libres en ausencia de B

45

Page 46: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Figura 12: Detección de ciclos denotando bloqueos: Grafo de procesos y recursosen un momento dado

Y quedamos con un interbloqueo entre C, D y E, en torno a los recursos 4,5 y 7.

Figura 13: Detección de bloqueos: Proceso de reducción en un grafo de procesosy recursos, manejando clases de recursos

Nótese que reducir un proceso del grafo no implica que éste haya entregadosus recursos, sino que únicamente que, hasta donde tenemos conocimiento, tieneposibilidad de hacerlo. Los procesos que estan esperando por recursos retenidospor un proceso pueden sufrir inanición aún por un tiempo indeterminado.

Una vez que un bloqueo es diagnosticado, dado que los procesos no podránterminar por sí mismos (dado que están precisamente bloqueados, su ejecuciónno avanzará más), hay varias estrategias para la recuperación:

46

Page 47: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Terminar a todos los procesos bloqueados. Esta es la técnica más sencilla y,de cierto modo, más justa — Todos los procesos implicados en el bloqueopueden ser relanzados, pero todo el estado del cómputo que han realizadohasta este momento se perderá.

Retroceder a los procesos implicados hasta el último punto de control(checkpoint) conocido. Esto es posible únicamente cuando el sistema im-plementa esta funcionalidad, que tiene un elevado costo adicional. Cuandoel estado de uno de los procesos depende de factores externos a éste, esimposible implementar fielmente los puntos de control.

Podría parecer que retroceder a un punto previo llevaría indefectiblementea que se repita la situación — Pero los bloqueos mutuos requieren deun órden de ejecución específico para aparecer. Muy probablemente, dosejecuciones posteriores lograrían salvar el bloqueo — y en caso contrario,puede repetirse este paso.

Terminar, uno por uno y no en bloque, a cada uno de los procesos blo-queados. Una vez que se termina uno, se evalúa la situación para verificarsi logró romperse la situación de bloqueo, en cuyo caso la ejecución de losrestantes continúa sin interrupción.

Para esto, si bien podría elegirse un proceso al azar de entre los bloqueados,típicamente se consideran elementos adicionales como:

• Los procesos que demandan garantías de tiempo real son los mássensibles para detener y relanzar

• La menor cantidad de tiempo de procesador consumido hasta el mo-mento. Dado que el proceso probablemente tenga que ser re-lanzado(re-ejecutado), puede ser conveniente apostarle a un proceso que hayahecho poco cálculo (para que el tiempo que tenga que invertir paravolver al punto actual sea el mínimo posible).

• Mayor tiempo restante estimado. Si se puede estimar cuánto tiempode procesamiento queda pendiente, conviene terminar al proceso quemás le falte por hacer.

• Menor número de recursos asignados hasta el momento. Un pococomo criterio de justicia, y un poco partiendo de que es un procesoque está haciendo menor uso del sistema.

• Prioridad más baja. Cuando hay un ordenamiento de procesos o usua-rios por prioridades, siempre es preferible terminar un proceso de me-nor prioridad o perteneciente a un usuario poco importante que unode mayor prioridad.

• En caso de contar con la información necesaria, es siempre mejorinterrumpir un proceso que pueda ser repetido sin pérdida de infor-mación que uno que la cause. Por ejemplo, es preferible interrumpiruna compilación que la actualización de una base de datos.

47

Page 48: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

Un punto importante a considerar es cada cuánto debe realizarse la verifica-ción de bloqueos. Podría hacerse:

Cada vez que un proceso solicite un recurso. pero esto llevaría a un gastode tiempo en este análisis demasiado frecuente.

Con una periodicidad fija, pero esto arriesga a que los procesos pasen mástiempo bloqueados.

Cuando el nivel del uso del CPU baje de cierto porcentaje. Esto indicaríaque hay un nivel elevado de procesos en espera.

Una estrategia combinada.

Por último, si bien los dispositivos aquí mencionados requieren bloqueo ex-clusivo, otra estragegia es la apropiación temporal : Tomar un recurso asignadoa determinado proceso para otorgárselo temporalmente a otro. Esto no siemprees posible, claro, y depende fuertemente de la naturaleza del mismo — pero po-dría, por ejemplo, interrumpirse un proceso que tiene asignada (pero inactiva) auna impresora para otorgársela temporalmente a otro que tiene un trabajo cor-to pendiente. Esto último, sin embargo, es tan sensible a detalles de cada clasede recursos que rara vez puede hacerlo el sistema operativo — es normalmentehecho de acuerdo entre los procesos competidores, por medio de algún protocolopre-establecido.

4.4. Algoritmo del avestruzUna cuarta línea (que, por increíble que parezca, es la más común, empleada

en todos los sistemas operativos de propósito general con amplia utilización)es el llamado algoritmo del avestruz : Ignorar las situaciones de bloqueo (escon-diéndose de ellas como avestruz que esconde la cabeza bajo la tierra), esperandoque su ocurrencia sea suficientemente poco frecuente.

4.4.1. Justificando a los avestruces

Hay que comprender que esto ocurre porque las condiciones impuestas porlas demás estrategias resultan demasiado onerosas, el conocimiento previo re-sulta insuficiente, o los bloqueos simplemente pueden presentarse ante recursosexternos y no controlados (o conocidos siquiera) por el sistema operativo.

Ignorar la posibilidad de un bloqueo cuando su probabilidad es suficientemen-te baja será preferible para los usuarios (y programadores) ante la disyuntiva deafrontar restricciones para la forma y conveniencia de solicitar recursos.

En este caso, se toma una decisión entre lo correcto y lo conveniente — Unsistema operativo formalmente no debería permitir la posibilidad de que hubierabloqueos, pero la inconveniencia presentada al usuario sería inaceptable.

Por último, cabe mencionar algo que a lo largo de todo este apartado men-cionamos únicamente de forma casual, evadiendo definiciones claras: ¿Qué es un

48

Page 49: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

recurso? La realidad es que no está muy bien definido. Podríamos, como mencio-nan nuestros ejemplos, hablar de los clásicos recursos de acceso rival y secuencial:Impresoras, cintas, terminales seriales, etc. Sin embargo, también podemos vercomo recursos a otras entidades administradas por el sistema operativo — El es-pacio disponible de memoria, el tiempo de procesamiento, o incluso estructuraslógicas creadas y gestionadas por el sistema operativo, como archivos, semáforoso monitores. Y para esos casos, prácticamente ninguno de los mecanismos aquíanalizados resolvería las características de acceso y bloqueo necesarias.

4.4.2. Enfrentando a los avestruces

La realidad del cómputo marca que es el programador de aplicaciones quiendebe prever las situaciones de carrera, bloqueo e inanición en su código — Elsistema operativo empleará ciertos mecanismos para asegurar la seguridad engeneral entre los componentes del sistema, pero para muchas tareas, esto recaeen las manos del programador.

Una posible salida ante la presencia del algoritmo del avestruz es adoptarun método defensivo de programar. Un ejemplo de esto sería que los programassoliciten un recurso pero, en vez de solicitarlo por medio de una llamada blo-queante, hacerlo por medio de una llamada no bloqueante y, en caso de fallarésta, esperar un tiempo aleatorio e intentarlo e intentar nuevamente acceder alrecurso un número dado de veces, y, tras n intentos, abortar limpiamente elproceso y notificar al usuario (evitando un bloqueo mutuo circular indefinido).

Por otro lado, hay una gran cantidad de aplicaciones de monitoreo en espaciode usuario. Conociendo el funcionamiento esperable específico de determinadosprogramas es posible construir aplicaciones que los monitoreen de una formainteligente y tomen acciones (ya sea alertar a los administradores o, como lorevisamos en la sección de detección y recuperación de bloqueos, aborten (yposiblemente reinicien) la ejecución de aquellos procesos que no puedan recupe-rarse.

4.4.3. De avestruces, ingenieros y matemáticos

Esta misma contraposición puede leerse, hasta cierto punto en tono de bro-ma, como un síntoma de la tensión que caracteriza a nuestra profesión: Lacomputación nación como ciencia dentro de los departamentos de matemáticasen diversas facultades, sin embargo, al pasar de los años ha ido integrando cadavez más a la ingeniería. Y el campo, una de las áreas más jóvenes pero al mismotiempo más prolíficas del conocimiento humano, está en una constante discusióny definición: ¿Qué somos? ¿Matemáticos, ingenieros, o. . . alguna otra cosa?

La asignación de recursos, pues, puede verse desde el punto de vista matemá-tico: Es un problema con un planteamiento de origen, y hay varias estrategiasdistintas (los mecanismos y algoritmos descritos en esta sección). Pueden noser perfectos, pero el problema no ha demostrado ser intratable. Y un bloqueoes claramente un error — Una situación de excepción, inaceptable. Los mate-máticos en nuestro árbol genialógico académico nos llaman a no ignorar este

49

Page 50: SistemasOperativos—Administraciónde procesossistop.gwolf.org/pdf/02_administracion_de_procesos.pdfBloqueomutuo (o interbloqueo; en inglés, deadlock) Situación que ocurre cuando

problema, a resolverlo sin importar la complejidad computacional.Los ingenieros, más aterrizados en el mundo real, tienen como parte básica

de su formación, sí, el evitar defectos nocivos — Pero también contemplan elcálculo de costos, la probabilidad de impacto, los umbrales de tolerancia. . . Paraun ingeniero, si un sistema típico corre riesgo de caer en un bloqueo mutuocon una probabilidad p > 0, dejando inservibles a dos procesos en un sistema,pero debe también considerar no sólo las fallas en hardware y en los diferentescomponentes del sistema operativo, sino que en todos los demás programas quecorren en espacio de usuario, y considerando que prevenir el bloqueo conlleva uncosto adicional en complejidad para el desarrollo o en rendimiento del sistema(dado que perderá tiempo llevando a cabo las verificaciones ante cualquier nuevasolicitud de recursos), no debe sorprender a nadie que los ingenieros se inclinenpor adoptar la estrategia del avestruz — Claro está, siempre que no haya opciónrazonable.

5. Otros recursosTutorial de hilos de Perl

Python higher level threading interface

Spin Locks & Other Forms of Mutual Exclusion (Theodore P. Baker 2010)

Dining philosophers revisited (Armando R. Gingras, 1990)

50