Volver al temario
Tema 04 · 70 páginas

Comunicación y sincronización

Tema completo Modo estudio
TEXTO ORIGINAL · Páginas 5154 Ver PDF

Actividades y ejercicios de autoevaluación

Texto íntegro de la conversión. Las figuras y la disposición de las notas se pueden consultar en el PDF.

Página 51

Actividades

  1. ¿Por qué interesa que las zonas de exclusión sean cuanto más pequeñas mejor? Proponed un ejemplo en el que se vean claras las posibles ventajas.

  2. ¿Es necesario acceder en exclusión mutua a una variable compartida si tan sólo se quiere consultar su valor? ¿Por qué?

  3. Demostrad que si las operaciones sem_wait y sem_signal no se ejecutan atómicamente se puede violar la condición de exclusión mutua.

  4. Implementad un semáforo n-ario utilizando mensajes indirectos con buzones.

  5. Considerad la comunicación entre procesos utilizando el esquema de los buzones. Considerad buzones infinitos (send no bloquea nunca).

a) Suponed que un proceso quiere esperar un mensaje del buzón-A y un mensaje del buzón-B (un mensaje de cada buzón). ¿Qué secuencia de sends y receives debería ejecutar?

b) ¿Qué secuencia de sends y receives se debería ejecutar si el mismo proceso quiere esperar un mensaje de un buzón o un mensaje del otro, o de buzón-A o de buzón-B?

  1. Escribid la serie de llamadas al sistema que ha de invocar el intérprete de comandos para dar servicio a la línea de comandos ps | grep sh | wc -1.

  2. Cuando se hace el shut-down de una máquina con el sistema operativo Linux, el SO genera la señal SIGTERM a todos los procesos que quedan en ejecución, espera unos segundos y, a continuación, genera la señal SIGKILL sobre todos los procesos que quedan en ejecución. ¿Por qué creéis que procede de este modo y no genera directamente la señal SIGKILL?

  3. Trabajando sobre un terminal en Linux, pulsar las teclas Ctrl-C suele provocar la muerte del proceso en ejecución. ¿Podéis explicar los acontecimientos que se producen desde que el usuario pulsa Ctrl-C hasta que el proceso muere?

  4. Suponed que un proceso Unix muere debido al depósito de una señal. ¿Su proceso padre puede saber qué señal ha provocado la muerte del hijo? Para responder podéis consultar la página del manual de la llamada al sistema wait.

  5. Buscad información sobre qué características del estándar de señales POSIX se pueden cambiar utilizando el campo sa_flags de la estructura de datos struct sigaction de la llamada al sistema sigaction.

  6. Escribid un programa que calcule cuál es la medida interna de las pipes. Podéis utilizar señales.

Ejercicios de autoevaluación

  1. Os proponemos que creéis un generador de números aleatorios. Los números se irán obteniendo de un vector de memoria intermedia (buffer) circular de medida maxbuff en la que diferentes flujos irán insertando y extrayendo números siguiendo el esquema del productor/consumidor. El sistema se compone de los tres tipos de flujos siguientes:
  • El flujo productor, que se encarga de crear números y los va introduciendo en un vector de memoria intermedia con una política de asignación FIFO compartida por todos los flujos. Como este vector tiene una medida finita, los flujos productores se bloquearán cuando esté lleno.
  • El flujo aleatorizador, que se encarga de consumir los números. Lo hace tomando los cinco números más antiguos del vector de memoria intermedia (si no hay cinco, se espera a que estén), aplica la función int processa(X1...,X5) y obtiene otro número como resultado. Este número es introducido otra vez en el vector de memoria intermedia como si este proceso fuera un productor.
  • El flujo consumidor, que se encarga de obtener los valores aleatorios y de darlos a quien se los pida. Para hacerlo, en el instante en el que necesita un número, mira el valor de un número cualquiera de los que hay en el vector de memoria intermedia sin extraerlo.

Pedimos el código esquemático de generador de números aleatorios poniendo énfasis en las partes de sincronización y exclusión mutua de estos tres tipos de flujos. La solución debe tener en cuenta que podría haber diferentes flujos de cada tipo.

