Sistemas operativos Comunicación y sincronización de procesos Comunicación y sincronización de procesos Contenido 1. Introducción 2. Comunicación por memoria común 3. El problema de la sección crítica 4. Soluciones hardware 5. Soluciones por software 6. Mecanismos de comunicación y sincronización - Semáforos - Tuberías -Mutex y variables condicionales 7. Problemas clásicos de programación concurrente 8. Comunicación por mensajes Introducción ● ● ● Un programa concurrente es aquel que contiene uno o más procesos que trabajan en forma conjunta para realizar una tarea Procesos concurrentes entonces: ● Compiten por recursos (tanto físicos como lógicos) ● Se comunican entre si Los procesos concurrentes se pueden ejecutar en los siguientes modelos de computadora: ● Multiprogramación: con un solo procesador ● Multiprocesador: memoria compartida. ● Multicomputadora → memoria distribuida – mensajes Introducción ● ● ● Existe la necesidad de comunicación entre procesos. Los procesos requieren con frecuencia comunicación entre ellos. La comunicación entre procesos puede seguir dos esquemas básicos: ● Comunicación por memoria común ● Comunicación por mensajes Comunicación por memoria común ● La comunicación por memoria común se puede dar en los siguientes casos: ● ● ● Espacio de direcciones único: es el caso de los hilos de ejecución El s.o. crea una zona de memoria accesible a un grupo de procesos Problema de la sección crítica Comunicación por memoria común ● Ejemplo: suma de los n primeros números naturales de forma paralela. Hilo principal S1 = 1+2+ … +50 S2 = 51+52+ … +100 suma_total = S1 + S2 Comunicación por memoria común int suma_total = 0; void suma_parcial(int ni, int nf) { int j = 0; int suma = 0; for (j = ni; j <= nf; j++) suma = suma + j; suma_total = suma_total + suma; pthread_exit(0) } mov mov add mov reg1, suma_total reg2, suma reg1, reg2 suma_total, reg1 El problema de la sección crítica ● ● ● Una sección crítica es una zona de código en donde el proceso modifica variables o datos comunes entre varios procesos. Problemas potenciales: puede introducir condiciones de carrera si no se adoptan las medidas adecuadas. Posible solución: sincronizar el acceso a los datos (exclusión mútua). El problema de la sección crítica Formulación del problema de la sección crítica ● ● ● Sean n procesos compitiendo para acceder a datos compartidos Cada proceso tiene una zona de código, denominada sección crítica, en la que accede a los datos compartidos. Problema: encontrar un protocolo del tipo: protocolo de entrada sección CRÍTICA protocolo de salida sección RESTANTE El problema de la sección crítica Solución al problema de la sección crítica debe satisfacer: ● Exclusión mútua ● Progreso ● Espera limitada Cada proceso se ejecuta a su propia velocidad, pero se desconoce el orden de ejecución. El problema de la sección crítica Variables compartidas: var turno : 0..1; task Pi; … while turno <> i do no-op; sección CRÍTICA turno := j; sección RESTANTE … task Pj; end Pi; … while turno <> j do no-op; sección CRÍTICA turno := i; sección RESTANTE … end Pi; Soluciones por software ● Pueden implementarlo procesos concurrentes que se ejecuten en máquina monoprocesador o multiprocesador con una memoria principal compartida ● No se require ningún soporte hardware ● Soluciones: ● Algoritmo de Dekker: Algoritmo de exclusión mutua para dos procesos Algoritmo de Peterson ➢ ● ➢ Adaptación del algoritmo de Dekker Soluciones por software ● Primer intento: variable Turno int turno = 0; Proceso 0 . . . Proceso 1 . . . While (turno != 0) /* no hacer nada */ SC0; While (turno != 1) /* no hacer nada */ SC1; turno = 1; . . . turno = 0; . . . Soluciones por software ● Segundo intento: Avisores Boolean señal[2] = {false, false}; Proceso 0 Proceso 1 . . . . . . While (señal[1]) /* no hacer nada */ While (señal[0]) /* no hacer nada */ señal[0] = true; SC0; señal[0] = false; . . . señal[1] = true; SC1; señal[1] = false; . . . Soluciones por software ● Tercer intento Boolean señal[2] = {false, false}; Proceso 0 Proceso 1 . . . señal[0] = true; While (señal[1]) . . . señal[1] = true; While (señal[0]) /* no hacer nada */ SC0; señal[0] = false; . . . /* no hacer nada */ SC1; señal[1] = false; . . . Soluciones por software ● Cuarto intento Boolean señal[2] = {false, false}; Proceso 0 . . . señal[0] = true; While (señal[1]){ Señal[0] = false; wait(); Señal[0] = true; } SC0; señal[0] = false; . . . Proceso 1 . . . señal[1] = true; While (señal[0]){ Señal[0] = false; wait(); Señal[0] = true; } SC1; señal[1] = false; . . . Soluciones por hardware ● Las soluciones hardware son soluciones a nivel de instrucciones del lenguaje máquina: ● Deshabilitación de interrupciones ● Instrucción test_and_set (operación atómica). ● Instrucción swap (operación atómica) Soluciones por hardware Deshabilitación de interrupciones ● ● ● ● ● Las instrucciones de máquina que se utilizan son: ● DI : Deshabilitar interrupciones ● EI : Habilitar interrupciones Se consigue la exclusión mutua inhibiendo los cambios de contexto durante la SC. Solución solo viable a nivel del núcleo del SO La eficiencia puede verse afectada – capacidad del procesador No funciona en arquitectura multiprocesador. Soluciones por hardware Instrucción test and set ● Permite comparar y fijar una variable atómicamente. bool TS(int i) { if(i==0){ i = 1; return true; } else { return false; } } Soluciones por hardware Instrucción Swap ● Permite intercambiar atómicamente dos variables. void swap(int register, int memory) { int temp; temp = memory; Memory = register; register = temp; } Soluciones por hardware Soporte hardware para la SC const int n= /*No.procesos*/; int cerrojo const int n= /*No. procesos*/; void P(int i) int cerrojo { void P(int i) while(1) { { int clavei; while(!TS(cerrojo)) while(1) /*No hacer nada*/ { SC; clavei = 1; cerrojo = 0; /* SR */ while(clavei != 0) } swap(clavei, cerrojo) } SC; swap(clavei, cerrojo) /* SR */ } } Soluciones por hardware Ventajas ● ● ● Desventajas Aplicable para cualquier número de procesos en sistemas con memoria compartida. Es simple verificar y fácil de Puede usarse para soportar múltiples secciones críticas, cada una con su propia variable. ● ● ● Se emplea espera activa Puede inanición arbitraria producirse – selección Puede producirse interbloqueo – Ejm: proceso de baja prioridad entra a la SC y es interrumpido por uno de mayor prioridad Semáforos Definición (Dijkstra): ● Es una variable entera ● ● Se inicializa con un valor no negativo Sobre ella se definen dos operaciones o funciones atómicas: ● Espera (wait o P ) ● Señal (signal o V) Semáforos S: semaforo(N) N >= 0 Espera P(S) { S:=S-1; if S<0 then esperar(S); } Señal V(S) { S:=S+1; if S<=0 then despertar(S); } La operación esperar(S) suspende al proceso que ha invocado P() y lo introduce en una cola de espera asociada a S. La operación despertar(S) extrae un proceso de la cola de espera asociada a S y lo activa. Semáforos Solución al problema de la SC con semáforos ● Los semáforos son un mecanismo de sincronización Variables compartidas: var mutex:semaforo(1); //vlr inicial 1 task Pi; ... P(mutex); sección CRÍTICA V(mutex); sección RESTANTE ... end Pi; Semáforos Implementación typedef struct { int contador; queueType *cola; } Semaforo Espera void wait(semaforo s) { s.contador--; if (s.contador < 0) { encola(s.cola, proActual); bloquea(); } } Semáforos Implementación typedef struct { int contador; queueType *cola; } Semaforo Señal void signal(semaforo s) { s.contador++; if (s.contador <= 0) { P = sacar(s.cola); despertar(P) } } Semáforos Semáforos Implementación. ● Espera activa: ● ● El proceso que espera entrar en la sección crítica “desaprovecha” el tiempo de CPU comprobando cuando puede entrar (ejecutando las instrucciones del protocolo de entrada). Espera Pasiva: ● ● La espera se realiza en la cola del semáforo, que es una cola de procesos suspendidos. El proceso que espera entrar en la sección crítica no utiliza CPU; está suspendido. Semáforos Ejemplos de utilización ● Un semáforo es un mecanismo de sincronización de uso general: ➢ ➢ Para conseguir exclusión mútua. Para forzar relaciones de precedencia, como por ejemplo: ● El proceso Pj debe ejecutar B después de que el proceso Pi haya ejecutado A. Semáforos var sinc: semaforo(0); task Pi; ... A; V(sinc); ... task Pj; ... P(sinc) B; Semáforos Problemas derivados de una utilización incorrecta ● Interbloqueos: Hay un conjunto de procesos en el que todos esperan (indefinidamente) un evento que sólo otro proceso del conjunto puede producir. Ejemplo: var s1: semaforo(1); s2: semaforo(1); task P1 ... P(s1); P(s2); ... V(s1); V(s2); task P2 ... P(s2); P(s1); ... V(s1); V(s2); Tuberias ● ● Es un mecanismos de comunicación y sincronización Conceptualmente, cada proceso ve la tubería como un conducto con dos extremos ● ● ● Un extremo → para escribir o insertar datos y el otro extremo → para extraer o leer datos de la tubería El flujo de datos es unidireccional y FIFO ( extraen de la tubería en el mismo orden en que se insertaron) Tuberias Comunicación Unidireccional Tuberias Comunicación Bidireccional Mutex y variables condicionales ● ● ● Son mecanismos de sincronización de hilos Los mutex se emplean para obtener acceso exclusivo a recursos compartidos y para asegurar la exclusión mutua sobre la sección crítica Es un semáforo binario con dos operaciones atómicas ● ● lock(m) Intenta bloquear el mutex, si el mutex ya está bloqueado el proceso se suspende unlock(m) Desbloquea el mutex, si existen procesos bloqueados en el mutex se desbloquea a uno. lock(m) /* solicita la entrada en la sección crítica <sección crítica> unlock(m) /* salida de la sección crítica*/ Mutex y variables condicionales Mutex y variables condicionales ● Variables de sincronización asociadas a un mutex que se utiliza para bloquear un proceso hasta que ocurra algún suceso. ● Conveniente ejecutarlas entre lock y unlock ● Dos operaciones atómicas para esperar y señalizar ● ● c_wait(): bloquea al proceso y le expulsa del mutex dentro del cual se ejecuta y al que está asociada la variable condicional c_signal(): desbloquea a uno o varios procesos suspendidos en la variable condicional. Mutex y variables condicionales Problemas clásicos de PC ● Los productores y consumidores ● Los lectores y escritores ● Los cinco filósofos Problemas clásicos de PC ● Los productores y consumidores ● Con buffer acotado Problemas clásicos de PC var buffer: array[0..n-1] of elemento; entrada:=0, salida:=0, contador:= 0 : 0..n-1; lleno: semaforo(0); vacio: semaforo(n), mutex: semaforo(1); task productor; var item: elemento; repeat item := producir(); P(vacio); P(mutex); buffer[entrada] := item; entrada:=(entrada +1) mod n; contador:=contador+1; V(mutex); V(lleno); until false end productor; task consumidor; var item: elemento; repeat P(lleno); P(mutex); item := buffer[salida] salida := (salida +1) mod n; contador:=contador-1; V(mutex); V(vacio); consumir(item); until false end consumidor; Problemas clásicos de PC var buffer: array[0..n-1] of elemento; entrada:=0, salida:=0, contador:= 0 : 0..n-1; m:mutex, lleno, vacio: variable condicional task productor; var item: elemento; repeat item := producir(); lock(m); while(contador==n) c-wait(lleno,m); buffer[entrada] := item; entrada:=(entrada +1) mod n; contador:=contador+1; if(contador == 1) c_signal(vacio); unlock(m); until false end productor; task consumidor; var item: elemento; repeat lock(m); while(contador==0) c-wait(vacio,m); item := buffer[salida] salida := (salida +1) mod n; contador:=contador-1; if(contador== n-1) c_signal(lleno); unlock(m); consumir(item); until false end consumidor; Problemas clásicos de PC ● Problema de los lectores y escritores ● ● ● Hay un conjunto de datos comunes (un fichero, una base de datos, etc.) Los procesos lectores, acceden a los datos en modo de sólo lectura Los procesos escritores, acceden a los datos en modo lectura-escritura Problemas clásicos de PC ● Problema de los lectores y escritores ● Especificación del protocolo Problemas clásicos de PC ● Lectores y escritores (prioridad a los lectores) Problemas clásicos de PC ● Lectores y escritores (prioridad a los lectores) var mutex: semaforo(1); esc semaforo(1); nlectores:=0 : integer; task escritor; ... P(esc); escribir(); V(esc); . . . end escritor; task lector; … P(mutex); nlectores := if nlectores V(mutex); leer(); P(mutex); nlectores := if nlectores V(mutex); … end lector; nlectores + 1; = 1 then P(esc); nlectores - 1; = 0 then V(esc); Problemas clásicos de PC ● Lectores y escritores (prioridad a los escritores) Problemas clásicos de PC var mutexE, mutexL, Esem, Lsem,z : semaforo(1); nlectores:=0, nescritores:=0 : integer; task lector; while(true){ P(z); P(Lsem); P(mutexL); nlectores++; if nlectores==1 then P(Esem); V(mutexL); V(Lsem); V(z); Leer_Unidad P(mutexL); nlectores--; if nlectores==0 then V(Esem); V(mutexL); } end lector; task escritor; while(true){ P(mutexE); nescritores++; if nescritores==1 then P(Lsem); V(mutexE); P(Esem); Escribir_Unidad; V(Esem); P(mutexE); nescritores--; if nescritores==0 then V(Lsem); V(mutexE); } end escritor; Comunicación por mensajes Pasos de mensajes ● ● ● ● Los sistemas de mensajes permiten la comunicación entre procesos con espacios de direcciones distintos, bien sea locales y remotos Permite resolver la exclusión mutua Sincronizar entre un proceso que recibe un mensaje y otro que lo envía Hay dos operaciones básicas ● ● send(destino, mensaje): enviar un mensaje a un destino receive(origen, mensaje): recibir un mensaje de un origen Comunicación por mensajes Aspectos de diseño relativos a este tipo de sistemas 1. Tamaño del mensaje: ➔ ➔ Fija: más sencilla, pero obliga a descomponer los mensajes grandes en mensajes de longitud fija más pequeños. Variable 2. Flujo de datos: de acuerdo al flujo de datos la comunicación puede ser: ➔ Comunicación Unidireccional: ➔ Comunicación bidireccional: Comunicación por mensajes 3. Comunicación: según el modo de nombrar el origen y el destino esta puede ser: ➢ Comunicación directa: ➢ ➢ ➢ ➢ send(P, mensaje) receive(Q, mensaje) receive(ANY, mensaje) Comunicación Indirecta: ➢ una estructura de datos: colas de mensajes o puertos Comunicación por mensajes ● Comunicación Indirecta: – – send(Q, mensaje) receive(Q, mensaje) Comunicación por mensajes Comunicación por mensajes 4. Sincronización ● Al ejecutar la primitiva send, hay dos posibilidades ➢ ➢ ● El emisor se bloquea hasta que se recibe el mensaje El emisor no se bloquea Al ejecutar la primitiva receive, también hay dos posibilidades ➢ ➢ Si hay un mensaje enviado, se recibe el mensaje y se continua la ejecución Si no hay ningún mensaje esperando, entonces, el proceso se bloquea hasta que llegue un mensaje o el proceso continua abandonando el intento de recepción de mensaje. Comunicación por mensajes 4. Sincronización ● ● Los emisores y receptores pueden ser bloqueantes o no bloqueantes Tres combinaciones: ➔ Envío bloqueante, recepción bloqueante ● ● ➔ Emisor y el receptor se bloquean hasta que se entrega el mensaje Es una técnica totalmente sincrona, conocida como cita Envío no bloqueante, recepción no bloqueante ● ● El emisor puede continuar, pero el receptor está bloqueado esperando el mensaje más utilizada → estilo servidor Comunicación por mensajes 4. Sincronización ● Tres combinaciones: ➔ Envío no bloqueante, recepción no bloqueante ● ● ● Ninguno debe esperar Totalmente asincrona Es necesario disponer de servicios que permitan al receptor saber si se ha recibido un mensaje. Comunicación por mensajes 5. Almacenamiento ● Hace referencia a la capacidad del enlace de comunicaciones. Pueden ser: ➔ ➔ ➔ ➔ No tener capacidad (sin almacenamiento) para almacenar mensajes. Tener capacidad (con almacenamiento) para almacenar mensajes. Si la cola no esta llena entonces el emisor puede continuar su ejecución Sino el emisor se bloqueará. Comunicación por mensajes Solución al problema de productores y consumidores task productor; var item : elemento; repeat item := producir(); send( consumidor, item ); until false; end productor; task consumidor; var item : elemento; repeat receive( productor, item ); consumir(item); until false; end consumidor; Comunicación directa Comunicación sincrona