26/01/2010 Tema 5: Comunicación y sincronización y sincronización de procesos de procesos Tema 5: Comunicación y sincronización de procesos • • • • • • Co u cac ó y ssincronización Comunicación c o ac ó Espera activa Semáforos Problemas clásicos de com. y sin. Mecanismos de com. com y sin. sin Servicios POSIX 1 26/01/2010 Escenario Un sistema operativo multiprogramado permite: ejecución concurrente y compartición de recursos entre varios procesos activos Tipos de procesos: Procesos independientes • • • • Su estado no es compartido Son deterministas S Son reproducibles d ibl Pueden ser detenidos y rearrancados sin ningún efecto negativo Procesos cooperantes • Su estado es compartido • Su funcionamiento no es determinista • Su funcionamiento puede ser irreproducible • Si son detenidos y posteriormente rearrancados puede que se produzcan efectos negativos Escenario Los procesos cooperantes pueden acceder simultáneamente a recursos compartidos •Deben comunicarse para lograr un objetivo común => Mecanismos de comunicación •Pueden producirse secuenciaciones incorrectas => Mecanismos de sincronización Problemas clásicos: •Problema del productor consumidor •Problema del productor‐consumidor •Problema de la sección crítica •Problema de lectores‐escritores •Problema del puente estrecho •etc. 2 26/01/2010 Comunicación y sincronización • • • • • • Problema del productor‐consumidor Condiciones de carrera Sección crítica Productor‐consumidor El problema de los lectores y escritores Comunicación cliente cliente‐servidor servidor Comunicación y sincronización • Acceso concurrente a datos compartidos puede dar como resultado datos inconsistentes • El mantenimiento de la consistecia de los datos requiere mecanismos para asegurar el orden en la ejecución de los procesos cooperates • Como ejemplo usamos el problema del consumidor‐ productor. Un proceso produce datos que son posteriormente procesados por otro proceso Ej: el manejador de teclado y el programa que recoge los caracteres de un buffer Lo más cómodo es emplear un buffer circular 3 26/01/2010 Comunicación y sincronización • El entero cuenta lleva la cuenta de elementos del buffer llenos – Inicialmente cuenta está a 0 – Se incrementa Se incrementa cuando el productor el productor produce un nuevo produce un nuevo elemento – Se decrementa cuando el consumidor consume un elemento Escribe Lee Consumidor Productor Productor while (true) { /* produce un elemento en la variable nuevoElto */ while (cuenta == BUFFER_SIZE); // no hace nada buffer [in] = nuevoElto; i (i 1) % BUFFER SIZE in = (in + 1) % BUFFER_SIZE; cuenta++; } 4 26/01/2010 Consumidor while (true) { while (cuenta == 0); // no hace while (cuenta 0); // no hace nada sigConsumido = buffer[out]; out = (out + 1) % BUFFER_SIZE; cuenta‐‐; /* consume el elemento en sigConsumido } Condiciones de carrera I cuenta++ se puede implementar como: registro1 = cuenta registro1 = registro1 + 1 cuenta = registro1 cuenta-- se puede implementar como: registro2 = cuenta registro2 = registro2 - 1 cuenta = registro2 Considerar la ejecución intercalada con “cuenta = 5” : S0: productor ejecuta registro1 = cuenta {registro1 = 5} S1: productor ejecuta registro1 = registro1 + 1 {registro1 = 6} S2: consumidor ejecuta registro2 = cuenta {registro2 = 5} S3: consumidor ejecuta registro2 = registro2 - 1 {registro2 = 4} S4: productor ejecuta cuenta = registro1 {cuenta = 6 } S5: consumidor ejecuta cuenta = registro2 {cuenta = 4} 5 26/01/2010 Condiciones de carrera II ¿Cómo solucionar el problema planteado? 1. Atomicidad‐ Una operación se dice que es atómica (en un sistema uniprocesador) cuando se ejecuta con las interrupciones deshabilitadas uniprocesador) cuando se ejecuta con las interrupciones deshabilitadas Las referencias y las asignaciones son atómicas en a mayoría de los sistemas. Esto no es siempre cierto para matrices, estructuras o números en coma flotante Si el HW no proporciona operaciones atómicas, éstas no pueden construirse por SW 2. Exclusión mutua ‐ Es el mecanismo que asegura que sólo una persona o proceso está haciendo algo en un instante determinado (los otros están excluidos). excluidos) 3. Sección crítica ‐ es la sección de código, o colección de operaciones, en el que se actualizan variables comunes. Cuando un proceso está ejecutando código de su SC, ningún otro proceso puede estar en su SC. Sección crítica I Toda solución debe cumplir tres condiciones: 1. Exclusión mútua‐ No puede haber dos procesos simultánamante en la región crítica. Si un proceso Pi está ejecutando en su región crítica, ningún otro proceso se puede ejecutar en dicha región crítica. 2. Progreso ‐ Ningún proceso fuera de la región crítica puede bloquear a otros procesos. 3. Espera acotada ‐ Ningún proceso debe esperar infinitamente para entrar en su región crítica. 6 26/01/2010 Sección crítica II Exclusión mútua usando regiones críticas Tipos de soluciones Suposiciones: 1. 2. Los procesos se ejecutan a una velocidad <>0 Su velocidad relativa no influye No se realiza ninguna asumpción sobre la velocidad de los procesos o sobre el número de CPUs Soluciones: 1. Basadas en variables de control – solución de Peterson 2. Basadas en instrucciones máquina – test‐and‐set, swap 3. Basadas en primitivas del SO – sin espera activa 4. Basadas en regiones críticas y monitores ‐ lenguajes de programación, Ada, Java 7 26/01/2010 Exclusión mútua con espera activa Mutual Exclusion with Busy Waiting (1) PROBLEMA: Alternancía estricta ‐> viola la condición 2 de sección crítica Exclusión mútua con espera activa Solución de Peterson I • Solución para dos procesos • Asume que las instrucciones LOAD y STORE son atómicas; esto es, no se pueden interrumpir es, no se pueden • Los dos procesos comparten dos variabes: – int turn; – boolean flag[2] • La variable turn indica el turno de entrada en la región crítica. • El array El fl se utiliza flag tili para indicar i di sii un proceso está tá listo li t para entrar en la región: • flag[i] = true implica que el proceso Pi está listo! 8 26/01/2010 Exclusión mútua con espera activa Solución de Peterson II Algorithmo para el proceso Pi while (true) { hil (t ) { flag[i] = TRUE; turn = j; while ( flag[j] && turn == j); REGIÓN CRÍTICA flag[i] = FALSE; } Exclusión mútua con espera activa Solución de Peterson III 9 26/01/2010 Exclusión mútua con espera activa Sincronización Hardware I • Muchos sistemas proporcionan soporte hardware para secciones críticas • Uniprocesadores – se pueden deshabilitar las interrupciones – El código se ejecuta sin expulsión: poco recomendable – Generalmente muy ineficiente en multiprocesadores s • Hace que el SO no escale bien • IInstrucciones t i h hw especiales i l de ejecución d j ió atómica: tó i • Atómica = no‐interrumpible – lee una palabra o bit y escribe un valor (TAS o TSL) – Intercambia el contenido de dos posiciones de memoria o una posición de memoria y un registro (SWAP) Exclusión mútua con espera activa Sincronización Hardware II Entrar en la región crítica utilizando la instrucción Test-and-Set-Lock: TSL Test-And-Set: TAS 20 10 26/01/2010 Exclusión mútua con espera activa Sincronización Hardware III • Operación atómicas: int test-and-set(int *valor) { int temp; temp = *valor; *valor = 1; return temp; /* true */ } void swap (int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; return; } Exclusión mútua con espera activa Sincronización Hardware IV • Sección crítica con test‐and‐set: • Los procesos comparten la variable lock con valor inicial FALSE while ( test‐and‐set (&lock ) ; /* nada // critical section lock = FALSE; • Sección crítica con swap: • Los procesos comparten la variable lock con valor inicial FALSE. variable local llave. • Cada proceso utiliza una variable local llave. llave = TRUE; while ( llave == TRUE) swap (lock, llave ); // región crítica lock = FALSE; 11 26/01/2010 Semáforos I Herramienta de sincronización sin espera activa Semáforo S – variable entera Misma máquina Dos operaciones atómicas modifican S: wait() y signal() Originalmente llamadas P() y V() wait(s); /* entrada en la seccion critica */ < seccion critica > signal(s); • /* salida de la seccion critica */ El semáforo binario debe tener valor inicial 1 Semáforos II • Semáforo contador – valor entero • Semáforo binario – valor entero entre 0 y 1; puede ser más sencillo ll de implementar l – También se conocen como mutex o locks – Proporciona exclusión mútua Semaphore S; // initializado 1 wait (S); wait (S); Critical Section signal (S); 12 26/01/2010 Semáforos III Valor del semáforo (s) P P 0 P 1 2 1 0 wait(s) wait(s) -1 wait(s) -2 desbloquea -1 signal(s) desbloquea 0 signal(s) Ejecutando código de la sección crítica Proceso bloqueado en el semáforo 1 signal(s) Semáforos IV • Cada semáforo tiene asociada una cola de espera. • Cada entrada e t ada een la cola tiene a co a t e e dos dos items: te s: – valor (un tipo entero) – Puntero al siguiente elemento de la lista • Dos operaciones: – block – coloca al proceso p que invoca la operación q p en la cola apropiada. – wakeup – quita uno de los procesos de la cola y lo pone en la cola de preparados. 13 26/01/2010 Semáforos V wait(s) { s = s - 1; if (s < 0) { <Bloquear al proceso> } } signal(s) { s = s + 1 1; if (s <= 0) <Desbloquear a un proceso bloqueado por la operacion wait> } } Interbloqueo e Inanición • Interbloqueo – Dos o más procesos están esperando infinitamente por un evento que debe producir un proceso que está bloqueado. bloqueado Sean S y Q dos semáforos inicializados a 1 P1 P0 wait (S); wait (Q); . . . signal (S); signal (Q); wait (Q); wait (S); . . . signal (Q); signal (S); • Inanición – Bloqueo indefinido. Un proceso puede estar en indefinidamente en la cola del semáforo. 14 26/01/2010 Problemas Clásicos de Comunicación y Sincronización • Productor‐ Productor‐consumidor consumidor: : Problema del buffer li it d limitado • Lectores Lectores‐‐escritores escritores: : Problema acceso exclusivo del escritor y acceso compartido de lectores • El El barbero barbero dormilón : : Problema con procesos no cíclicos no cíclicos • Cena de los de los filósofos filósofos: : Problema de número recursos limitados Productor-Consumdor ProductorConsumdor.. Condiciones de carrera #define BUFFER_SIZE 100 int cuenta 0; void productor(void) { int elto; while (true) { /* produce un elemento en la variable elto */ while (cuenta == BUFFER_SIZE); // no hace nada insertarElemento(elto); cuenta++; } } void consumidor(void) { int elto; while (true) { while (cuenta == 0); // no hace nada elto =consumeElto(); cuenta‐‐; /* consume el elemento en sigConsumido } } 15 26/01/2010 Productor-Consumdor ProductorConsumdor:: Problema del buffer limitado • N buffers, cada uno puede contener un elemento • Semáforo mutex inicializado a 1 • Semáforo full inicializado a 0 • Semáforo S áf empty inicializado i i i li d a N N Productor-Consumdor ProductorCon semáforos I • Estructura del proceso productor while (true) { // produce an item wait (empty); wait (mutex); // add the item to the buffer signal (mutex); i l( t ) signal (full); } 16 26/01/2010 Productor-Consumdor ProductorCon semáforos II • Estructura del proceso consumidor while (true) { wait (full); it (f ll) wait (mutex); // remove an item from buffer signal (mutex); signal (empty); // consume the removed item } Productor-Consumdor ProductorCon semáforos III 17 26/01/2010 Lectores-Escritores LectoresEscritores.. Definición del Problema • Varios procesos concurrentes comparten una hoja de datos: – Readers – leen la hoja de datos; no realizan actualizaciones l – Writers – pueden leer leer y escribir • Problema – se permiten varios lectores al mismo tiempo y un único escritor con acceso exclusivo • Variables compartidas – – – – Hoja de datos Semáforo mutex inicializado a to 1. Semáforo wrt inicializado a 1. Entero readcount inicializado a 0. Lectores-Escritores LectoresEscritores.. Con semáforos I • Estructura de un proceso escritor while (true) { while (true) { wait (wrt) ; // writing is performed signal (wrt) ; } 18 26/01/2010 Lectores-Escritores LectoresEscritores.. Con semáforos II • Estructura de un proceso lector while (true) { wait (mutex) ; readcount ++ ; if (readcount == 1) wait (wrt) ; signal (mutex) // reading is performed wait (mutex) ; readcount ‐ ‐ ; if (readcount == 0) signal (wrt) ; signal (mutex) ; signal (mutex) ; } Lectores-Escritores LectoresEscritores.. Con semáforos III 19 26/01/2010 El barbero dormilón dormilón.. Definición del Problema Variables compartidas Semáforo clientes inicializado a 0 (num clientes esperando, no incluye al que se está atendiendo ). No se puede leer‐> Espera: num clientes esperando Semáforo barbero inicializado a 0 (dormido=0, activo=1) El barbero dormilón dormilón.. Con semáforos I 20 26/01/2010 Cena de los filósofos. filósofos. Definición del Problema • Variables compartidas – Plato de arroz (hoja de datos) – Semáforo tenedor [5] inicializado a 1 Cena de los filósofos. filósofos. Con semáforos I Estructura del filósofo I (con interbloqueos): While (true) { While (true) { wait (tenedor[i]); wait (tenedor[ (i + 1) % 5]); // comer signal (tenedor[i] ); signal (tenedor[ (i + 1) % 5]); // pensar } 21 26/01/2010 Cena de los filósofos. filósofos. Con semáforos II Cena de los filósofos. filósofos. Con semáforos III 22 26/01/2010 Problemas con Semáforos • Uso correcto de las peraciones: – signal (mutex) …. wait (mutex) – wait (mutex) … wait (mutex) – O Omisión i ió de wait (mutex) o signal (mutex) d it ( t ) i l ( t ) (o ambos) Mecanismos de Comunicación y Sincronización • • • • • Monitores Variables de Variables de condición condición Paso de Paso de Mensajes Mensajes Barreras Transacciones 23 26/01/2010 Monitores • Herramienta con alto nivel de abstracción que proporciona sinccronización entre procesos o threads. Lenguajes de alto nivel: Java: synchronize, Ada 95: protected • Únicamente un proceso puede stas activo en le monitor en un momento dado. • monitor nombre‐ monitor { // decharación de variables compartidas procedure P1 (…) { …. } … procedure Pn (…) {……} procedure Pn ( ){ } código de inicialización ( ….) { … } … } } Esquema de un monitor 24 26/01/2010 Variables de condición • Variables de sincronización asociadas a un mutex – condition x, y; • Operationes sobre una variale de condición: – wait (x) – El proceos que invoca la operación se bloquea – signal (x) – reanuda uno de los procesos bloqueados en la condición X (si hay alguno); sino la señal se pierde: han invocado wait (x) • Conveniente ejecutarlas dentro de una región crítica (ej. un monitor) Esquema de un monitor con variables de condición • Cuando un proceso reanuda a otro en una variable de condición: – Señalizar y esperar (Hoare, Hansen): la condición para el proceso que reanuda la ejecución se sigue cumpliendo. – Señalizar y continuar (Lampson): Evita la inanición de los procesos bloqueados. El proceso reanudado debe preguntar otra vez por la condición (while X). 25 26/01/2010 Cena de los filósofos. filósofos. Con monitores I monitor DP { enum { THINKING; HUNGRY, EATING) state [5] ; condition s[5]; // Un semáforo por cada filósfo void take_forks void take forks (int i) { i) { state[i] = HUNGRY; test(i); if (state[i] != EATING) wait(s[i]); Lampson } void put_forks (int i) { state[i] = THINKING; test((i + 4) % 5); test((i + 1) % 5); } // while (state[i] != EATING) s[i].wait; en // izquierda // derecha void test (int id t t (i t i) { i) { if ( (state[i] == HUNGRY) state[(i + 4) % 5] != EATING) &&( &&(state[(i + 1) % 5] != EATING) ) { state[i] = EATING ; signal(s[i]) ; } } } initialization_code() { for (int i = 0; i < 5; i++) state[i] = THINKING; } Cena de los filósofos. filósofos. Con monitores II • Cada filósofo i invoca las operaciones take_forks() y put_forks() en la siguiente secuencia: dp.take_forks(i); EAT dp.put_forks(i); 26 26/01/2010 Productor-consumidor Productorcon monitores Paso de mensajes • Permite resolver: – Exclusión mutua – Sincronización entre un proceso que recibe un mensaje y otro que lo envía í – Comunicación de datos entre espacios de memoria diferentes • mismo computador • diferentes computadores • Primitivas básicas: – send(P, mensaje) envía un mensaje al proceso P – receive(Q, mensaje) recibe un mensaje del proceso Q – receive(ANY, mensaje) recibe un mensaje de cualquier proceso 27 26/01/2010 Paso de mensajes mensajes.. Aspectos de diseño – Tamaño del mensaje: fijo o variable – Flujo de datos: unidireccional o bidireccional – Nombrado • Directo Directo‐> proceso: pid > proceso: pid o ANY o ANY – send(P, mensaje) envía un mensaje al proceso P – receive(Q, mensaje) recibe un mensaje del proceso Q • Indirecto (puerto, cola) ‐> buzón asociado a un proceso – send(Q, mensaje) envía un mensaje a la cola o puerto Q – receive(Q, mensaje) recibe un mensaje a la cola o puerto Q – Sincronización (síncrono, asíncrono) • Envío y recepción bloqueante: síncrono ‐> cita o rendezvous (Ej. ADA) • Envío no bloqueante q yy recepción p bloqueante: es q la más utilizada • Envío y recepción no bloqueante: asíncrono – Almacenamiento • Sin (cita) • Asícrono – Ámbito • Local (tubería pipes) • Remoto (sockets) Paso de mensajes mensajes.. Interbloqueos • Ejemplo: Si C1 y C2 son dos colas de mensajes: P1 receive(P2, M) ... send(P2, M) P2 receive(P1, N) ... send(P1, N) • Condiciones del interbloqueo: – – – – Exclusión mutua Retención y espera No apropiación Espera circular 28 26/01/2010 Productor-consumidor Productorcon paso de mensajes Mecanismos ofrecidos por el SO Mecanismo Comunicación Sincronización Tuberías SÍ SÍÍ Señales NO SÍ Variables en memoria compartida SÍ SÍ Semáforos NO SÍ Mutex y variables condicionales NO SÍ Paso de mensajes SÍ SÍ 29 26/01/2010 Servicios POSIX • Tuberías – Sin Sin nombre nombre – Con Con nombre nombre • • • • Semáforos Mutex V i bl condicionales Variables condicionales Variables di i l Paso de Paso de mensajes mensajes Tuberías • • • • Flujo unidireccional Con almacenamiento. El tamaño depende de cada SO d SO Generalmente se implementa como una zona de memoria compartida entre los procesos que usan la tubería write() Proceso A Read() Tubería Proceso B 30 26/01/2010 Tuberías II • Flujo Bidireccional: dos tuberías write() Read() Tubería 1 Proceso A Proceso B Tubería 2 Read() write() Tuberías III • Funcionamiento FIFO • Escritura • Si Si la la tubería está llena o se tubería está llena o se llena durante esta operación, se bloquea el proceso escritor hasta que se pueda realizar la operación • Lectura • Sila tubería está vacía, se tubería está vacía, se bloquea la proceso lector hasta que se escriba en ella. Si no hay escritores, la operación devuelve fin de archivo y no hay bloqueo • Si la tubería almacena M bytes y se quieren leer n bytes: si M>=n la operación bytes: si M>=n, la operación devuelve n. Si M<n, la operación devuelkve M. En ambos casos, se eliminan los datos leídos de la tubería • Operación atómica • Operación atómica 31 26/01/2010 Tuberías IV • Tipos de tuberías: – Tuberías sin nombre (pipe): • Sólo Sólo se pueden usar por el proceso que las crea y sus se pueden usar por el proceso que las crea y sus procesos hijos. – Tuberías con nombre (FIFO): • Tienen un nombre local (nombre del archivo FIFO) • Se pueden usar para comunicar procesos independientes • Principales servicios POSIX para gestionar tuberías: – Crear/Borrar – Abrir/Cerrar – Leer/Escribir Tuberías POSIX I • Crear una tubería sin nombre – int pipe(int fildes[2]); – Devuelve dos descriptores de archivos y cero si se ejecuta correctamente. En caso contrario contrario, -1 • Crear una tuberías con nombre – int mkfifo(char *name, mode_t mode); – Devuelve cero si se ejecuta correctamente. En caso contrario, -1 • Abrir una tubería con nombre con nombre – int open(char *fifo, int flag); – Bloquea al proceso que la ejecuta hasta que haya otro proceso en el otro extremo del FIFO – Devuelve un descriptor de archivo que se puede usar para leer y escribir del FIFO y cero si se ejecuta correctamente. En otro caso –1 32 26/01/2010 Tuberías POSIX II • Cerrar una tubería – int close(int fd); – Tubería sin nombre: se destruye cuando se cierra el último de sus descriptores asociados • Borrar una tubería con nombre – int unlink(char *fifo); – Destruye el FIFO si todos los procesos que lo están utilizando lo han cerrado con el servicio close Tuberías POSIX III • Leer de una tubería – int read(fildes[0], buffer, n); – Busca el bloque o los bloques del archivo especificado por descriptor (descriptor de lectura del pipe o descriptor del FIFO), transfiere el número de datos n_bytes que se desean leer de la tubería al buffer de memoria buf y actualiza el puntero – No bloquea al proceso si está vacía la tubería y sin escritores – En caso de éxito devuelve el número de datos leídos. Si no, -1 • Escribir en una tubería – int write(fildes[1], buffer, n); – Busca el bloque o los bloques del archivo especificado por descriptor (descriptor de escritura del pipe o descriptor del FIFO), transfiere el número de datos n_bytes que se desean escribir desde el buffer de memoria buf a la tubería y actualiza el puntero – Si no hay lectores, el SO envía la señal SIGPIPE al proceso – En caso de éxito devuelve el número de datos escritos. Si no, -1 33 26/01/2010 Ejemplo con tuberías POSIX. Región crítica #include <stdio.h> #include <unistd.h> void main(void) { int fildes[2]; char h paso; /* pipe para sincronizar */ /* caracter t para sincronizar i i */ pipe(fildes); write(fildes[1], &paso, 1); /* necesario para entrar en la seccion critica la primera vez */ if (fork() == 0) { /* proceso hijo */ for(;;) { read(fildes[0], &paso, 1); /* entrada seccion critica */ < Seccion critica > write(fildes[1], &paso, 1); /* salida seccion critica */ } } else { /* proceso padre */ for(;;) { read(fildes[0], &paso, 1); /* entrada seccion critica */ < seccion critica > write(fildes[1], &pao, 1); /* salida seccion critica */ } } } Ejemplo con tuberías POSIX. Productor--Consumidor Productor #include <stdio.h> #include <unistd.h> void main(void) { int fildes[2]; / /* pipe para comunicar y sincronizar */ / int dato_p[4]; /* datos a producir */ int dato_c; /* dato a consumir */ pipe(fildes); if (fork() == 0) { /* proceso hijo: productor */ for(;;) { < producir dato_p, escribe 4 enteros * write(fildes[1], dato_p, 4*sizeof(int)); } } else { /* proceso padre: consumidor */ for(;;) { read(fildes[0], &dato_c, sizeof(int)); /* consumir dato, lee un entero */ } } } 34 26/01/2010 Ejemplo con tuberías POSIX. Mandato ``ls ``ls | wc” wc” I /* programa que ejecuta el mandato ``ls | wc'' */ void main(void) { int fd[2]; pid_t pid; ls pipe i wc if (pipe(fd) < 0) { perror(``pipe''); exit(-1); } pid = fork(); switch(pid) { case -1: /* error */ perror(``fork''); exit(-1); case 0: /* proceso hijo ejecuta ``ls'' */ close(fd[0]); /* cierra el pipe de lectura; no lo utiliza */ close(STDOUT_FILENO); /* cierra la salida estandar; libera el descriptor 1*/ dup(fd[1]); /* duplica el pipe de escritura, se le asigna el 1 */ close(fd[1]); /* cierra el pipe de escritura de la tubería */ execlp(``ls'',``ls'',NULL); perror(``execlp''); exit(-1); default: /* proceso padre ejecuta ``wc'' */ close(fd[1]); /* cierra el pipe de escritura */ close(STDIN_FILENO); /* cierra la entrada estandar; libera el descrip. 0 */ dup(fd[0]); /* duplica el pipe de lectura, se le asigna el 0 */ close(fd[0]); /* cierra el pipe de lecture de la tubería */ execlp(``wc'',``wc'',NULL); perror(``execlp''); } } Ejemplo con tuberías POSIX. Mandato ``ls ``ls | wc” wc” II STDIN STDOUT pipe(fd) 35 26/01/2010 Ejemplo con tuberías POSIX. Mandato ``ls ``ls | wc” wc” II STDIN STDOUT STDIN STDOUT fd(0) fd(1) pipe(fd) fork() pipe Ejemplo con tuberías POSIX. Mandato ``ls ``ls | wc” wc” II STDIN STDOUT STDIN STDOUT fd(0) fd(1) pipe(fd) STDIN STDOUT fd(0) fd(1) pipe STDIN STDOUT fd(0) fd(1) fork() pipe close(1); dup(fd[1]); close(fd[1]); 36 26/01/2010 Ejemplo con tuberías POSIX. Mandato ``ls ``ls | wc” wc” II STDIN STDOUT STDIN STDOUT fd(0) fd(1) pipe(fd) STDIN STDOUT fd(0) fd(1) pipe STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) pipe STDIN STDOUT fd(0) fd(1) fork() pipe close(1); dup(fd[1]); close(fd[1]); close(fd[0]); Ejemplo con tuberías POSIX. Mandato ``ls ``ls | wc” wc” II STDIN STDOUT STDIN STDOUT fd(0) fd(1) pipe(fd) STDIN STDOUT fd(0) fd(1) pipe STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) pipe STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) pipe i STDIN STDOUT fd(0) fd(1) fork() pipe close(1); dup(fd[1]); close(fd[1]); close(fd[0]); close(0); dup(fd[0]); close(fd[0]); 37 26/01/2010 Ejemplo con tuberías POSIX. Mandato ``ls ``ls | wc” wc” II STDIN STDOUT STDIN STDOUT fd(0) fd(1) pipe(fd) fork() pipe STDIN STDOUT fd(0) fd(1) pipe STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) pipe STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) close(0); dup(fd[0]); close(fd[0]); pipe i exec(ls); exec(wc); pipe STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) close(1); dup(fd[1]); close(fd[1]); close(fd[0]); close(fd[1]) Ejemplo con tuberías POSIX. Mandato ``ls ``ls | wc” wc” II STDIN STDOUT STDIN STDOUT fd(0) fd(1) ls STDIN STDOUT fd(0) fd(1) pipe(fd) fork() pipe pipe STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) pipe STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) close(1); dup(fd[1]); pipe i STDIN STDOUT fd(0) fd(1) STDIN STDOUT fd(0) fd(1) close(fd[1]); pipe pipe close(1); dup(fd[1]); close(fd[1]); close(1); dup(fd[1]); close(fd[1]); close(fd[0]); wc 38 26/01/2010 Semáforos POSIX I • Tipos de semáforos: – Semáforos sin nombre: • Permiten sinchronizar los threads dentro de un proceso. • Se pueden usar parar sincronizar el proceso que los crea y los procesos que lo heredan (proceso hijos). – Semáforos con nombre: • El semáro tiene un nombre; sigue la convención de nombres en ficheros. • Los procesos que se sincronizan no tienen que estar sincronizados. • P Principales servicios POSIX para gestionar i i l i i POSIX ti semáforos: – Inicializar/destruir – Abrir/cerrar – Wait/signal Semáforos POSIX II • Inicializa un semáforo sin nombre – – – int sem_init(sem_t *sem, int shared, int val); Crea e inicializa un semáforo sin nombre. Todos los semáforos deben inicializarse antes de usarse. El parámetro shrared indica si el semáforo sicncroniza threads (shared =0) o procesos emparentados (shared<>0). • Destruye un semáforo sin nombre – – int sem_destroy(sem_t *sem); D t Destruye un semáforo áf sin i nombre, b previamente i t creado d con sem_init. i it • Abre (crea) un semáforo con nombre. – – – – – sem_t *sem_open(char *name, int flag); sem_t *sem_open(char *name, int flag, mode_t mode,int val); Abre un semáforo con nombre; si no existe, lo crea. El parámetro flag indica si se accede a un semáforo previamente creado (flag=0) . Si el semáfor se crea, se especifícan los permisos (parám mode) y el valor inicial (parám val). • Cierra un semáforo con nombre – – int sem_close(sem_t *sem); Cierra un semáforo con nombre, rompiendo la asociación que tenía un proceso con un semáforo. • Destruye un semáforo sin nombre Destruye un semáforo sin nombre – – int sem_unlink(char *name); Elimina del sistema un semáforo con nombre, se postpone la destrucción del semáforo hasta que todos los procesos que lo estén utilizando lo hayan cerrado con sem_close. • Realiza la operación wait sobre un semáforo – int sem_wait(sem_t *sem); • Realiza la operación signal sobre un semáforo – int sem_post(sem_t *sem); 39 26/01/2010 Semáforos POSIX. Productor--Consumidor I Productor #include <pthread.h> #include <semaphore.h> #define MAX_BUFFER #define DATOS_A_PRODUCIR 1024 100000 sem_t t elementos; l t sem_t huecos; int buffer[MAX_BUFFER]; /* elementos l t en el l b buffer ff */ /* huecos en el buffer */ /* buffer comun */ void main(void) { pthread_t th1, th2; /* tamanio del buffer */ /* datos a producir */ /* identificadores de threads */ /* inicializar los semaforos */ sem_init(&elementos, 0, 0); sem_init(&huecos, 0, MAX_BUFFER); /* crear los procesos ligeros */ pthread_create(&th1, th d t (&th1 NULL NULL, Productor, P d t NULL) NULL); pthread_create(&th2, NULL, Consumidor, NULL); /* esperar su finalizacion */ pthread_join(th1, NULL); pthread_join(th2, NULL); sem_destroy(&huecos); sem_destroy(&elementos); exit(0); } Semáforos POSIX. Productor--Consumidor II Productor void Productor(void) { int pos = 0; int i; for(i=0; i < DATOS_A_PRODUCIR; i++ ) sem_wait(&huecos); buffer[pos] = i; pos = (pos + 1) % MAX_BUFFER; sem_post(&elementos); /* posicion dentro del buffer */ /* dato a producir */ { /* un hueco menos */ /* un elemento mas */ } pthread_exit(0); } void Consumidor(void) { int pos = 0; int dato; int i; for(i=0; i < DATOS_A_PRODUCIR; i++ ) { sem_wait(&elementos); dato = buffer[pos]; pos = (pos + 1) % MAX_BUFFER; sem_post(&huecos); / /* cosumir dato */ } pthread_exit(0); /* un elemento menos */ * un hueco mas */ } 40 26/01/2010 Semáforos POSIX. Lectores--escritores I Lectores #include <pthread.h> #include <semaphore.h> int dato = 5; /* recurso */ int n_lectores = 0; /* numero de lectores */ sem_t sem_lec; /* controlar el acceso n_lectores */ sem t mutex; sem_t /* controlar el acceso a dato */ / / void main(void) { pthread_t th1, th2, th3, th4; sem_init(&mutex, 0, 1); sem_init(&sem_lec, 0, 1); pthread_create(&th1, NULL, pthread_create(&th2, NULL, pthread_create(&th3, NULL, pthread_create(&th4, NULL, pthread_join(th1, h d j i ( h1 NULL); ) pthread_join(th2, NULL); pthread_join(th3, NULL); pthread_join(th4, NULL); Lector, NULL); Escritor, NULL); Lector, NULL); Escritor, NULL); /* cerrar todos los semaforos */ sem_destroy(&mutex); sem_destroy(&sem_lec); exit(0); } Semáforos POSIX. Lectores--escritores II Lectores void Lector(void) { sem_wait(&sem_lec); n_lectores = n_lectores + 1; if (n_lectores == 1) sem_wait(&mutex); sem_post(&sem_lec); printf(``%d\n'', dato); /* leer dato */ sem_wait(&sem_lec); n_lectores = n_lectores - 1; if (n_lectores == 0) sem_post(&mutex); sem_post(&sem_lec); pthread_exit(0); } void Escritor(void) { sem_wait(&mutex); dato = dato + 2; /* modificar el recurso */ sem_post(&mutex); pthread_exit(0); } 41 26/01/2010 Mutex POSIX I • Mecanismo de sincronización de hilos • Permite acceso exclusivo a recursos compartidos • Se pueden realizar dos operaciones atómicas – lock: • Intenta obtener el mutex. Si ya está adquirido, se bloquea. – unlock: • Desbloquea el mutex. Si hay threads bloqueados el, desbloquea uno. • Principales servicios POSIX para gestionar semáforos: – Inicializar/destruir – Abrir/cerrar – lock/unlock Mutex POSIX II • int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr); – Inicializa un mutex. • int pthread_mutex_destroy(pthread_mutex_t *mutex) ; – Destruye un mutex. • int pthread_mutex_lock(pthread_mutex_t *mutex); – Intenta obtener el mutex. Bloquea al proceso ligero si el mutex se encuentra adquirido por otro proceso ligero. • int pthread_mutex_unlock(pthread_mutex_t *mutex); – Desbloquea el mutex. 42 26/01/2010 Mutex POSIX. Lectores--escritores I Lectores #include <pthread.h> int dato = 5; /* recurso */ int n_lectores = 0; /* numero de lectores */ pthread_mutex_t mutex_lec; /* controlar el acceso n_lectores */ pthread_mutex_t mutex; /* controlar el acceso a dato */ void main(void) { pthread_t th1, th2, th3, th4; pthread_mutex_init(&mutex, NULL); pthread_mutex_init(&mutex_lec, NULL); pthread_create(&th1, NULL, pthread_create(&th2, NULL, pthread_create(&th3, NULL, pthread_create(&th4, NULL, pthread_join(th1, NULL); pthread_join(th2, NULL); pthread_join(th3, NULL); pthread_join(th4, NULL); Lector, NULL); Escritor, NULL); Lector, NULL); Escritor, NULL); /* cerrar todos los semaforos */ pthread_mutex_destroy(&mutex); pthread_mutex_destroy(&mutex_lec); exit(0); } Mutex POSIX. Lectores--escritores II Lectores void Lector(void) { pthread_mutex_lock(&mutex_lec); n_lectores = n_lectores + 1; if (n_lectores == 1) pthread_mutex_lock(&mutex); pthread_mutex_unlock(&mutex_lec); printf(``%d\n'', dato); /* leer dato */ pthread_mutex_lock(&mutex_lec); n_lectores = n_lectores - 1; if (n_lectores == 0) pthread_mutex_ulock(&mutex); pthread_mutex_unlock(&mutex_lec); pthread_exit(0); } void Escritor(void) { pthread_mutex_lock(&mutex); dato = dato + 2; /* modificar el recurso */ pthread_mutex_unlock(&mutex); pthread_exit(0); } 43 26/01/2010 Variables condicionales POSIX I • Variables de sincronización asociadas a un mutex (se utiliza como monitor) • Conveniente ejecutarlas entre lock y unlock Dos operaciones atómicas: atómicas: • Dos operaciones – wait Bloquea al proceso ligero que la ejecuta y le expulsa del mutex – signal Desbloquea a uno o varios procesos suspendidos en la variable condicional. El proceso que se despierta compite de nuevo por el mutex Variables condicionales POSIX II • int pthread_cond_init(pthread_cond_t*cond, pthread_condattr_t*attr); – Inicializa una variable condicional. • • • • int pthread_cond_destroy(pthread_cond_t *cond); – Destruye un variable condicional. un variable condicional. int pthread_cond_signal(pthread_cond_t *cond); – Se reactivan uno o más de los procesos ligeros que están suspendidos en la variable condicional cond. – No tiene efecto si no hay ningún proceso ligero esperando (diferente a los semáforos). int pthread_cond_broadcast(pthread_cond_t *cond); – Todos los threads suspendidos en la variable condicional cond se reactivan. – No tiene No tiene efecto si no hay ningún no hay ningún proceso ligero esperando. esperando int pthread_cond_wait(pthread_cond_t*cond, pthread_mutex_t*mutex); – Suspende al proceso ligero hasta que otro proceso señaliza la variable condicional cond. – Automáticamente se libera el mutex. Cuando se despierta el proceso ligero vuelve a competir por el mutex. 44 26/01/2010 Variables condicionales POSIX III • Thread A lock(mutex); /* acceso al recurso */ comprobar las estructuras de datos; while (recurso ocupado) wait(condition, mutex); /* recurso como ocupado*/ unlock(mutex); • Thread B lock(mutex); /* acceso al recurso */ … signal(condition, mutex); /* liberar recurso */ unlock(mutex); • Importante utilizar while Variables condicionales POSIX. Productor--consumidor I Productor #include <pthread.h> #include <mutex.h> #define MAX_BUFFER _ #define DATOS_A_PRODUCIR 1024 100000 int buffer[MAX_BUFFER]; pthread_mutex_t mutex; pthread_cond_t no_lleno; pthread_cond_t no_vacio; int n_elementos; /* /* /* /* /* /* tamanio del buffer */ /* datos a producir */ buffer comun */ mutex para controlar el monitor */ controla el llenado del buffer */ controla el vaciado del buffer */ numero de elementos en el buffer */ main(int argc, char *argv[]){ pthread_t th1, th2; pthread_mutex_init(&mutex, NULL); pthread_cond_init(&no_lleno, NULL); pthread_cond_init(&no_vacio, NULL); 45 26/01/2010 Variables condicionales POSIX. Productor--consumidor II Productor pthread_create(&th1, NULL, Productor, NULL); pthread_create(&th2, NULL, Consumidor, NULL); pthread_join(th1, NULL); pthread_join(th2, NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&no_lleno); pthread_cond_destroy(&no_vacio); exit(0); } Variables condicionales POSIX. Productor--consumidor III Productor void Productor(void) { int dato, i ,pos = 0; /* codigo del productor */ for(i=0; i < DATOS_A_PRODUCIR; i++ ) { dato = i; /* producir dato */ pthread_mutex_lock(&mutex); /* acceder al buffer */ while (n_elementos == MAX_BUFFER) /* si buffer lleno */ pthread_cond_wait(&no_lleno, &mutex); /* se bloquea */ buffer[pos] = i; pos = (pos + 1) % MAX_BUFFER; n_elementos ++; pthread_cond_signal(&no_vacio); th d d i l( i ) /* b buffer ff no vacio i */ pthread_mutex_unlock(&mutex); } pthread_exit(0); } 46 26/01/2010 Variables condicionales POSIX. Productor--consumidor IV Productor void Consumidor(void) { /* codigo del sonsumidor */ int dato, i ,pos = 0; for(i=0; i < DATOS DATOS_A_PRODUCIR; A PRODUCIR; i++ ) { pthread_mutex_lock(&mutex); /* acceder al buffer */ while (n_elementos == 0) /* si buffer vacio */ pthread_cond_wait(&no_vacio, &mutex); /* se bloquea */ dato = buffer[pos]; pos = (pos + 1) % MAX_BUFFER; n_elementos --; pthread_cond_signal(&no_lleno); /* buffer no lleno */ pthread mutex unlock(&mutex); pthread_mutex_unlock(&mutex); printf("Consume %d \n", dato); /* consume dato */ } pthread_exit(0); } Variables condicionales POSIX. Lectores--escritores I Lectores #include <pthread.h> #include <mutex.h> int dato = 5; int n_lectores = 0; pthread_mutex_t mutex; pthread_mutex_t mutex_lectores; /* /* /* /* recurso */ numero de lectores */ controlar el acceso a dato */ controla la variable n_lectores */ main(int argc, char *argv[]) { pthread_t th1, th2, th3, th4; pthread_mutex_init(&mutex, NULL); pthread_cond_init(&no_lectores, NULL); pthread_create(&th1, pthread_create(&th2, pthread_create(&th3, pthread_create(&th4, NULL, NULL, NULL, NULL, Lector, NULL); Escritor, NULL); Lector, NULL); Escritor, NULL); 47 26/01/2010 Variables condicionales POSIX. Lectores--escritores II Lectores pthread_join(th1, pthread_join(th2, pthread_join(th3, pthread_join(th4, NULL); NULL); NULL); NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&no_lectores); exit(0); } void Escritor(void) { /* codigo del escritor */ pthread_mutex_lock(&mutex); dato = dato + 2; /* modificar el recurso */ pthread_mutex_unlock(&mutex); pthread_exit(0); } Variables condicionales POSIX. Lectores--escritores III Lectores void Lector(void) { /* codigo del lector */ pthread_mutex_lock(&mutex_lectores); n_lectores++; (n_lectores == 1) ) if ( pthread_mutex_lock(&mutex); pthread_mutex_unlock(&mutex_lectores); printf("%d\n", dato); /* leer dato */ pthread_mutex_lock(&mutex_lectores); n_lectores--; if (n_lectores == 0) pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&mutex_lectores); pthread_exit(0); } 48 26/01/2010 Colas de mensajes POSIX I • mqd_t mq_open(char *name, int flag, mode_t mode, mq_attr *attr); – Crea una cola de mensajes cola de mensajes con nombre con nombre y atributos y atributos attr: attr: • Número máximo de mensajes. • Tamaño máximo del mensaje. • Bloqueante, No bloqueante. • int mq_close (mqd_t mqdes); – Cierra una cola de mensajes. cola de mensajes. • int mq_unlink(char *name); – Borra una cola de mensajes. Colas de mensajes POSIX II • int mq_send(mqd_t mqdes, char *msg, size_t len, int prio); – EEnvía í el mensaje l j msg de longitud d l i d len l a la cola de l l d mensajes mqdes con prioridad prio; – Si la cola está llena el envío puede ser bloqueante o no. • int mq_receive(mqd_t mqdes, char *msg, size_t len, p int prio); – Recibe un mensaje msg de longitud len de la cola de mensajes mqdes con prioridad prio; – Recepción bloqueante o no. 49 26/01/2010 Colas de mensajes POSIX. Región crítica I void main(void) { mqd_t mutex; struct mq_attr attr; char c; attr.mq_maxmsg = 1; attr.mq_msgsize = 1; /* para sincronizar la seccion critica */ /* atributos de la cola de mensajes */ /* caracter para sincronizar */ /* numero maximo de mensajes */ /* tamanio del mensaje */ mutex = mq_open(``MUTEX'', O_CREAT|O_RDWR, 0777, &attr); mq_send(mutex, &c, 1, 0); /* entrar en la seccion critica la primera vez */ Colas de mensajes POSIX. Región crítica II if (fork() == 0) { /* proceso hijo */ for(;;) { q_receive(mutex, &c, 1, 0); /* entrada seccion critica */ mq /* seccion critica */ mq_send(mutex, &c, 1, 0); /* salida seccion critica */ } else { /* proceso padre */ for(;;) { mq_receive(mutex, &c, 1, 0); /* entrada seccion critica */ /* seccion critica */ mq_send(mutex, &c, 1, 0); /* salida seccion critica */ } } } 50 26/01/2010 Colas de mensajes POSIX. Productor--consumidor I Productor #define MAX_BUFFER 1024 /* tamanio del buffer */ #define DATOS_A_PRODUCIR 100000 /* datos a producir */ mqd_t almacen; /* cola de mensaje donde dejar los datos producidos y recoger los datos a consumir */ void oid main( main(void) oid) { struct mq_attr attr; attr.mq_maxmsg = MAX_BUFFER; attr.mq_msgsize = sizeof(int); almacen = mq_open("ALMACEN", O_CREAT|O_RDWR, 0777, &attr); if (almacen == -1) { perror("mq_open"); exit(0); } if (fork() == 0) Productor(); else Consumidor(); exit(0); /* proceso hijo */ /* proceso padre */ } Colas de mensajes POSIX. Productor--consumidor II Productor void Productor(void) { int dato, i; for(i=0; i 0 i < DATOS_A_PRODUCIR; i++ i ) { dato = i; /* producir dato */ printf("Produce %d \n", dato); mq_send(almacen, &dato, sizeof(int), 0); } return; } void Consumidor(void) { int dato; int i; for(i=0; i < DATOS_A_PRODUCIR; i++ ) { mq_receive(almacen, &dato, sizeof(int), 0); /* cosumir dato */ printf("Consume %d \n", dato); } return; } 51