Página 52

  1. Tenemos un vector de memoria intermedia circular de medida N que contiene elementos de un cierto tipo y que permite comunicar diferentes procesos entre ellos. Algunos de los procesos –los productores– escriben elementos mientras el vector de memoria intermedia no esté lleno, y los otros –los consumidores– leen elementos de este vector de memoria mientras no esté vacío.

Queremos introducir el concepto de prioridad entre los productores y los consumidores. Cada proceso productor y cada proceso consumidor tendrá una prioridad asociada entre 0 (la menos prioritaria) y P - 1 (la más prioritaria), donde P es el número de procesos que hay en ejecución. Querremos también que un proceso consumidor no acceda al vector de memoria intermedia mientras haya algún otro proceso consumidor con más prioridad que también quiera acceder a él. Análogamente, un proceso productor no debe acceder al vector de memoria intermedia mientras haya algún otro proceso productor con más prioridad que también quiera acceder a él.

Implementad la solución de manera que si damos permiso a un proceso para acceder al vector de memoria intermedia, el resto de procesos del mismo tipo que quieran acceder a él deberán esperar que éste lo libere, independientemente de la prioridad de los procesos que pidan permiso para acceder a éste.

Debéis resolver este problema utilizando tantos semáforos como necesitéis y también memoria compartida entre los procesos (variables compartidas). Disponéis de una función que devuelve la prioridad del proceso que la ejecuta denominada int prioridad, que devuelve un valor entre 0 y P - 1.

  1. Simulad una horchatería en la que trabajan dos personas: un horchatero y un camarero. El primero se encarga de producir las horchatas y pasarlas al camarero para que las pueda vender. Los clientes, que pueden ser muy numerosos, cuando llegan a la horchatería piden un número variable de horchatas al camarero. Éste, una vez las ha servido, avisa al cliente para que las tome. Mientras no hay clientes, el horchatero continúa preparando horchatas y el camarero espera clientes nuevos.

/Definiciones e inicialización de variables compartidas/ #define true 1 int n_horchatas = 0; semaphore sem1, sem2, sem3, sem4, sem5, sem6;

void camarero() { int tmp, i; while (true) { sem_wait(sem2); sem_wait(sem5); tmp = n_horchatas; n_horchatas = 0; sem_signal(sem5); for (i=0; i<tmp; i++) { sem_wait(sem1); servir_horchata(); } sem_signal(sem3); } }

Figura 44

void cliente() { ir_a_la_horchatería(); sem_wait(sem4); sem_wait(sem5); n_horchatas = cuantas_horchatas(); sem_signal(sem5); sem_signal(sem2); sem_wait(sem3); sem_signal(sem4); tomar_horchatas(); }

Figura 45

void horchatero() { int preparadas = 0;

Página 53

while (true) { preparar_horchata(); sem_signal(sem1); sem_wait(sem6); preparadas++; sem_signal(sem6); } }

Figura 46

Ayudas: antes de empezar, leed bien todas las preguntas, os puede ayudar sustituir los nombres de los semáforos por otros más significativos. Las rutinas servir_horchata, ir_a_la_horchatería, cuantas_horchatas, tomar_horchatas y preparar_horchata no modifican ninguna variable ni ningún semáforo compartido.

a) ¿Con qué valor inicializaríais cada uno de los semáforos?

b) ¿Para qué se supone que sirven el semáforos sem5 y sem6? ¿Son realmente necesarios estos semáforos? ¿Por qué?

c) ¿Qué información se guarda de manera implícita en el contador del semáforo sem1?

d) ¿Para qué sirve el semáforo sem4?

  1. En el programa de la figura 30, ¿qué efecto tendría eliminar las dos invocaciones a la llamada al sistema close realizadas por el proceso padre justo antes de invocar las llamadas al sistema wait?

  2. Indicad qué señales hay bloqueadas en los puntos de ejecución A, B, C, D, E, F, G, H e I.

