Tema 5: 5: Comunicación Comunicación y y sincronización

Anuncio
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
Descargar