void sigusr1(int signum) {sigset_t mascara; /* C / sigemptyset(&mascara); sigaddset(&mascara, SIGINT); sigaddset(&mascara, SIGusr1); sigprocmask(SIG_BLOCK, &mascara, NULL); / D*/ } void sigusr2(int signum) { /* B / kill(getpid(), SIGUSR1); } void sigalrm(int signum) { / H*/ } main() {sigset_t mascara; struct sigaction new; new.sa_handler = sigusr1; new.sa_flags = 0; sigemptyset(&new.sa_mask);sigaddset(&new.sa_mask, SIGALRM); sigaction(SIGUSR1, &new, NULL); new.sa_handler = sigalrm; sigemptyset(&new.sa_mask); sigaction(SIGALRM, &new, NULL); new.sa_handler = sigusr2; sigemptyset(&new.sa_mask);sigaddset(&new.sa_mask, SIGPIPE); sigaction(SIGUSR2, &new, NULL); /* A / kill(getpid(), SIGUSR2); / E / sigemptyset(&mascara); sigaddset(&mascara, SIGALRM); sigprocmask(SIG_BLOCK, &mascara, NULL); / F / sigfillset(&mascara); sigdelset(&mascara, SIGALRM); alarm(2); / G / sigsuspend(&mascara); / I */ }

Página 54

  1. Escribid un fragmento de código que espere la llegada de una señal de tipo SIGUSR1 o SIGUSR2, pero que atienda cualquier otro tipo de señal que no esté bloqueada.

  2. Escribid un fragmento de código que espere la llegada de una señal de tipo SIGUSR1 y otro de tipo SIGUSR2 (en cualquier orden), pero que atienda cualquier otro tipo de señal que no esté bloqueada.

Ver fragmento extraído sin normalizar
## Página 51

<!-- source-page: 51 -->

GNUFDL • PID_00214803                                                                                  51                                                                                         Comunicación y sincronización

Actividades

1. ¿Por qué interesa que las zonas de exclusión sean cuanto más pequeñas mejor? Proponed
un ejemplo en el que se vean claras las posibles ventajas.

2. ¿Es necesario acceder en exclusión mutua a una variable compartida si tan sólo se quiere
consultar su valor? ¿Por qué?

3. Demostrad que si las operaciones sem_wait y sem_signal no se ejecutan atómicamente
se puede violar la condición de exclusión mutua.

4. Implementad un semáforo n-ario utilizando mensajes indirectos con buzones.

5. Considerad la comunicación entre procesos utilizando el esquema de los buzones. Consi-
derad buzones infinitos (send no bloquea nunca).

a) Suponed que un proceso quiere esperar un mensaje del buzón-A y un mensaje del buzón-B
(un mensaje de cada buzón). ¿Qué secuencia de sends y receives debería ejecutar?

b) ¿Qué secuencia de sends y receives se debería ejecutar si el mismo proceso quiere esperar
un mensaje de un buzón o un mensaje del otro, o de buzón-A o de buzón-B?

6. Escribid la serie de llamadas al sistema que ha de invocar el intérprete de comandos para
dar servicio a la línea de comandos ps | grep sh | wc -1.

7. Cuando se hace el shut-down de una máquina con el sistema operativo Linux, el SO genera
la señal SIGTERM a todos los procesos que quedan en ejecución, espera unos segundos y, a
continuación, genera la señal SIGKILL sobre todos los procesos que quedan en ejecución.
¿Por qué creéis que procede de este modo y no genera directamente la señal SIGKILL?

8. Trabajando sobre un terminal en Linux, pulsar las teclas Ctrl-C suele provocar la muerte
del proceso en ejecución. ¿Podéis explicar los acontecimientos que se producen desde que el
usuario pulsa Ctrl-C hasta que el proceso muere?

9. Suponed que un proceso Unix muere debido al depósito de una señal. ¿Su proceso padre
puede saber qué señal ha provocado la muerte del hijo? Para responder podéis consultar la
página del manual de la llamada al sistema wait.

10. Buscad información sobre qué características del estándar de señales POSIX se pueden
cambiar utilizando el campo sa_flags de la estructura de datos struct sigaction de la
llamada al sistema sigaction.

11. Escribid un programa que calcule cuál es la medida interna de las pipes. Podéis utilizar
señales.


Ejercicios de autoevaluación

1. Os proponemos que creéis un generador de números aleatorios. Los números se irán ob-
teniendo de un vector de memoria intermedia (buffer) circular de medida maxbuff en la que
diferentes flujos irán insertando y extrayendo números siguiendo el esquema del produc-
tor/consumidor. El sistema se compone de los tres tipos de flujos siguientes:
•    El flujo productor, que se encarga de crear números y los va introduciendo en un vector
    de memoria intermedia con una política de asignación FIFO compartida por todos los
    flujos. Como este vector tiene una medida finita, los flujos productores se bloquearán
    cuando esté lleno.
•    El flujo aleatorizador, que se encarga de consumir los números. Lo hace tomando los
    cinco números más antiguos del vector de memoria intermedia (si no hay cinco, se espera
    a que estén), aplica la función int processa(X1...,X5) y obtiene otro número como
    resultado. Este número es introducido otra vez en el vector de memoria intermedia como
    si este proceso fuera un productor.
•    El flujo consumidor, que se encarga de obtener los valores aleatorios y de darlos a quien
    se los pida. Para hacerlo, en el instante en el que necesita un número, mira el valor de
    un número cualquiera de los que hay en el vector de memoria intermedia sin extraerlo.

Pedimos el código esquemático de generador de números aleatorios poniendo énfasis en las
partes de sincronización y exclusión mutua de estos tres tipos de flujos. La solución debe
tener en cuenta que podría haber diferentes flujos de cada tipo.

## Página 52

<!-- source-page: 52 -->

GNUFDL • PID_00214803                                                                                  52                                                                                         Comunicación y sincronización

2. Tenemos un vector de memoria intermedia circular de medida N que contiene elementos
de un cierto tipo y que permite comunicar diferentes procesos entre ellos. Algunos de los
procesos –los productores– escriben elementos mientras el vector de memoria intermedia no
esté lleno, y los otros –los consumidores– leen elementos de este vector de memoria mientras
no esté vacío.

Queremos introducir el concepto de prioridad entre los productores y los consumidores.
Cada proceso productor y cada proceso consumidor tendrá una prioridad asociada entre 0
(la menos prioritaria) y P - 1 (la más prioritaria), donde P es el número de procesos que
hay en ejecución. Querremos también que un proceso consumidor no acceda al vector de
memoria intermedia mientras haya algún otro proceso consumidor con más prioridad que
también quiera acceder a él. Análogamente, un proceso productor no debe acceder al vector
de memoria intermedia mientras haya algún otro proceso productor con más prioridad que
también quiera acceder a él.

Implementad la solución de manera que si damos permiso a un proceso para acceder al vector
de memoria intermedia, el resto de procesos del mismo tipo que quieran acceder a él deberán
esperar que éste lo libere, independientemente de la prioridad de los procesos que pidan
permiso para acceder a éste.

Debéis resolver este problema utilizando tantos semáforos como necesitéis y también me-
moria compartida entre los procesos (variables compartidas). Disponéis de una función que
devuelve la prioridad del proceso que la ejecuta denominada int prioridad, que devuelve un
valor entre 0 y P - 1.

3. Simulad una horchatería en la que trabajan dos personas: un horchatero y un camarero.
El primero se encarga de producir las horchatas y pasarlas al camarero para que las pueda
vender. Los clientes, que pueden ser muy numerosos, cuando llegan a la horchatería piden
un número variable de horchatas al camarero. Éste, una vez las ha servido, avisa al cliente
para que las tome. Mientras no hay clientes, el horchatero continúa preparando horchatas
y el camarero espera clientes nuevos.

/*Definiciones e inicialización de variables compartidas*/
#define true 1
int n_horchatas = 0;
semaphore sem1, sem2, sem3, sem4, sem5, sem6;

void camarero() {
int tmp, i;
while (true) {
   sem_wait(sem2);
   sem_wait(sem5);
   tmp = n_horchatas;
   n_horchatas = 0;
   sem_signal(sem5);
   for (i=0; i<tmp; i++) {
         sem_wait(sem1);
         servir_horchata();
}
   sem_signal(sem3);
}
}

Figura 44

void cliente() {
   ir_a_la_horchatería();
   sem_wait(sem4);
   sem_wait(sem5);
   n_horchatas =
        cuantas_horchatas();
   sem_signal(sem5);
   sem_signal(sem2);
   sem_wait(sem3);
   sem_signal(sem4);
   tomar_horchatas();
}

Figura 45

void horchatero() {
int preparadas = 0;

## Página 53

<!-- source-page: 53 -->

GNUFDL • PID_00214803                                                                                  53                                                                                         Comunicación y sincronización

while (true) {
   preparar_horchata();
   sem_signal(sem1);
   sem_wait(sem6);
   preparadas++;
   sem_signal(sem6);
}
}

Figura 46

Ayudas:  antes  de  empezar,  leed  bien  todas  las  preguntas,  os  puede  ayudar  sustituir
los nombres de los semáforos por otros más significativos. Las rutinas servir_horchata,
ir_a_la_horchatería, cuantas_horchatas, tomar_horchatas y preparar_horchata no modifican nin-
guna variable ni ningún semáforo compartido.

a) ¿Con qué valor inicializaríais cada uno de los semáforos?

b) ¿Para qué se supone que sirven el semáforos sem5 y sem6? ¿Son realmente necesarios estos
semáforos? ¿Por qué?

c) ¿Qué información se guarda de manera implícita en el contador del semáforo sem1?

d) ¿Para qué sirve el semáforo sem4?

4. En el programa de la figura 30, ¿qué efecto tendría eliminar las dos invocaciones a la
llamada al sistema close realizadas por el proceso padre justo antes de invocar las llamadas
al sistema wait?

5. Indicad qué señales hay bloqueadas en los puntos de ejecución A, B, C, D, E, F, G, H e I.

void sigusr1(int signum)
{sigset_t mascara;
   /* C */
   sigemptyset(&mascara);
   sigaddset(&mascara, SIGINT); sigaddset(&mascara, SIGusr1);
   sigprocmask(SIG_BLOCK, &mascara, NULL);
   /* D*/
}
void sigusr2(int signum)
{ /* B */
   kill(getpid(), SIGUSR1);
}
void sigalrm(int signum)
{ /* H*/
}
main()
{sigset_t mascara;
   struct sigaction new;
   new.sa_handler = sigusr1; new.sa_flags = 0;
   sigemptyset(&new.sa_mask);sigaddset(&new.sa_mask, SIGALRM);
   sigaction(SIGUSR1, &new, NULL);
   new.sa_handler = sigalrm; sigemptyset(&new.sa_mask);
   sigaction(SIGALRM, &new, NULL);
   new.sa_handler = sigusr2;
   sigemptyset(&new.sa_mask);sigaddset(&new.sa_mask, SIGPIPE);
   sigaction(SIGUSR2, &new, NULL);
   /* A */
   kill(getpid(), SIGUSR2);
   /* E */
   sigemptyset(&mascara); sigaddset(&mascara, SIGALRM);
   sigprocmask(SIG_BLOCK, &mascara, NULL);
   /* F */
   sigfillset(&mascara); sigdelset(&mascara, SIGALRM);
   alarm(2);
    /* G */
   sigsuspend(&mascara);
   /* I */
}

## Página 54

<!-- source-page: 54 -->

GNUFDL • PID_00214803                                                                                  54                                                                                         Comunicación y sincronización

6. Escribid un fragmento de código que espere la llegada de una señal de tipo SIGUSR1 o
SIGUSR2, pero que atienda cualquier otro tipo de señal que no esté bloqueada.

7. Escribid un fragmento de código que espere la llegada de una señal de tipo SIGUSR1 y
otro de tipo SIGUSR2 (en cualquier orden), pero que atienda cualquier otro tipo de señal
que no esté bloqueada.

Descargar Markdown originalEstudiar este apartado