recopilacion 163 ejercicios de so2

106
Sist3m@s Op3r@#iv0s Sistemas Operativos 2 Estudios de Informática ETSIAp / FI Recopilación de ejercicios y problemas de la asignatura Sistemas Operativos 2 impartida en las titulaciones de Informática de la Universidad Politécnica de Valencia. Versión: 2.0s Rev: feb. 2008 Contiene soluciones. Contenido 1. Ejercicios sobre procesos y ficheros .............................................................................................................................. 2 2. Ejercicios sobre ficheros, redirección y tubos .............................................................................................................. 10 3. Ejercicios sobre señales................................................................................................................................................ 23 4. Ejercicios sobre directorios y protección. .................................................................................................................... 34 5. Ejercicios sobre hilos y sección crítica......................................................................................................................... 41 6. Ejercicios sobre semáforos y monitores ....................................................................................................................... 52 7. Problemas de sincronización ........................................................................................................................................ 79 8. Ejercicios sobre prácticas. ............................................................................................................................................ 95

Upload: cecilia-galarza-rojas

Post on 05-Jul-2015

724 views

Category:

Documents


5 download

TRANSCRIPT

Page 1: Recopilacion 163 Ejercicios de So2

Sist3m@sOp3r@#iv0s

Sistemas Operativos 2

Estudios de Informática ETSIAp / FI

Recopilación de ejercicios y problemas de la asignatura Sistemas Operativos 2 impartida en las titulaciones de Informática de la Universidad Politécnica de Valencia. Versión: 2.0s Rev: feb. 2008 Contiene soluciones. Contenido 1. Ejercicios sobre procesos y ficheros .............................................................................................................................. 2 2. Ejercicios sobre ficheros, redirección y tubos .............................................................................................................. 10 3. Ejercicios sobre señales ................................................................................................................................................ 23 4. Ejercicios sobre directorios y protección. .................................................................................................................... 34 5. Ejercicios sobre hilos y sección crítica ......................................................................................................................... 41 6. Ejercicios sobre semáforos y monitores ....................................................................................................................... 52 7. Problemas de sincronización ........................................................................................................................................ 79 8. Ejercicios sobre prácticas. ............................................................................................................................................ 95

Page 2: Recopilacion 163 Ejercicios de So2

1. Ejercicios sobre procesos y ficheros

1.1 Dificultad de los ejercicios

Nivel de dificultad Ejercicios Principiante Ej. 1-1,

Medio Ej. 1-8 Avanzado

1.2 Ejercicios Para cada una de las siguientes llamadas, describe una de las razones por las que podría fallar. Puede que haya llamadas que nunca fallen. En caso de ser así indica también por qué

Ej.

1-1 fork() Puede fallar si el sistema no tiene suficientes recursos para realizar la creación de un

nuevo proceso. Ejemplos: falta de memoria, falta de entradas libres en la tabla de procesos o haber llegado al límite impuesto para el usuario.

exit() No puede fallar. Esta llamada termina al proceso que la utilice.

wait() Fallará si no hay procesos hijos a los que esperar, o si la dirección pasada como argumento no pertenece al espacio asignado al proceso.

Para cada una de las siguientes llamadas, describe una de las razones por las que podría fallar. Puede que haya llamadas que nunca fallen. En caso de ser así indica también por qué

Ej.

1-2 execv() Existen múltiples razones. Ejemplos: Que el fichero no exista, que no sea ejecutable, que

alguna de las direcciones facilitadas como argumentos no sea válida (esté fuera de los rangos del espacio lógico a los que se ha asignado memoria), ...

open() También puede fallar por múltiples causas. Ejemplos: Que el fichero no exista, que no tenga permiso para poder utilizar el modo de apertura solicitado (por ejemplo, tratar de abrir en modo sólo lectura un fichero para el que no se tenga ese derecho en la palabra de protección), que se intente abrir en modo escritura en un dispositivo montado como sólo lectura, etc.

exit () Esta llamada no puede fallar. Con ella se solicita la terminación del proceso y no se necesita solicitar ningún recurso para poder llevar a cabo esta acción.

Indica cuántos procesos llegarán a generarse como resultado de la ejecución de este programa y cuántos de ellos terminarán. Justifícalo adecuadamente. #include <unistd.h> int main(void) { int i;

Page 3: Recopilacion 163 Ejercicios de So2

for (i=0; i < 5; i++) if (!fork()) break; while ( wait(NULL) != -1 ); }

Ej.

1-3 Sólo se generan cinco procesos (más el inicial, necesario para ejecutar el programa), debido a que los

procesos hijos salen del bucle "for" utilizando la sentencia "break;". Todos los procesos terminan. Los hijos no se suspenden en el wait(), pues esta llamada devuelve un error indicando que ellos no tenían hijos. El padre, al llegar al bucle "while " irá esperando a que todos sus hijos vayan terminando, en caso de que no lo hubieran hecho ya. Cuando ya haya visto la terminación de todos ellos, la llamada wait() devolverá un error por no quedar más procesos hijos, saliendo entonces del bucle "while". Todos los procesos llamarán implícitamente a exit() cuando el control retorne a la biblioteca de C, tras haber ejecutado completamente la función principal del programa.

Explica si como resultado de la ejecución del programa anterior podrá llegar a generarse, aunque sea durante un intervalo muy breve procesos huérfanos o procesos zombies.

Ej.

1-4 No pueden generarse procesos huérfanos, pues el proceso padre no terminará antes que los hijos. Esto se logra

con la utilización de la llamada wait(). Algunos de los procesos hijos pueden quedar en estado zombie durante un corto intervalo, si llegan a terminar antes de que el padre realice la llamada wait() correspondiente. Que esto suceda o no, depende del planificador de la CPU que utilice nuestro sistema operativo.

¿Cómo puede un proceso hijo saber si su padre ha finalizado su ejecución? Comenta la respuesta. NOTA.- No se puede modificar el código del proceso padre

Ej.

1-5

Observando si ha sido adoptado por “init” usando la llamada a sistema getppid()

Indica cuántos procesos llegarán a generarse como resultado de la ejecución de este programa y cuántos de ellos terminarán. Justifícalo adecuadamente.

#include <unistd.h> int main(void) { int i; for (i =0; i <5; i++) if (!fork()) wait(NULL); }

Ej.

1-6 El número total de procesos generados es 32, pues en cada iteración se genera un proceso nuevo por

cada uno de los ya existentes y se da un total de 5 iteraciones, con lo que al final habrá 2^5 procesos. Todos los procesos terminarán normalmente. La llamada wait() sólo es utilizada por los procesos "hijos " de cada fork() completado, por lo que falla de inmediato y no tiene ningún efecto.

Explica si como resultado de la ejecución del programa anterior podrá llegar a generarse, aunque sea durante un intervalo muy breve procesos huérfanos o procesos zombies.

Page 4: Recopilacion 163 Ejercicios de So2

Ej.

1-7 Pueden llegar a generarse ambos tipos de procesos.

Podrá haber procesos huérfanos porque resulta posible que un proceso padre termine antes que alguno de sus hijos, con lo que éstos quedarían huérfanos. También es posible que haya procesos en estado zombie durante cierto intervalo de tiempo. Esto sucederá si un proceso hijo termina antes que su padre. Como ninguno de los procesos padres llega a realizar un wait(), mientras no terminen estos padres, sus hijos que hayan terminado permanecerán en estado zombie. Cuando su padre termine, estos procesos quedarían huérfanos y serían heredados por un proceso especial del sistema (normalmente "init"), que realizaría el waitQ correspondiente y los eliminaría.

Dado el siguiente código en C y POSIX, indique que imprimirán por salida estándar cada uno de los procesos que se generan. Considere todos los posibles casos de ejecución de fork().

#include <stdio.h> #include <unistd.h> main(){ int pid; int i, status; i=0; printf("Mensaje 1: valor %d\n”,i); switch(pid=fork()){ case -1: i++; printf("Mensaje 2: valor %d\n",i); exit(-1); case 0: i++; printf("Mensaje 3: valor %d \n”,i); exit(3); default: i++; printf("Mensaje 4: valor %d \n”,i); while (wait(&status)>0); } printf("Mensaje 5: valor %d \n”,i); }

Ej.

1-8 El primer “mensaje” siempre se escribe y entonces la variable i sigue valiendo cero. A continuación se llega al

fork(), que devolverá un cero para el proceso hijo y el PID del hijo para el proceso padre. No obstante, dicha llamada podría llegar a fallar si el proceso que intenta el fork() se está ejecutando en un sistema donde haya muy poca memoria libre, por ejemplo. Por tanto, si el fork() llega a fallar, por esa u otra causa, el resultado obtenido habría sido: Mensaje 1: valor 0 Mensaje 2: valor 1 Mientras que si el fork() no falla, se obtendría: Mensaje 1: valor 0 Mensaje 3: valor 1 Mensaje 4: valor 1 Mensaje 5: valor 1 donde el “mensaje” 3 habría sido impreso por el proceso hijo y los “mensajes” 1, 4 y 5 serían impresos por el proceso padre. El orden entre los “mensajes” 3 y 4 podría ser diferente. Sólo se imprimirá un “mensaje” 1.

Dé el nombre de tres llamadas al sistema de la interfaz POSIX que nunca puedan fallar (es decir, jamás devolverán un valor -1 como resultado) y explique por qué ninguna de ellas falla jamás.

Page 5: Recopilacion 163 Ejercicios de So2

Ej.

1-9 Hay varios ejemplos. Todas aquellas llamadas que impliquen la obtención de atributos del propio

proceso jamás fallarían pues la información solicitada está siempre disponible y no se necesita ninguna control de protección para llegar a ella: getpid(), getppid(), getuid(), geteuid(), getgid(), getgid(), etc. La llamada para solicitar la terminación del propio proceso también funcionará, pues es una acción que siempre debe permitirse. Por tanto, exit() también funciona en todos los casos.

Indique cuántos procesos llegarán a generarse como resultado de la ejecución de este programa y cuántos de ellos terminarán. Justifíquelo adecuadamente.

#include <unistd.h> int main(void) { int i; for (i=0; i<3; i++) if (fork()) wait(NULL); }

Ej.

1-10

Se crearán 23 procesos y todos ellos terminan. Debido a la presencia de la llamada wait(), empleada únicamente por los procesos padres en cada iteración, los respectivos procesos hijos deberían terminar antes que sus padres. Así, el orden de creación y terminación sería el siguiente (los nombres de los procesos tratan de reflejar únicamente el orden de creación, pueden utilizarse otros cualesquiera):

1) Inicialmente se crea el proceso P1 que empieza a ejecutar el programa. 2) En la primera iteración del bucle genera al proceso P2 y espera a que termine. 3) P2 continúa y genera cuando i=1 al proceso P3 y espera a que termine. 4) P3 continúa y genera cuando i=2 al proceso P4 y espera a que termine. 5) P4 continúa, pero ya no debe generar a ningún proceso más, terminando enseguida. 6) Con ello, P3 continúa, termina también su bucle y finaliza. 7) Con ello, P2 continúa, realiza la iteración con i=2, generando entonces al proceso P5 y queda esperando

a que P5 acabe. 8) P5 puede continuar. En el bucle ya no tiene que hacer nada más, por lo que termina de inmediato. 9) Con ello, P2 continúa, termina su bucle y finaliza su ejecución. 10) Ahora P1 ya puede continuar, entra en la iteración i=1, generando a un proceso P6 y espera a que éste

termine. 11) P6 puede continuar, entrando entonces en la iteración i=2, generando por ello al proceso P7 y debiendo

esperar su finalización. 12) P7 es el único que puede continuar y como ha terminado ya el bucle, acaba su ejecución. 13) Con ello, P6 sale del wait(), terminando su bucle y finalizando su ejecución. 14) Gracias a esto, P2 sale también del wait(), entra en su iteración i=2, genera entonces al proceso P8 y

espera a que acabe. 15) P8 continúa, finalizando su bucle y terminando enseguida. 16) Esto libera a P1, con lo que ya puede también finalizar la ejecución del bucle y terminar.

Sabiendo que el descriptor 0 es el que corresponde a la entrada estándar y que la llamada close()permite cerrar el descriptor cuyo número reciba como argumento escribe el fragmento de código necesario para que un programa tome su entrada estándar del fichero "ejemplo". Impleméntalo sin utilizar dup()ni dup2(). ¿Puede ocasionar algún problema esta implementación de la redirección?

Page 6: Recopilacion 163 Ejercicios de So2

Ej.

1-11

close (0); /* El descriptor cero queda libre. */ open (“ejemplo”, O_RDONLY); /* Open utiliza el descriptor más bajo */ /* y en este caso es el cero. */

Sabiendo que el descriptor 2 es el que corresponde a la salida de error y que la llamada close()permite cerrar el descriptor cuyo número reciba como argumento escribe el fragmento de código necesario para que un programa redirija su salida de error al fichero "ejemplo". Impleméntalo sin utilizar dup()ni dup2(). ¿Puede ocasionar algún problema esta implementación de la redirección?

Ej.

1-12

close (2); /* El descriptor cero queda libre. */ open (“ejemplo”, O_WRONLY | O_CREAT | O_TRUNC, 0666); /* Open utiliza el descriptor más bajo */ /* y en este caso es el cero. */ Este código plantea el problema de que hemos tenido que perder la salida de error actual antes de abrir el fichero que deseábamos asociar a dicha entrada. Con ello, si la posterior llamada open() fallara (ejemplos: por no tener permisos de escritura sobre el directorio, o estar trabajando en un disco montado en modo sólo lectura) habríamos dejado al proceso sin salida de error. La llamada dup() y su variante dup2() solucionan estos problemas.

Indica si las siguientes afirmaciones son verdaderas (V) o falsas (F)

Ej.

1-13

V En la llamada execlp()se busca el programa a ejecutar en todos los directorios de la variable de entorno PATH. Cierto. Tanto con esta llamada como con execvp() el ejecutable se busca en los directorios presentes en PA TH

V Utilizando la llamada waitpid(), el proceso puede averiguar si tiene procesos hijos que no han terminado sin necesidad de suspenderse. Cierto Para ello debe utilizar la opción W NOHANG en el último argumento

V Utilizando la llamada open(), un proceso puede crear un fichero y darle la palabra de protección inicial que considere conveniente. Cierto. Para crearlo debe utilizar la combinación "O WRONLY | O CREAT" en el segundo argumento y el valor que quiera darle a la palabra de protección en el tercero

F Con esta línea de órdenes: (ls –l noestaba * | sort) > uno 2> uno se garantiza que no se perderá ningún carácter de la salida estándar ni de error Lo podremos encontrar todo en el fichero "uno". Falso. Al haber especificado el mismo nombre de fichero en las dos redirecciones, se efectuarán dos aperturas. Cada apertura tendrá su propio puntero de posición. Si hay tanto errores como salida "normal" la parte inicial de la primera de las dos salidas que llegue al fichero se perderá pues la segunda la machacaría.

F Es imposible que dos procesos distintos compartan una misma descripción de apertura Falso. Los procesos hijos heredan los descriptores de fichero que tuvieran sus padres. Con ello, padres e hijos comparten una misma descripción de apertura.

Indica si las siguientes afirmaciones son verdaderas (V) o falsas (F)

Page 7: Recopilacion 163 Ejercicios de So2

Ej.

1-14

F En la llamada execve () se busca el programa a ejecutar en todos los directorios de la variable de entorno PATH. No. Las variantes que buscan el ejecutable en el PATH son las terminadas en "p": execvp() y execlp(). El resto necesitan la ruta completa del ejecutable.

F Utilizando la llamada wait(), el proceso puede averiguar si tiene procesos hijo que no han terminado sin necesidad de suspenderse. No. Para realizar esto debe utilizarse la llamada waitpid() con el flag W_NOHANG

F Utilizando la llamada open(), un proceso puede crear un fichero y darle un propietario diferente a su UID efectivo (es decir al UID del proceso) No. Con esta llamada sí que pueden crearse ficheros, pero éstos tienen como propietario al UID efectivo del proceso creador.

F Resulta imposible abrir dos veces un mismo fichero en un mismo proceso. Sí que es posible. En algunos de los ejemplos vistos en clase ocurría.

V Es posible que dos procesos distintos compartan una misma descripción de apertura Sí que es posible. Al realizar un fork() el proceso hijo hereda una copia de los descriptores del padre y con ello obtiene acceso a las mismas descripciones de apertura.

Dado el siguiente código, indique cuántos procesos serán generados por la ejecución de este programa (incluyendo al original), así como cuál de ellos será el último que terminará. Suponga que ninguna de las llamadas al sistema utilizadas provoca errores y que los procesos generados no reciben ninguna señal.

#include <stdlib.h> int main(void) { int i, pid, pid2; for (i=0; i<4; i++) { pid=fork(); if(pid==0) { pid2=fork(); if (pid2!=0) wait(NULL); break; } wait(NULL); } return 0; }

Ej.

1-15

Se crean 8 procesos más el original, total 9 procesos.

Cada hijo crea un único proceso y tanto los hijos como el proceso que crean hacen break.

El último proceso en finalizar es el original.

Considere la ejecución del siguiente código:

#include <stdio.h> int main() { if (fork() == 0) {sleep(10);} else {sleep(5);} return 0;

Page 8: Recopilacion 163 Ejercicios de So2

} Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F)

Ej.

1-16

F Se genera un proceso hijo zombieV Se genera un proceso hijo huérfanoF Se genera un proceso padre zombieF Se genera un proceso padre huérfanoF No se genera ningún nuevo proceso

Indica si las siguientes afirmaciones son verdaderas (V) o falsas (F)

Ej.

1-17

F Utilizando la llamada open(“archivo”, O_RDWR) el sistema nos retorna dos descriptores uno para lectura y otro para escritura para el fichero “archivo”.

F Un proceso puede crear un fichero con un propietario diferente a su UID efectivo, utilizando la llamada open().

V La llamada open() no debe ser utilizada para abrir un directorio.F Resulta imposible que un proceso ejecute dos veces la llamada open() en su código sobre un mismo

fichero. F Es imposible que dos procesos distintos compartan una misma descripción de apertura V Es posible que al ejecutar la llamada

open(“nuevo”,O_WRONLY|O_CREAT|O_TRUNC,0644) se cree un fichero con la palabra de protección "-rw-r--r--“

Se pide escribir el código para la creación de los procesos según los esquemas descritos en las figuras (a) y (b). El código se realizará utilizando las correspondientes llamadas POSIX y deberá incluir un comentario que indique dónde habría que introducir la llamada “exec” a ejecutar por cada uno de los hijos. Suponiendo que cada proceso hijo realiza la llamada exec correspondiente, comente en cada caso la posibilidad de evitar la aparición de procesos zombie.

Ej.

1-18

(a) #include <unistd.h> int main(void) { int i; for (i =0; i <3; i++) { if (!fork()) { //código del hijo //introducir aquí la llamada exec exit(); // si falla “exec”, el hijo debe terminar. } else { //código del padre } } // de for while ( wait(NULL) != -1 ); } Comentario: no se producen procesos zombie ya que el padre realiza llamadas wait hasta que no queden hijos en ejecución.

padre

hijo2hijo1 hijo3

padre

hijo1

hijo2

hijo3

a) b)padre

hijo2hijo1 hijo3

padre

hijo2hijo1 hijo3

padre

hijo1

hijo2

hijo3

padre

hijo1

hijo2

hijo3

a) b)

Page 9: Recopilacion 163 Ejercicios de So2

Ej.

1-19

(b) #include <unistd.h> int main(void) { int i; if (!fork()) { if (!fork()) { if (!fork()) { //codigo del Hijo 3 //introducir aquí la llamada exec } else { //codigo del Hijo 2 //introducir aquí la llamada exec } } else { //codigo del Hijo 1 //introducir aquí la llamada exec } } else { //codigo del padre wait(NULL); //del padre a hijo 1 } } Comentario: En este esquema de creación de procesos, es posible que aparezcan procesos zombie. El padre puede esperar la finalización de Hijo1, pero como Hijo1 tiene que realizar una llamada exec, es imposible que realice también una llamada wait para esperar la terminación de Hijo2. Lo mismo ocurre entre Hijo2 e Hijo3.

Page 10: Recopilacion 163 Ejercicios de So2

2. Ejercicios sobre ficheros, redirección y tubos

2.1 Dificultad de los ejercicios

Nivel de dificultad Ejercicios Principiante

Medio Avanzado

2.2 Ejercicios Indique ordenada y secuencialmente los pasos necesarios para establecer un mecanismo de comunicación entre dos procesos UNIX, padre e hijo, de manera que: todo lo que el padre escriba en su salida Standard, el hijo lo lea desde su entrada Standard. Ponga en cada paso las primitivas POSIX e instrucciones C necesarias.

Ej.

2-1 Los pasos a realizar para crear un tubo de comunicación entre dos procesos padre e hijo de manera que el padre

escribe y el hijo lee son: 1º) Crear el tubo realizando una llamada pipe() en el proceso padre. int fd[2]; pipe(fd); 2º) Crear un proceso hijo, mediante la llamada fork() que heredara la tabla de descriptores de ficheros. hijo_pid=fork(); 3º) Asignarle a la entrada standard del proceso hijo al descriptor de lectura del tubo y cerrar los descriptores del tubo. if (hijo_pid==0) { dup2(fd[0],STDIN_FILENO); close(fd[0]); close(fd[1]); } 4º) Asignarle a la salida standard del proceso padre al descriptor de escritura del tubo y cerrar los descriptores del tubo. if (hijo_pid>0) { dup2(fd[1],STDOUT_FILENO); close(fd[0]); close(fd[1]); }

Escriba el fragmento de código necesario para que un proceso realice lo siguiente (no necesariamente en el orden listado): 1) Cree un proceso hijo.

Page 11: Recopilacion 163 Ejercicios de So2

2) Se intercomunique con el hijo mediante un tubo, de manera que la salida de error del padre se escriba en el tubo y el hijo lea su entrada estándar de él. 3) El tubo debe ser capaz de devolver la "marca" de fin de fichero al hijo y "enviarle" la señal SIGPIPE al padre si el hijo muriera.

Ej.

2-2

int tubo [2] pipe(tubo) /* Creamos el tubo antes de crear el proceso hijo, para que lo herede. */ if(fork()) { /* Código del padre. Suponemos que no hay fallos. */ dup2(tubo[l], 2); /* Cambiamos la salida de error. */ } else { /* Código del hijo. */ dup2(tubo[0], 0); /* Cambiamos la entrada estándar. */ /* Lo siguiente ya es código común. */ close(tubo[0]); /* Cerramos todos los descriptores del tubo, para cumplir lo pedido en el punto 3. */ close(tubo[1]);

Escriba el fragmento de código necesario para que un proceso realice lo siguiente (no necesariamente en el orden listado): 1 ) Cree dos procesos hijos. 2 ) Los hijos deben intercomunicarse mediante un tubo, de manera que la salida de error de uno de ellos se escriba en el tubo y el otro lea su entrada estándar de él. 3) El tubo debe ser capaz de devolver la "marca" de fin de fichero al proceso lector y "enviarle" la señal SIGPIPE al proceso escritor si el proceso lector muriera.

Ej.

2-3

int fds[2]; fds=pipe(); if(!fork()) /* Creamos un primer proceso hijo. */ dup2(fds[0], 0); /* Este hijo utiliza el descriptor de lectura del tubo como entrada estándar. */ else if(¡fork()) /* Creamos un segundo proceso hijo. */ dup2(fds[l], 2); /* El nuevo hijo utiliza el descriptor de escritura en el tubo como salida de error. */ /* El padre y los dos hijos cerrarán los descriptores que permiten acceder al tubo. Así funcionará lo pedido en el punto 3). */ close(fds[0]); close(fds[l]);

Dado el siguiente código, indica como queda la tabla de descriptores del proceso padre en la línea señalada como B) y en el hijo en la línea señalada como A).

int fd[2], fd1, fde; fde = open(“error.txt”, NEWFILE, MODE644); dup2(fde, STDERR_FILENO); close (fde); pipe(fd); if (fork() == 0) { /*CODIGO DEL HIJO */ dup2 (fd[0], STDIN_FILENO); close (fd[0]); close (fd[1]); fd1 = open(“salida.txt”, NEWFILE, MODE644));

Page 12: Recopilacion 163 Ejercicios de So2

dup2(fd1, STDOUT_FILENO); close (fd1); // ---> A) ESTADO TABLA HIJO ... } else /* CODIGO DEL PADRE*/ dup2 (fd[1], STDOUT_FILENO); close (fd[0]); close (fd[1]); // ---> B) ESTADO TABLA PADRE ... }

Suponer que el estado inicial de la tabla de descriptores es el siguiente:

PADRE [0] /dev/tty0 [1] /dev/tty0 [2] /dev/tty0 [3] - [4] -

Ej.

2-4

PADRE HIJO [0] /dev/tty0 [0] Tubo_r [1] Tubo_w [1] Salida.txt [2] Error.txr [2] Error.txt [3] [3] [4] [4]

Indique si finaliza o no el siguiente programa (y por qué) y qué imprime por pantalla.

int main(){ int i; char leer[100]; int tubo[2]; pipe (tubo); if (fork()==0) { for (i=0; i<10; i++){ write(tubo[1],"datos",6); } } else { while ( read(tubo[0],leer,6) != 0){ printf("%s\n",leer); } } }

Page 13: Recopilacion 163 Ejercicios de So2

Ej.

2-5

Imprime por pantalla 10 líneas con la palabra “datos” cada una. El programa no acaba ya que como el padre no cierra el descriptor tubo[1], el read bloqueante no retorna cuando el hijo muere.

Indique qué valor tendrá el puntero de posición asociado a los diferentes descriptores de fichero utilizados en el siguiente fragmento de programa cuando se haya llegado a su última instrucción. Asuma que no se produce ningún error en ninguna de las llamadas y que ninguna de las funciones utilizadas llega al final de fichero.

int main(void) { int fd1, fd2, fd3, fd4; char buffer[512]; fd1 = open( “ejemplo1”, O_RDWR ); read(fd1, buffer, 100); fd2 = dup(fd1); write(fd1, buffer, 100); fd3 = open( “ejemplo2”, O_WRONLY ); write(fd3, buffer, 40); fd4 = dup(fd2); read(fd4, buffer, 323); … }

Ej.

2-6

fd1: 523 fd2: 523 fd3: 40 fd4: 523 Hay que tener en cuenta que al realizar un dup() estamos duplicando los descriptores, pero todos ellos siguen haciendo referencia a la misma descripción de apertura. Por ello, fd1 , fd2 y fd4 acceden a un mismo puntero de posición, ligado a la única apertura del fichero "ejemplol". Sobre ese fichero se realiza una primera lectura de 100 byte y, una escritura de100 bytes. Posteriormente, a través de fd4, se realiza una lectura de 323 bytes. Como el puntero de posición avanza secuencialmente en cada uno de los accesos, al final la posición pedida será la 523. El descriptor fd3 apunta a la descripción de apertura del fichero "ejemplo2". Sobre ese fichero sólo se efectúa un acceso de escritura donde se transfieren 40 bytes. La posición en el fichero será la 40.

Indique qué valor tendrá el puntero de posición asociado a los diferentes descriptores de fichero utilizados en el siguiente fragmento de programa cuando se haya llegado a su última instrucción. Asuma que no se produce ningún error en ninguna de las llamadas y que ninguna de las funciones utilizadas llega al final de fichero.

int main(void) { int fd1, fd2; char buffer[512]; fd1 = open( “ejemplo1”, O_RDWR ); read(fd1, buffer, 100); fd2 = open( “ejemplo2”, O_WRONLY ); fork(); /* A partir de esta línea hay dos procesos. */ write(fd2, buffer, 40); read(fd1, buffer, 323); ... }

Page 14: Recopilacion 163 Ejercicios de So2

Ej.

2-7

fdl: 746 fdl: 80 "fd1" ya valía 100 antes del fork(). Tras el fork(), tanto el padre como el hijo tienen acceso a la misma descripción de apertura sobre "ejemplo1" con dicha variable. Ambos realizan una lectura de 323 bytes, con lo que el valor final del puntero será 100+323+323=746. "ejemplo2" también había sido abierto antes del fork(), por lo que la escritura que aparece después de él es ejecutada tanto por el padre como por el hijo. Con ello, el puntero de posición vale finalmente 40+40=80.

El siguiente programa genera distintos procesos que acceden a ficheros. Suponemos que no existe problema alguno para abrir dichos ficheros, y que su talla es infinita. Para cada proceso, indica cual será el valor del puntero de posición asociado a cada uno de los descriptores inmediatamente antes de el proceso ejecute exit().

void main(void) { int fd1, fd2, fd3; char buffer[100]; fd1 = open( “exemple”, O_RDONLY ); read(fd1, buffer, 50); fd2 = open( “exemple2”, O_WRONLY ); write(fd2, buffer, 50); fd3 = dup(fd1); read(fd3, buffer, 60); fork(); write(fd2, buffer, 45); read(fd1, buffer, 50); write(fd1, buffer, 20); exit(1); }

Ej.

2-8

Los descriptores fd1 y fd3 apuntan a la misma descripción de apertura y por ello tendrán el mismo valor en su puntero de posición. Además, todos los descriptores se comparten entre los procesos padre e hijo, tras la llamada a fork(). Debido a esto deberán tener el mismo valor para ambos procesos. La llamada a write() sobre fd1 fallará pues ese fichero no fue abierto en un modo que permitiera su escritura. Los punteros de posición tendrán finalmente estos valores: fd1, fd3: 50 (primer read sobre fd1) + 60 (segundo read, sobre fd3) + 50 (tercer read, sobre fd1) + 50 (tercer read, sobre fd1, realizado por el otro proceso) = 210 fd2: 50 (primer write, sobre fd2) + 45 (segundo write, sobre fd2 realizado por el proceso padre o el hijo) + 45 (segundo write, sobre fd2, realizado por el otro proceso) = 140.

Indique de qué forma se puede lograr que dos procesos compartan una misma descripción de apertura (para tener acceso al mismo fichero). Para ello indique qué llamadas al sistema deben utilizarse y qué secuencia debe seguirse (no es necesario dar un ejemplo de código, basta con dar el nombre de las llamadas).

Page 15: Recopilacion 163 Ejercicios de So2

Ej.

2-9

La única forma de lograr que lo compartieran es que uno de los procesos herede tal descripción de apertura del otro proceso. Por ello, en primer lugar deberemos crear una descripción de apertura y después habrá que generar un proceso hijo. Para crear la descripción de apertura habrá que crear o abrir un fichero mediante la llamada al sistema open(). La llamada al sistema dup() no sirve para esto, pues genera un nuevo descriptor para acceder a una descripción ya existente, pero no crea ninguna descripción nueva. Para generar un proceso hijo, basta con utilizar fork().

Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F)

Ej.

2-10

F Si un proceso cierra su entrada, salida y salida de error estándares ya no puede te acceso a la terminal. No tiene por qué haberlo perdido para siempre. Puede abrir el fichero especial correspondiente ("/dev/tty" o el que sea) más tarde, si es que tiene permisos. Otra opción habría sido realizar un dup() o dup2() para conservar el acceso en otros descriptores, antes de efectuar el cierre.

F Las llamadas necesarias para leer o escribir en la Terminal no son las mismas que la que deben emplearse para leer o escribir ficheros. Deben ser las mismas, en caso contrario no tendría ningún sentido todo lo que se ha explicado sobre las redirecciones de la entrada, salida v salida de error estándares.

F No hay forma de que un proceso pueda tener la misma descripción de apertura asociada a la entrada y salida de error estándar. Sí que puede conseguirse, utilizando "dup2(2,0)" por ejemplo. No tendría excesivo sentido hacerlo pero sí que es posible.

V En ciertos casos, escribir en un tubo puede causar que el sistema operativo envíe una señal a ese proceso escritor. Sí, se llegaría a enviar SIGPIPE si no hubiera ningún descriptor que permitiera la lectura de la información existente en el tubo

F Un proceso siempre se suspende al utilizar la llamada read () sobre un fichero puesto que dicha petición siempre se traduce en una operación de E/S sobre el disco. No tiene por qué suspenderse. La caché de bloques que mantiene el sistema operativo puede evitar la suspensión en muchos casos

Dado el siguiente código en el cual se generan al menos tres procesos P1, P2 y P3:

... pipe(fd); pipe(fd2); if(fork() != 0){ /***Proceso P1 ***/ dup2(fd[1],STDOUT_FILENO); close(fd[0]); close(fd[1]); dup2(fd2[0],STDIN_FILENO); close(fd2[0]); close(fd2[1]); }else{ /***Proceso P2 ***/ dup2(fd[0],STDIN_FILENO); close(fd[0]); close(fd[1]);

pipe(fd3); if(fork() != 0){ close(fd2[0]); close(fd2[1]); dup2(fd3[1],STDOUT_FILENO); close(fd3[0]); close(fd3[1]); }else{ /***Proceso P3 ***/ dup2(fd3[0],STDIN_FILENO); close(fd3[0]); close(fd3[1]); dup2(fd2[1],STDOUT_FILENO); close(fd2[0]); close(fd2[1]); } } ...

Indique para los procesos P1, P2 y P3, el contenido, después de la ejecución de este código, de sus respectivas tablas de descriptores de archivo, la relación existe entre ellos y cuál es el esquema de comunicación resultante.

Page 16: Recopilacion 163 Ejercicios de So2

Ej.

2-11

En el código presentado se están creando tres tubos, a los que llamaremos T1 (asociado al vector de descriptores fd), T2 (asociado al vector fd2) y T3 (con el vector fd3). También se muestra claramente que se creará un total de 3 procesos. Las tablas de descriptores serían: P1 P2 P3 0: T2(lect) T1(lect) T3(lect) 1: T1(escr) T3(escr) T2(escr) 2: heredado heredado heredado Donde «heredado» indica que tal descriptor no ha sido “redirigido” y que sigue manteniendo el fichero que tenía asociado el proceso padre de P1. La relación existente entre los tres procesos es: P1 es el padre de P2 y P2 es el padre de P3.

El esquema de comunicación resultante es una especie de “anillo” entre P1, P2 y P3, como puede deducirse de la tabla de descriptores anterior. Así, T1 sirve para pasar la salida estándar de P1 a la entrada estándar de P2, T3 pasa la salida estándar de P2 a la entrada estándar de P3 y T2 pasa la salida estándar de P3 a la entrada estándar de P1.

Diga qué imprimirán por la salida estándar cada una de las órdenes que se detallan a continuación, suponiendo que previamente se ha ejecutado con éxito la orden $ echo hola > hola:

1. $./a.out cat 2. $./a.out hola

siendo a.out el ejecutable correspondiente al siguiente código en C: main(int argc, char **argv) { int fd[2], f1, i; char str[80]="adios"; pipe(fd); if (fork()) { dup2(fd[1], STDOUT_FILENO); close(fd[0]); close(fd[1]); f1 = open("hola", O_RDONLY); dup2(f1, STDIN_FILENO); if (execvp(argv[1], &argv[1]) == -1) printf("El proceso 1 ha cometido un error\n"); } else { dup2(fd[0], STDIN_FILENO); close(fd[0]); close(fd[1]); for(i=0; (str[i] =getchar()) != EOF && i<80; i++); str[i-1]='\0'; printf("Soy el proceso p2 y digo: %s\n", str); } }

NOTA: la función getchar() devuelve el siguiente carácter que lee de la entrada estándar.

Page 17: Recopilacion 163 Ejercicios de So2

Ej.

2-12

1: El código presentado trata de abrir en modo lectura el fichero “hola” y dejarlo como entrada estándar para el proceso padre. Éste ejecutará la orden facilitada como primer argumento al programa. Además, se crea un proceso hijo. Mediante un tubo se consigue pasar la salida del proceso padre a la entrada del proceso hijo. Este proceso hijo, almacena dicha entrada en la cadena “str” (que puede guardar un máximo de 80 caracteres) y posteriormente la imprime en el mensaje “Soy el proceso p2 y...”.

En este primer ejemplo, el programa a ejecutar en el proceso padre será un “cat” por lo que el hijo, al final, obtendrá en su cadena “str” el contenido del fichero “hola”, que es justamente esa misma palabra. Por tanto, la salida obtenida será:

Soy el proceso p2 y digo: hola

2: En este caso se pasa como argumento la cadena “hola”, pero tal fichero no es un ejecutable. Por ello, el proceso padre fallará al intentar ejecutar tal fichero. Debido a esto escribirá en su salida estándar la cadena “El proceso 1 ha cometido un error”. Esta cadena será recibida en la entrada estándar del hijo. Por tanto, al final se escribirá en pantalla:

Soy el proceso p2 y digo: El proceso 1 ha cometido un error

Imagine que en un determinado sistema operativo se facilita una llamada (por ejemplo, int crear_proceso(char *nombre_programa, char *argumentos[]); ) que de manera atómica genera un proceso hijo y, si no hay errores para localizar y ejecutar el fichero, consigue que este nuevo proceso ejecute el programa suministrado en el primer parámetro con los argumentos facilitados en el segundo. En esta llamada, el proceso hijo hereda todos los descriptores de fichero que pudiera tener el proceso padre. Indique qué ventajas o inconvenientes plantea esta aproximación frente a la empleada en POSIX, a la hora de implantar las redirecciones y tuberías.

Ej.

2-13

En POSIX, las redirecciones y tuberías se suelen implantar una vez se ha creado el proceso hijo mediante fork(), heredando todos los descriptores del padre y antes de efectuar el exec() para cambiar el programa que ejecutará el nuevo proceso hijo. Con ello no hay que modificar para nada la tabla de descriptores que poseía el padre y tampoco hay que hacer nada especial en el programa que finalmente ejecutará el hijo.

Con la llamada que se comenta, el padre estaría obligado a:

1) Duplicar sus descriptores estándar en otras entradas para poderlos recuperar posteriormente.

2) Modificar sus descriptores estándar para que tengan los valores que espera el hijo para implantar sus redirecciones o tuberías.

3) Llamar a crear_proceso() para generar el proceso hijo con las redirecciones apropiadas.

4) Recuperar los descriptores estándar del padre almacenados en el paso 1.

No es excesivamente complicado hacer esto, pero resulta bastante más cómodo hacerlo con el esquema propio de POSIX. Por tanto, únicamente implicaría el inconveniente de necesitar más código en el programa del proceso padre.

Se pretende implantar un esquema entre dos procesos (Proceso padre y Proceso hijo) como el que se muestra en la figura siguiente:

Page 18: Recopilacion 163 Ejercicios de So2

Proceso hijo

tuboProceso padre /tmp/file.txt

Proceso hijo

tuboProceso padre /tmp/file.txt

Proceso hijoProceso hijo

tuboProceso padreProceso padre /tmp/file.txt

Donde el proceso padre redirige su salida al fichero /tmp/file.txt. Para ello se propone el siguiente código: ... pipe(fd); if(fork() == 0){ dup2(fd[1],STDOUT_FILENO); close(fd[0]); close(fd[1]); /* resto de codigo */ }else{ dup2(fd[0],STDIN_FILENO); close(fd[0]); close(fd[1]); fd_open=open("/tmp/file.txt",O_WRONLY|O_TRUNC|O_CREAT,0600); /* resto de codigo */ } ... Suponiendo que el resto de código no altera la redirección de la E/S de los procesos, indique si el código es correcto o no. En caso de no serlo escriba las modificaciones que son necesarias realizar en el código para que lo sea.

Ej.

2-14

else{ fd_open=open("/tmp/file.txt",O_WRONLY|O_TRUNC|O_CREAT,0600); dup2(fd[0],STDIN_FILENO); dup2(fd_open,STDOUT_FILENO); close(fd[0]); close(fd[1]); close(fd_open); /* resto de codigo */ }

Dada la siguiente línea de ordenes del shell:

$ cat fich1 | sort | wc –l > result a) Indique el número de procesos y ficheros que intervienen en la misma y el contenido de las tablas de

descriptores de cada proceso.

Ej.

2-15

3 proc 2 fich + 2 tubos cat: 0:/tty/console; 1:tubo1[1]; 2:/tty/console; sort: 0:tubo1[0]; 1:tubo2[1]; 2:/tty/console; wc: 0:tubo2[0]; 1:result; 2:/tty/console;

b) Escriba el código, en POSIX y C, necesario para que se lleve a cabo la comunicación entre los procesos

que intervienen en dicha línea de órdenes. Utilice las siguientes llamadas: execlp(“/bin/cat”, “cat”, “fich1”,NULL); execlp(“/bin/sort”, “sort”,NULL); execlp(“/usr/bin/wc”, “wc”, “l”,NULL);

Page 19: Recopilacion 163 Ejercicios de So2

E

j. 2-

16

{int tubo0[2], tubo1[2]; int fd; pipe(tubo0); pipe(tubo1); if (!(pid=fork())) { dup2(tubo0[1],1); close(tubo0[0]); close(tubo0[1]); close(tubo1[0]); close(tubo1[1]); execlp("/bin/cat", "cat", "fich1", NULL); } if(!(pid=fork())){ dup2(tubo0[0],0); dup2(tubo1[1],1); close(tubo0[0]); close(tubo0[1]); close(tubo1[0]); close(tubo1[1]); execlp("/bin/sort", "sort", NULL); } if(!(pid=fork())){ dup2(tubo1[0],0); fd=open(“result”,O_WRONLY |O_CREAT|O_TRUNC,0666); dup2(fd,1); close(tubo0[0]); close(tubo0[1]); close(tubo1[0]); close(tubo1[1]); close(fd); execlp("/usr/bin/wc", "wc", "-l",NULL); } close(tubo0[0]);close(tubo0[1]); close(tubo1[0]);close(tubo1[1]); while(pid != wait(&status)); }

Completa el siguiente programa para que ejecute el mandato “ls –la”, redirigiendo la salida estándar al fichero ‘listado.txt’. Para ello utiliza las variables ‘Fichero’ y ‘Comando’.

#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #define NEWFILE (O_WRONLY | O_CREAT | O_EXCL) #define MODE644 (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) char *Fichero = "listado.txt"; char *Comando[2] = { "ls", "-la" }; main(int argc, char *agrv[]) {

int fd; /********** COMPLETAR ************/ }

Page 20: Recopilacion 163 Ejercicios de So2

Ej.

2-17

#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #define NEWFILE (O_WRONLY | O_CREAT | O_EXCL) #define MODE644 (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) char *fitxer = "llistat.txt"; char *comandament[3] = { "ls", "-la", NULL }; main(int argc, char *argv[]) { int fd; if ((fd = open(fitxer, NEWFILE, MODE644)) == -1) { fprintf(stderr, "No es pot crear: %s\n", fitxer); exit(1); } // Creació de nou fitxer if (dup2(fd, STDOUT_FILENO) == -1) { fprintf(stderr, "No es pot redirigir a: %s\n", fitxer); exit(1); } close (fd); //Eixida estàndard redirigida a fitxer if (execvp(comandament[0], &comandament[0]) < 0) { fprintf(stderr, "No es pot executar: %s\n", comandament[0]); exit(1); } // Comandament llençat }

Considere el código de la figura, cuyo correspondiente fichero ejecutable se denomina a.out

main(int argc, char *argv[]) { pipe(tubo0); pipe(tubo1); if (!(pid=fork())) { dup2(tubo0[1],1); execlp("/bin/echo", "echo", "hola", NULL); } if(!(pid=fork())){ dup2(tubo0[0],0); dup2(tubo1[1],1); execlp("/bin/sort", "sort", NULL); } if(!(pid=fork())){ dup2(tubo1[0],0); execlp("/usr/bin/wc", "wc", NULL); } close(tubo0[0]); close(tubo0[1]); close(tubo1[0]); close(tubo1[1]); while(pid != wait(&status)); }

Page 21: Recopilacion 163 Ejercicios de So2

La ejecución de dicho código pretende ser equivalente a la ejecución en el shell de: $ echo hola | sort | wc Sin embargo, al ejecutar a.out se observa que este proceso no termina (el shell no devuelve el prompt) y que su ejecución no proporciona ningún resultado por la salida estándar. Se observa, además, que después de lanzada la ejecución de a.out permanecen sin acabar los siguientes procesos en el sistema:

PID TTY TIME CMD 17466 pts/4 00:00:00 a.out

17468 pts/4 00:00:00 sort 17469 pts/4 00:00:00 wc

a) Indique de forma concisa el motivo por el que a.out no termina y diga las modificaciones necesarias que se han de introducir en el código para que el resultado de la ejecución sea el esperado.

Ej.

2-18

El proceso a.out no termina porque está esperando la terminación de los procesos sort y wc. Estos procesos no terminan porque la lectura desde su entrada estándar esta redirigida desde los tubos, y los procesos están suspendidos esperando en la operación de lectura (no se produce EOF). La causa de esto es que la tubería no está bien organizada: existen procesos que tienen descriptores de escritura abiertos sobre el tubo, aunque no los utilizan. Así por ejemplo, los procesos sort y wc son escritores potenciales de tubo0 y tubo1, pues no han cerrado sus descriptores de fichero para escribir. La solución es que los procesos hijos de a.out cierren los descriptores que no utilizan sobre el tubo. Es decir invocar: close(tubo0[0]);close(tubo0[1]);close(tubo1[0]); close(tubo1[1]); antes de execlp;

b) En la situación expuesta en el enunciado y con el código descrito:

b1) Razone si al ejecutar la orden $kill -9 17468 se produciría la señal SIGPIPE. b2) Si en vez de la orden anterior se ejecuta la orden $kill -9 17466 , indique si quedarían en el sistema procesos zombies de forma permanente. Razone la respuesta.

Ej.

2-19

b1) No. La señal SIGPIPE se genera cuando un proceso intenta escribir en un tubo sin ningún descriptor de lectura abierto. Al matar al proceso sort, el tubo1 sigue teniendo como lector y escritor a wc. Con el código correcto se hubiera producido la señal SIGPIPE al matar el proceso wc e intentar escribir sort sobre el tubo. Si el proceso echo no hubiese terminado de escribir al recibir sort la señal SIGPIPE, nuevamente la recibiría echo, terminando así en cadena todos los procesos de la tubería

b2) No quedan zombies. Quedan procesos huérfanos de forma permanente. El padre de estos procesos ha muerto, pero los procesos no han invocado exit, pues se encuentran suspendido esperando leer del tubo. Con el código correcto, no quedarían zombies permanentes, pues si el padre muriese antes de ejecutar wait, INIT adoptaría e invocaría wait.

Page 22: Recopilacion 163 Ejercicios de So2

Justifique si finalizan o no y en que estado se encontrarían cada uno de los procesos (padre e hijo) que se crearían al ejecutar los códigos representados en la tabla (CODIGO_A y CODIGO_B). Diga que mensajes se imprimirían en la pantalla.

//*****CODIGO_A**************// int main() {int i; char leer[100]; int tubo[2]; pipe (tubo); if (fork()==0) {for (i=0; i<10; i++) { write(tubo[1],"HIJO",5); } close(tubo[1]); close(tubo[0]); } else { close(tubo[1]); while( read(tubo[0],leer,5)!= 0) printf("%s\n",leer); while (wait(NULL)>0); } }

//*****CODIGO_B**************// int main() {int i; char leer[100]; int tubo[2]; pipe (tubo); if (fork()==0) {for (i=0; i<10; i++) { write(tubo[1],"HIJO",5); } } else { while(read(tubo[0],leer,5)!= 0) printf("%s\n",leer); close(tubo[1]); close(tubo[0]); while (wait(NULL)>0); } }

Ej.

2-20

A) //***********CODIGO_A**********// En este código el hijo escribe en el tubo 10 veces la palabra “HIJO” y se quedaría en estado zombie hasta que su padre haga wait. El padre imprime por pantalla 10 veces la palabra “HIJO”, hace wait y termina.

B) //***********CODIGO_B**********// En este código el hijo escribe en el tubo 10 veces la palabra “HIJO” y se quedaría en estado zombie hasta que su padre haga wait. En este código el padre lee todo lo que el hijo ha escrito en el tubo y sigue intentando leer. Como no se ha cerrado el descriptor de escritura del tubo el padre no recibe la señal de EOF y por tanto se encuentra suspendido en la llamada read que es bloqueante.

Se pide escribir un programa que realice las llamadas a sistema necesarias para ejecutar la siguiente línea de órdenes: ls|wc Nota: Suponga que no se producen errores en las llamadas al sistema y que los ejecutables ls y wc existen en el sistema y son accesibles a través de la variable de entorno PATH.

Ej.

2-21

int main(){ int tubo[2]; pipe(tubo); If (fork()){ dup2(tubo[1],STDOUT_FILENO); close(tubo[0]); close(tubo[1]); execlp("ls","ls",NULL); } else { dup2(tubo[0],STDIN_FILENO); close(tubo[0]); close(tubo[1]); execlp("wc","wc",NULL); } return 0; }

Page 23: Recopilacion 163 Ejercicios de So2

3. Ejercicios sobre señales

3.1 Dificultad de los ejercicios

Nivel de dificultad Ejercicios Principiante

Medio Avanzado

3.2 Ejercicios Indique qué funciones POSIX permiten enviar señales a un proceso, a cuántos procesos pueden enviar tales funciones una señal a la vez (explicar mediante qué parámetros se puede especificar esto y de qué forma) y cuándo tiene lugar el envío

Ej.

3-1

Las dos únicas funciones que permiten enviar señales a los procesos son kill() y alarm(). Con el primer parámetro de kill() se puede especificar si la señal debe ser enviada a solo un proceso (valor positivo), a todos los procesos del grupo del emisor (valor cero), a todos los procesos (valor -1) o a todos los procesos de un grupo determinado (otros valores negativos). Utilizando esta función, la señal se envía de inmediato. Con alarm() la señal a enviar será la S1GALRM y el envío se producirá cuando hayan transcurrido los segundos especificados en el único parámetro que necesita esta función. Dicha señal sólo se envía al propio proceso que ha utilizado la llamada.

Indique qué funciones POSIX modifican la máscara de señales de un proceso, de qué manera (permanente o temporal) y cuándo tiene lugar la modificación.

Ej.

3-2

sigprocmask() es la función a utilizar para modificar permanentemente la máscara de bloqueo de señales que tiene un proceso. La modificación tiene lugar desde el preciso instante en que se invoque y se mantendrá hasta que vuelva a usarse esta misma función (salvo los intervalos en los que esté activa la máscara de sigsuspend() o de sigaction(). sigaction() permite especificar el tratamiento de señales que se llevará a cabo para una determinada señal. Si el tratamiento a utilizar es la instalación de una función manejadora, también existe la posibilidad de utilizar el campo "sa_mask" de la estructura "sigaction" para indicar las señales que se añadirán a la máscara "oficial" durante la ejecución de la función manejadora. Por tanto, esta nueva máscara tiene un duración breve (no es permanente). sigsuspend() permite especificar la máscara a utilizar mientras el proceso que solicite esta función permanezca suspendido. Tan pronto como llegue alguna de las señales no bloqueadas en esa máscara temporal, el proceso abandona el estado de suspensión (bien cambiando a preparado para ejecutar el manejador o bien muriendo) y si continúa activo recupera la máscara anterior.

Conteste de forma concreta y concisa a las siguientes preguntas sobre señales:

a) ¿Qué es una señal?

Page 24: Recopilacion 163 Ejercicios de So2

b) ¿Qué mecanismos pueden desencadenar la producción de una señal? c) ¿Cómo puede impedir temporalmente un proceso que el Sistema Operativo le notifique la ocurrencia de

una señal? d) ¿Qué es un manejador? ¿Cómo se instala un manejador? ¿Qué tipo de comportamiento se pueden

asociar a una señal?

Ej.

3-3

a) Una señal es la notificación por software de que ha ocurrido un evento. b) La generación de señales puede ser: c) Desde el Terminal. Ejemplo CTRL-C

• Por error en ejecución. Ejemplo: División por cero SIGFPE. • Por software. Ejemplo: alarm(10), kill(numero proceso, numero de señal) • Para impedir temporalmente el depósito de una señal esta debe enmascararse. Para ello se

ha de trabajar con la máscara definiéndola (sigemptyset(), sigfillset(), sigaddset()) e instalando la máscara (sigprocmask()).

d) Un manejador es el código que se ejecuta como respuesta a una señal, para ello hay que instalarlo previamente mediante la llamada sigaction(). La llamada sigaction() tiene un parámetro que es el manejador a instalar para una determinada señal que también pasamos como parámetro. Este manejador pude ser: el de defecto(SIG_DFL) que normalmente finaliza el proceso, el de ignorar la señal SIG_ING, o el código de una función.

Escriba el fragmento de código necesario para que un proceso pueda modificar su máscara actual de bloqueo de señales realizando lo siguientes cambios: desbloquear la señal SIGHUP y bloquear la señal SIGILL.

Ej.

3-4 /* Primera alternativa: */

sigset_t mascara /* Primero debemos crear una máscara donde sólo aparezca SIGHUP. */ sigemptyset(&mascara ); sigaddset(&mascara, SIGHUP); /* Ahora eliminamos las señales de esa máscara de la que tenga el proceso. */ Sigprocmask(SIGUNBLOCK, &mascara, NULL); /* Repetimos el trabajo para SIGILL, pero ahora utilizando la acción SIGC */ sigemptyset( &mascara ); sigaddset(&mascara, SIGILL) sigprocmask(SIGBLOCK &mascara NULL) /* Segunda alternativa: Partir de la máscara actual. */ sigset_t mascara; ... /* La acción no importa, queremos obtener la máscara actual. */ sigprocmask(SIG_BLOCK, NULL, &mascara); /* Ahora, eliminamos SIGINT. */ sigdelset(&mascara, SIGINT); /* Y añadimos las que hay que bloquear. */ sigaddset(&mascara, SIGHUP); sigaddset(&mascara, SIGILL); /* Finalmente, reinstalamos la máscara que acabamos de construir.*/ sigprocmask(SIG_SETMASK, &mascara, NULL); ... /* Otra solución consistiría en haber construido una máscara con solo SIGINT activo, utilizando sigprocmask() con la acción SIG_UNBLOCK. Tras esto, hay que construir otra máscara con SIGHUP y SIGILL activos, utilizando entonces sigprocmask() con la acción SIG_BLOCK. */

Page 25: Recopilacion 163 Ejercicios de So2

Escriba el fragmento de código necesario para que un proceso pueda instalar a la función manejador (que se supone definida e implementada en otra parte del programa) como manejadora de la señal SIGHUP y también que pase a ignorar cualquier ocurrencia de la señal SIGINT.

Ej.

3-5

struct sigaction act; /* Instalamos en primer lugar la función manejadora para SIGHUP */ act.sa_handler = mane]ador; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGHUP, &act, NULL); /* Ahora, ignoramos SIGINT */ act.sa_handler = SIG_IGN; sigaction(SIGINT,&act,NULL);

Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F)

Ej.

3-6

F Realizar una lectura en un tubo puede originar que el sistema operativo envíe la señal SIGPIPE al proceso lector. No. Esa señal sólo puede ser generada para un proceso que intente escribir en un tubo que no tenga ningún lector.

F Un proceso puede tener bloqueada la entrega de todas las señales existentes. Las señales SIGKILL y SIGCONT no pueden bloquearse.

F Cuando un proceso envía una señal SIGKILL a otro, el sistema operativo nos garantiza SIEMPRE que el destinatario de dicha señal morirá. Puede que el proceso especificado como destino de esa señal no exista o pertenezca a otro usuario. En ambos casos, fallaría.

F Mediante la llamada al sistema sigsuspend() se puede solicitar que algunas señales sean ignoradas mientras dure la espera. No. Lo que puede hacerse es añadir ciertas señales a la máscara de bloqueo. No es lo mismo ignorar una señal que bloquearla

V Existen señales que no pueden ser ignoradas, bloqueadas, ni tener una función manejadora asociada. SIGKILL y SIGCONT no pueden ser ignoradas, bloqueadas, ni tener una función manejadora. Sólo pueden tener su tratamiento por omisión (matar al proceso en el caso de SIGKILL y hacer que continúe en el caso de SIGCONT)

Explica las diferencias que pueden haber entre las siguientes formas de terminar un proceso POSIX. Si algunas formas son equivalentes razona por qué producen el mismo resultado. Nota: Considera la equivalencia entre soluciones en términos de los valores que recibe la llamada wait que ejecuta el proceso padre cuando termina el proceso en cuestión.

a) b) c) d) int main(void){ codi(); }

int main(void){ codi(); exit(0); }

int main(void){ codi(); return 0; }

int main(void){ codi(); kill(9,getpid()); }

Page 26: Recopilacion 163 Ejercicios de So2

Ej.

3-7

No hay diferencia entre las variantes a), b) y c) ya que todas ellas terminan haciendo una llamada a sistema exit con el valor 0 como parámetro. Esto es debido a que la primera instrucción de un programa escrito en C no es el main(), sino que se encuentra en la biblioteca del sistema. Desde allí se llama a main() y cuando este retorna, la propia biblioteca se encarga de realizar la llamada exit. La variante d) también garantiza que el proceso terminará, pero no de manera voluntaria sino por la llegada de la señal 9, que puede enmascararse ni tratarse. En este caso el valor de retorno obtenido por el proceso padre reflejará que el proceso ha terminado debido a la recepción de una señal.

Dado el programa señal.c cuyo código es el siguiente y que mantienen el tratamiento por omisión o defecto para todas las señales:

señal.c #include <stdlib.h> #include <stdio.h> #include <unistd.h> int main (int argc, char *argv[]) { alarm(5); for ( ; ; ); }

Indique de manera justificada cual será su tiempo aproximado de ejecución.

Ej.

3-8

La llamada al sistema “alarm()” programa el envío de una señal SIGALRM cuando hayan transcurrido los segundos especificados en su único parámetro. Como este proceso no realiza ningún tratamiento para esa señal y por omisión su efecto es terminar el proceso, el proceso mostrado terminaría al cabo de aproximadamente cinco segundos de haberse iniciado.

Obsérvese también que tras la llamada a “alarm()”, el programa presenta un bucle infinito, por lo que si no se hubiera enviado esa u otra señal, el proceso no terminaría.

Contesta las siguientes cuestiones relativas a las señales:

a) Explica qué diferencia existe entre enmascarar una señal y tratarla mediante una función b) ¿Podemos mantener una misma señal enmascarada y fijar una función para su tratamiento? De ser así,

¿Cuál es el comportamiento de esta combinación? Si no resulta posible, explica por qué.

Ej.

3-9

a) Enmascarar una señal significa evitar la notificación de su ocurrencia al sistema operativo, impidiendo así cualquier tratamiento por parte de éste. Tratar una señal significa asociar a la señal una función, la cual se ejecutará en cuanto se produzca una ocurrencia no enmascarada, o cuando se desenmascare una ocurrencia anterior. b) Sí que se puede. El comportamiento es que el manejador se ejecutará cuando se desenmascare (la señal se memoriza) si es que esto llega a ocurrir en algún momento.

Page 27: Recopilacion 163 Ejercicios de So2

Dado el siguiente programa POSIX, indique cuál es la salida que producirá por pantalla al ser ejecutado. Justificar la respuesta indicando los pasos que ha seguido la ejecución del proceso.

#include <sys/types.h> #include <unistd.h> #include <signal.h> void handle_signal(int signo) { if (signo==SIGINT) printf("Se ha recibido una señal SIGINT\n"); else printf("Se ha recibido otra señal\n"); } main() { pid_t val; int status; struct sigaction act; act.sa_handler = handle_signal; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGUSR1, &act, NULL); if (val = fork()) { while(wait(&status)!=val) printf("Espera no finalizada\n"); } else { kill(getppid(),SIGUSR1); sleep(1); exit(0); } }

Ej.

3-10

El programa empieza cambiando el tratamiento de la señal SIGUSR1, instalando la función “handle_signal()” como manejadora de ésta. Posteriormente se genera un proceso hijo, el cual enviará dicha señal a su proceso padre, esperará aproximadamente un segundo y posteriormente terminará. El proceso padre entra en un bucle de llamadas a la función “wait()” que concluirá cuando el proceso hijo haya terminado. En dicho bucle, se escribirá el mensaje “Espera no finalizada” si wait() ha fallado por algún motivo. De esta manera, cuando el proceso hijo envía la señal a su padre, éste es probable que se encuentre ya suspendido esperando la finalización del hijo. De ser así, la llamada wait() será interrumpida y pasará a ejecutarse el manejador. El manejador escribirá el mensaje “Se ha recibido otra señal”. Como consecuencia de la interrupción de la espera, wait() termina con error, por lo que el proceso padre también escribe “Espera no finalizada”. En la siguiente iteración de ese bucle de espera, el proceso padre volverá a quedar suspendido, hasta que el hijo concluya también su pausa de 1 segundo y termine. Esta segunda vez, el proceso padre ya no escribirá nada, pues el valor devuelto por wait() habrá coincidido con el PID de su proceso hijo. Otra opción es que, debido al algoritmo de planificación que utilice el núcleo del sistema operativo, al realizar el fork(), el proceso hijo expulse al padre (porque terminara su quantum o por ser el hijo más prioritario y emplear prioridades expulsivas, por ejemplo), envíe la señal SIGUSR1 y el padre todavía no se haya suspendido en el wait(). Si ocurriera así, el padre sólo escribiría “Se ha recibido otra señal”. Posteriormente llegaría al wait(), se suspendería y sólo saldría de este estado cuando el hijo terminara. Por tanto, los mensajes presentados serán: Se ha recibido otra señal. Espera no finalizada. O bien sólo el primero de estos dos mensajes.

Page 28: Recopilacion 163 Ejercicios de So2

Dado el siguiente código: char buffer[80] = ""; char buffer2[80] = “NADA”; struct sigaction act; void manejador(int signo) { int fd; fd=open("f", O_WRONLY | O_CREAT, 0666); dup2(fd,1); strcpy(buffer, "Error"); fprintf(stderr,"Resultado: %s\n", buffer); }

main() { int i=0; int n=0; act.sa_handler = manejador; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGALRM, &act, NULL); alarm(15); for (i=0; (n=read(0,&buffer2[i],1)) >0 && buffer2[i] != '\n' && i<80 ; i++); if (n>0) buffer2[i+1]='\0'; printf("La cadena leida es: %s\n", buffer2); }

Indicar cuál será el resultado de ejecutar el programa correspondiente si éste no recibe nada en su entrada estándar durante 15 segundos. Describa la secuencia de ejecución del programa, detallando el efecto de las llamadas a sistema, si el proceso acaba, el motivo de la posible terminación (terminación normal / anormal) y todas las salidas de texto que proporciona.

Ej.

3-11

1) Se instala un manejador para SIGALRM 2) El proceso se suspende en la llamada read 3) Al cabo de 15 seg. se produce SIGALRM 4) Se ejecuta el manejador de SIGALRM que redirige la salida estándar al fichero “f” 5) El manejador escribe “Resultado: Error” en la salida estándar de error. 6) La llamada read retorna con un error (EINTR) 7) Se escribe en el fichero “f”: "La cadena leída es: Error”

Dado el siguiente ejemplo en POSIX

#include <unistd.h> #include <signal.h> int a = 16, x = 3 ; void manejador(int senyal ) { a = a - 2; }

int main( void ) { struct sigaction act; act.sa_handler = manejador; sigemptyset(&act.sa_mask ); act.sa_flags = 0; sigaction(SIGQUIT, &act, NULL ); while ( a > 0 ) a = a + x - 3; return 0; }

¿Cuántas señales SIGQUIT habría que enviarle al proceso resultante, tras haber ejecutado el sigaction() para asegurar que terminase? ¿Puede haber condiciones de carrera?

Page 29: Recopilacion 163 Ejercicios de So2

Ej.

3-12

Como mínimo 8, pues con cada señal, el valor de la variable global a se reduce dos unidades y el proceso empezaba con "a" igual a 16. Sin embargo, con 8 no tenemos una garantía total de que la variable "a" haya llegado al valor cero, permitiendo la salida del bucle "while" contenido en la función principal. Esto se debe a que pueden darse condiciones de carrera, pues tanto esa función principal como la función manejadora de la señal asignan valores a la variable "a". Si la llegada de la señal se produjera en un momento inoportuno podría ocurrir que el decremento dado en esa ejecución de la función manejadora se perdiera al retornar a la función principal. Para ello bastaría con que la interrupción en la secuencia de la función principal se hubiera dado tras pasar el valor de "a" a un registro del procesador, pero antes de dejarlo en la posición de memoria asociada a dicha variable. Al recuperar el control tras la ejecución de la función manejadora, se perdería el cambio realizado en esa función manejadora

Sea el siguiente código:

struct sigaction act; int fd[2]; void manejador(int signo){ close(fd[1]); close(fd[0]); } main(int argc, char *argv[]) { char c; act.sa_handler = manejador; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGINT, &act, NULL);

/*sigue el main */ pipe(fd); if (fork()) { /* PADRE */ fprintf(stderr, "Padre: esperando\n"); wait(NULL); fprintf(stderr, "Padre: terminando\n"); } else { /* HIJO */ dup2(fd[1],1); fprintf(stderr, "Hijo: leyendo\n"); read(fd[0],&c,1); fprintf(stderr, "Hijo: escribiendo\n"); while(1) write(1,&c,1); } exit(1); }

Dada la lista de afirmaciones que más bajo se relaciona, realice una lista ORDENADA con todas las afirmaciones que son ciertas para los procesos PADRE e HIJO. El orden debe corresponderse con el orden de ejecución. Realice dicha lista en el siguiente supuesto:

a) Se ejecuta el programa y, después de imprimirse “Padre: esperando”, “Hijo: leyendo” se teclea <CTRL.-C>

AFIRMACIONES RELATIVAS AL PADRE

P1.- El proceso PADRE termina normalmente al ejecutar exit P2.- El proceso PADRE queda suspendido en estado WAITING al ejecutar wait P3.- El proceso PADRE muere por una señal (acción por omisión) P4.- El proceso PADRE pasa del estado WAITING al estado ACTIVO P5.- El proceso PADRE ejecuta wait sin suspenderse P6.- El proceso PADRE maneja la señal SIGPIPE P7.- El proceso PADRE maneja la señal SIGINT P8.- El proceso PADRE ignora la señal SIGINT

AFIRMACIONES RELATIVAS AL HIJO

H1.- El proceso HIJO termina normalmente al ejecutar exit H2.- El proceso HIJO queda HUÉRFANO ejecutando un bucle infinito (INIT lo adopta) H3.- El proceso HIJO queda suspendido en estado READING al ejecutar read H4.- El proceso HIJO queda suspendido en estado WRITING al ejecutar write

Page 30: Recopilacion 163 Ejercicios de So2

H5.- El proceso HIJO pasa del estado suspendido (READING) al estado ACTIVO H6.- El proceso HIJO provoca la señal SIGPIPE al ejecutar read H7.- El proceso HIJO provoca la señal SIGPIPE al ejecutar write H8.- El proceso HIJO muere por una señal (acción por omisión) H9.- El proceso HIJO maneja la señal SIGPIPE H10.- El proceso HIJO maneja la señal SIGINT H11.- El proceso HIJO ignora la señal SIGINT

Recomendación: estudie el comportamiento del programa, luego tache las afirmaciones falsas y, finalmente, ordene las verdaderas.

Ej.

3-13

PADRE

a) P2, P7, P4, P1 b) P2, P4, P7, P1

HIJO

a) H3, H5, H10, H7, H8 b) H3, H10, H5, H7, H8

Sea el código fuente siguiente:

#include <unistd.h> #include <stdio.h> #include <signal.h> char handmsg1[] = "He capturado una señal\n"; char handmsg2[] = " han transcurrido 15 seg.\n"; struct sigaction act; int signum, signal_received = 0; void handler (int signo) { if (signo!=SIGALRM) write(2,handmsg1, strlen(handmsg1)); else{ signal_received=1; write(2, handmsg2, strlen(handmsg2)); } }

main(int argc, char *argv[]) { sigset_t sigset; int i; if (argc != 2) {fprintf (stderr, "Usage: %s signo\n", argv[0]); exit(0);} fprintf(stderr, "Comienzo a trabajar\n"); signum=atoi(argv[1]); act.sa_handler = handler; sigfillset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGALRM, &act, NULL); sigaction(signum, &act, NULL); sigfillset(&sigset); sigdelset(&sigset, signum); sigdelset(&sigset, SIGALRM); fprintf (stderr, "Espera 15 seg. a la señal: %s\n", argv[1]); alarm(15); while(signal_received == 0) sigsuspend(&sigset); fprintf (stderr, "Programa terminado\n"); }

Dicho código se corresponde con un ejecutable denominado senyal. a) Indique qué se imprimiría por pantalla si se ejecuta el programa con:

$senyal 2

e inmediatamente antes de que dicho proceso ejecute alarm(15) el usuario teclea CTRL-C, CTRL-C, CTRL-C (se teclea tres veces lo mismo) NOTA: tenga el cuenta que CTRL-C es la señal 2.

Page 31: Recopilacion 163 Ejercicios de So2

b) Indique a lo largo del código que señales se enmascara o/y desenmascaran y durante que sección del mismo es valido ese estado.

Ej.

3-14

A) Comienzo a trabajar Espera 15 seg. a la señal 2 He capturado una señal He capturado una señal He capturado una señal Han transcurrido 15 seg. Programa terminado. B) sigfillset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGALRM, &act, NULL); sigaction(signum, &act, NULL); Durante la ejecución del manejador handler, se encuentran enmascaradas todas las señales. Por tanto mientras se ejecuta el manejador handler no se atenderá ninguna señal. /**********************************************************/ sigfillset(&sigset); sigdelset(&sigset, signum); sigdelset(&sigset, SIGALRM); Mientras el proceso está suspendido con sigsuspend() únicamente se atienden las señales signum y SIGALARM ya que son las únicas que nos están suspendidas.

Dado los ficheros senyal1 y senyal2 que contienen los ejecutables de los códigos fuentes siguientes:

senyal1.c senyal2.c #include <stdlib.h> #include <stdio.h> #include <unistd.h> int main (int argc, char *argv[]) { int val_pid; alarm(15); val_pid =fork(); for ( ; ; ); }

#include <stdlib.h> #include <stdio.h> #include <unistd.h> int main (int argc, char *argv[]) { int val_pid; alarm(15); val_pid=fork(); sleep(20); }

Indique de manera justificada cuál será el tiempo aproximado de ejecución:

A) De cada uno de los procesos que se generan cuando es ejecutado senyal1. B) De cada uno de los procesos que se generan cuando es ejecutado senyal2.

Page 32: Recopilacion 163 Ejercicios de So2

Ej.

3-15

A) Para responder a esta pregunta son necesarios los conceptos siguientes: a) alarm(seg), programa una señal que ocurre en seg segundos. b) la acción por omisión de una señal es terminar el proceso. c) Los procesos hijos no heredan las señales pendientes. d) Los procesos atienden las señales incluso cuando están suspendidos. Al ejecutar este código se generan dos procesos (padre e hijo) que ejecutan el mismo código. El proceso padre programa la alarma, crea el hijo y ejecuta el bucle infinito hasta que ocurra la alarma a los 15 segundos que finaliza el proceso padre. El proceso hijo ejecuta el bucle infinito y de ahí no saldrá a menos que se le envíe una señal no enmascarable como $kill -9 val_pid Ya que los procesos hijos no heredan las señales pendientes. Proceso padre-- 15 segundos Proceso hijo -- indeterminado B) AL ejecutar este código se generan dos procesos (padre e hijo) ambos ejecutan el mismo código. El proceso padre programa la alarma, crea el hijo y se suspende con sleep hasta que ocurra la alarma a los 15 segundos que finaliza el proceso padre. El proceso hijo se suspende con el sleep durante 20 segundos y luego finaliza. Proceso padre-- 15 segundos Proceso hijo -- 20 segundos

Se pide completar el siguiente código de un programa lector de correo. Este programa comprueba si existe correo nuevo a intervalos de tiempo regulares. El programa principal tiene un bucle en el que se lee por consola este periodo de tiempo en segundos (variable periodo). En el caso en que se introduzca un valor cero para el periodo, el programa termina. Utilizad para ello la señal de alarma (SIG_ALRM). El manejador que se ejecutará al ocurrir la señal, deberá invocar a la función ComprobarCorreo(). Considere que la función ComprobarCorreo() ya está implementada y que retorna el número de mensajes que hay en el buzón de correo.

#include <unistd.h> #include <stdio.h> #include <signal.h> struct sigaction act; // (1) ESCRIBA EL CÓDIGO DEL MANEJADOR int main(int argc, char *argv[]) { int periodo; printf("Mi lector de correo\n"); // (2) ESCRIBA EL CÓDIGO PARA INSTALAR EL MANEJADOR while (1) { // Lee el periodo desde consola printf("Periodo de consulta (segundos) - 0 fin programa "); scanf("%u", &periodo); if (periodo > 0) { // (3) COMPLETAR } else { // (4) COMPLETAR } } }

Page 33: Recopilacion 163 Ejercicios de So2

Ej.

3-16

#include <unistd.h> #include <stdio.h> #include <signal.h> struct sigaction act; // (1) ESCRIBA EL CÓDIGO DEL MANEJADOR void comprueba_correo(int signo) { int mensajes; if (signo==SIGALRM) { printf("\nComprobando correo.... "); mensajes = ComprobarCorreo(); printf("Tiene %u mensajes nuevos\n", mensajes); } } int main(int argc, char *argv[]) { int periodo; printf("Mi lector de correo\n"); // (2) ESCRIBA EL CÓDIGO PARA INSTALAR EL MANEJADOR act.sa_handler = comprueba_correo; sigfillset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGALRM, &act, NULL); while (1) { // Lee el periodo desde consola printf("Periodo de consulta (segundos) - 0 fin programa "); scanf("%u", &periodo); if (periodo > 0) { alarm(periodo); // (2) COMPLETAR } else { exit(0); // (3) COMPLETAR } } }

Page 34: Recopilacion 163 Ejercicios de So2

4. Ejercicios sobre directorios y protección.

4.1 Dificultad de los ejercicios

Nivel de dificultad Ejercicios Principiante

Medio Avanzado

4.2 Ejercicios Explique qué relación puede tener la palabra de protección de un fichero con los errores que podría dar la llamada al sistema open(). Para ello, suponga que existe un fichero llamado “prueba” con la palabra de protección “rw-r--r--”. Dé un solo ejemplo de apertura de dicho fichero (es decir, la línea completa de código donde se produciría tal apertura) y explique en qué casos fallaría y en qué otros no.

Ej.

4-1

Cuando se abre un fichero mediante la llamada “open()”, el sistema operativo comprueba que el proceso esté autorizado para emplear el modo de apertura solicitado. Para ello verifica que las operaciones asociadas a dicho modo estén presentes en la tripleta correspondiente de la palabra de protección del fichero. Para averiguar qué tripleta debe usarse hay que utilizar el UID y GID efectivos del proceso, comparándolos con el UID y GID del fichero. Por ejemplo, la apertura open(“prueba”, O_WRONLY ); podría tener éxito si el UID efectivo del proceso es igual a cero (superusuario) o si tal UID efectivo coincide con el UID del propietario del fichero, pues en este último caso la clase a utilizar sería la del propietario y tal clase sí que tiene permisos de escritura. Por el contrario, si el UID efectivo no coincide con el del propietario del fichero, estaremos en las clases del grupo o del resto (en la primera si el GID efectivo del proceso coincide con el GID del fichero, en la segunda en otro caso) y en ambas no hay permiso de escritura, por lo que la apertura fallaría.

Utilizando la orden "ls -l" se ha podido averiguar que un determinado directorio tiene 5 enlaces físicos. ¿En qué parte de la salida de dicha orden aparece tal información (es decir, en qué columna)? ¿Qué entradas de directorio podrían corresponder a dichos enlaces físicos? ¿Hay alguna forma de saber si alguna de las entradas listadas tiene algún enlace simbólico en otro directorio?

Ej.

4-2

Aparece en la segunda columna, tal como se podía ver en el enunciadote la siguiente cuestión. Las entradas de directorio que corresponderían a estos cinco enlaces son:

• El nombre de dicho directorio en su directorio padre. • a entrada ". " en el propio directorio. • a entrada ".. " en cada uno de los tres subdirectorios contenidos en él.

No hay ninguna forma de saber si alguno de los ficheros listados tiene un enlace simbólico ubicado en algún otro directorio y que haga referencia a él.

Page 35: Recopilacion 163 Ejercicios de So2

Dada la salida proporcionada por la orden “ls –li”, donde la primera columna hace referencia al nodo-i de cada fichero y la tercera columna al número de enlaces físicos de dicho nodo-i. Indique de forma justificada qué tipo de enlace son fich1, fich2, fich3 y fich4, sabiendo que han sido creados en ese orden.

916038 -rw-r--r-- 2 juan so2 36 May 23 20:46 fich1 916040 lrwxrwxrwx 2 juan so2 5 May 23 20:47 fich2 -> fich1 916038 -rw-r--r-- 2 juan so2 36 May 23 20:46 fich3 916040 lrwxrwxrwx 2 juan so2 5 May 23 20:47 fich4 -> fich1

Ej.

4-3

fich1: enlace físico (i-nodo 916038) fich2: enlace simbólico a fich1 (nuevo i-nodo 916040) fich3: enlace físico (i-nodo 916038) fich4: enlace físico a fich2 (i-nodo 916040)

Dado el siguiente listado de un directorio en un sistema POSIX: drwxr-xr-x 2 fmunyoz so2 4096 may 8 2002 . drwxr-xr-x 11 fmunyoz so2 4096 mar 21 14:39 .. -rwsrw-r-x 1 root so2 1139706 abr 9 2002 nuevo -rw-rw-r-- 1 fmunyoz so2 634310 abr 9 2002 fich1 -rw-rw-r-- 1 fmunyoz so2 104157 abr 9 2002 fich3 Donde el programa "nuevo" copia el fichero recibido como primer argumento, dándole a la copia el nombre recibido como segundo argumento. Explique si las siguientes órdenes podrán completarse con éxito o no asumiendo que todas ellas se ejecutan en el directorio listado anteriormente a) "nuevo fich1 f ich2", con los atributos eUID=jose y eGID=so2, inicialmente. b) "nuevo fich1 f ich3", con los atributos eUID=juan y eGID=grupo3 inicialmente

Ej.

4-4

a) Como "jose" no es el superusuario, ni coincide con el propietario del fichero “nuevo”, pero “so2” es el grupo del fichero, tendremos que utilizar la tripleta correspondiente al grupo. En ella vemos que vara este proceso resultará imposible ejecutar el programa "nuevo", con lo que la orden no podrá tener éxito. b) En este segundo caso, ni el eUID es el del propietario del fichero ni el eGID es el del grupo del fichero. Por tanto, la tripleta a utilizar para averiguar si podemos ejecutar "nuevo " será la tercera (correspondiente al resto de usuarios). En ella, vemos que resulta posible la ejecución. Como este programa tiene el bit SETUID activo, al ejecutarlo adoptaremos como eUID el valor 0, correspondiente a "root" (el superusuario). Al adoptar dicha identidad, todos los accesos que pida tal proceso se concederán, independientemente de la palabra de protección que pueda tener cada uno de los ficheros accedidos Con ello esta segunda orden si que tendría éxito.

¿Y las siguientes órdenes? c) "nuevo f ich1 f ich2", con los atributos eUID=jose y eGID=grupo3, inicialmente. d) "nuevo f ich1 f ich3", con los atributos eUID=juan y eGID=grupo3, inicialmente.

Page 36: Recopilacion 163 Ejercicios de So2

Ej.

4-5

c) Como "jóse" no es igual a root, ni igual a pedro", ni "grupo3" es igual a so2, tendremos que utilizar la tercera tripleta de "nuevo". Allí comprobamos que sí que podemos ejecutar el programa. Al hacerlo, adoptaremos "pedro" como eUID. Intentaremos leer "fich1" y podremos hacerlo, pues este fichero tiene el derecho de lectura en todas sus tripletas. Posteriormente intentaremos crear el fichero "fich2", para ello hay que examinar la palabra de protección del directorio "." y comprobar que el proceso tenga permiso de escritura. Este proceso está en la categoría de "otros" y en ella no aparece el permiso que se necesita. Por tanto, esta orden fallará d) Ocurre algo similar a la orden anterior, excepto en lo referente al acceso a "fich3". Ahora bastaría con tener permiso de escritura sobre dicho fichero, pues ya existía y no necesitamos crearlo, sino modificarlo. Seguimos estando en la categoría de otros, por lo que el derecho de escritura no está presente y esta orden también fallará

Dado el siguiente listado de un directorio en un sistema POSIX: drwxr-xrwx 2 juan so2 4096 may 8 2002 . drwxr-xrwx 11 juan so2 4096 mar 21 14:39 .. -rwxrwxr-x 1 root so2 1139706 abr 12 2003 copia1 -rwxrwsr-x 1 juan so2 1128236 abr 12 2003 copia2 -rw-r--rw- 1 juan so2 634310 abr 9 2002 fich1 -rw-r--rw- 1 juan so2 104157 abr 9 2002 fich3 donde los programas “copia1” y “copia2” copian el fichero recibido como primer argumento, dándole a la copia el nombre recibido como segundo argumento. Explique detalladamente si las siguientes órdenes podrán completarse con éxito o no, asumiendo que todas ellas se ejecutan en el directorio listado anteriormente. a) “copia1 fich1 fich2”, con los atributos eUID=jose y eGID=so2, inicialmente. b) “copia2 fich1 fich3”, con los atributos eUID=pedro y eGID=fco, inicialmente.

Ej.

4-6

a) Como copia2 tiene permisos de ejecución (concretamente para el grupo, ya que eGID=so2) se podrá ejecutar. También podremos leer el fichero fich1, ya que tiene permisos de lectura. No obstante la operación fallará al intentar crear el fichero fich2, ya que el grupo (eGID=so2) no tiene permisos de escritura en el directorio. Respuesta: FALLA. b) Como copia2 tiene activado el bit de SETGID, al ejecutarse copia2 con eGID=fco, el grupo efectivo pasará a ser so2. En este caso, al intentar sobrescribir fich3, no se tendrá éxito, ya que fich3 no tiene permisos de escritura para el grupo (so2). Respuesta: FALLA.

Se dispone de un sistema con 3 usuarios, Albert, Blanca y Carles. Albert y Carles pertenecen al grupo grup1, mientras que Blanca pertenece a grup2. La salida del directorio actual es la siguiente:

Page 37: Recopilacion 163 Ejercicios de So2

-rwxr-xr-x 1 albert grup1 1014 May 17 11:10 viatge -rw------- 2 blanca grup2 0 Jun 27 09:11 Londres -rw-rw-r-- 2 carles grup1 0 Jun 27 08:49 Roma -r-------- 2 albert grup1 0 Jun 27 09:11 Viena donde viatge es un programa que añade al final del fichero que se le pasa como argumento una línea con el nombre del usuario efectivo que ha ejecutado viatge. Indica cual será el contenido de cada uno de los ficheros Londres, Roma y Viena después de ejecutar la siguiente secuencia de órdenes:

Usuario Orden ------ ----------------- Albert viatge Viena Blanca viatge Londres Albert viatge Londres Albert chmod u+w Viena Albert viatge Viena Carles viatge Viena Carles viatge Roma Albert viatge Roma Albert chmod u+s viatge Blanca viatge Viena Blanca viatge Londres Carles viatge Roma

Ej.

4-7

Londres: blanca Roma: carles albert albert

Viena: albert albert

Razone si un proceso puede modificar sus UID y GID efectivos durante su ejecución. Si es imposible, indique por qué. Si fuera posible, describa cómo podría conseguir hacerlo.

Ej.

4-8

Sí que puede hacerse. Existen dos formas. La primera sólo podrán utilizarla aquellos procesos cuyo UID efectivo es 0 (root). Para ello, basta con utilizar las llamadas seteuid() y setegid(). Estas llamadas fallarían si el proceso no se ejecuta previamente con la identidad indicada (eUID=0) y se intentara instalar como UID efectivo un valor distinto al UID real del proceso. La segunda opción es ejecutar un programa que tenga en su palabra de protección el bit SETUID o el bit SETGID activos. Para ello, el proceso ha de tener derechos de ejecución sobre el programa correspondiente y su UID efectivo o GID efectivo pasarían a ser el del propietario o grupo del fichero, según el bit que estuviera activo.

Suponiendo que el fichero “dat.dat” no existe en el directorio actual, indique con qué permisos se crea dicho fichero al ejecutar el siguiente código:

#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> main() {

Page 38: Recopilacion 163 Ejercicios de So2

umask(S_IWGRP|S_IWOTH); open("dat.dat",O_CREAT,S_IWUSR|S_IWGRP|S_IWOTH ); }

Ej.

4-9

Se creará sólo con permiso S_IWUSR, es decir, permiso de escritura para el propietario. Esto se debe a que los otros dos permisos solicitados (escritura para grupo y resto de usuarios) también están presentes en la máscara de creación de nuevos ficheros. Por ello, dichos permisos no se conceden al crear un nuevo fichero, pues cualquier derecho presente en dicha máscara se elimina a la hora de que el proceso genere nuevos ficheros.

Explique qué representan los números mayor y menor en un fichero especial ¿Cómo podemos averiguar sus valores para un fichero determinado?

Ej.

4-10

El número mayor representa el tipo de dispositivo y de alguna forma permite que el sistema operativo identifique qué manejador tendrá que tratar las operaciones dirigidas a tal dispositivo. El número menor representa el número de unidad dentro del tipo especificado por el número mayor. Podemos ver sus valores realizando un "ls -l" sobre el directorio "/dev". En la salida proporcionada por dicha orden, la 5a columna nos daría el número mayor, mientras que la 6a proporcionaría el número menor

Explique al menos una forma de obtener el tamaño de un fichero. Para ello, indique qué llamada al sistema tendrá que utilizarse y cómo (con qué combinación de argumentos o en qué campo) podrá obtenerse ese tamaño

Ej.

4-11

Una primera opción es utilizar la siguiente linea: tamaño = lseek(fd,0,SEEK_END); Con ella obtendríamos el valor del puntero de posición resultante de mover éste en la posición final del fichero. Por ello, estaríamos obteniendo el tamaño del fichero. Nótese que el primer argumento de esta llamada es un descriptor de fichero obtenido previamente al abrir el fichero en cuestión. Una segunda opción es utilizar la llamada stat(), pasándole el nombre del fichero como primer parámetro y la dirección de una estructura "stat" como segundo. Posteriormente, el campo "st_size" de la estructura nos daría el tamaño (en bytes) del fichero.

Explique en qué consiste la operación de montaje de un dispositivo en un sistema UNIX. Describa sus efectos en el grafo de directorios utilizado por el sistema. Escriba un ejemplo de línea de órdenes en la que se realice una operación de montaje.

Ej.

4-12

La operación de montaje implica asociar el contenido (el grafo de directorios) de un determinado dispositivo de almacenamiento a un directorio utilizado como punto de montaje. Como resultado, la raíz del dispositivo montado quedará asociada al directorio empleado como punto de montaje y, a través de él, se podrá acceder al contenido de dicho dispositivo. Con ello conseguimos que el grafo de directorios en un sistema UNIX sea único, independientemente del número de dispositivos existentes en dicho equipo. Aparte, mientras dure el montaje todo el contenido del directorio empleado como punto de montaje queda temporalmente inaccesible, pues ha sido "sustituido" por lo que hubiese en el dispositivo montado. Ejemplo: mount /dev/fd0 /mnt (Montaría el disqueteen el directorion /mnt)

Page 39: Recopilacion 163 Ejercicios de So2

Indique qué efecto tendrá realizar una operación lseek () sobre un tubo. Razone adecuadamente por qué sucede eso.

Ej.

4-13

Si queremos emplear lseek() para desplazar el puntero de posición, esta llamada fallará pues los tubos sólo admiten el acceso secuencial.

Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F)

Ej.

4-14

F No existe ninguna función POSIX que permita obtener el valor del puntero de posición asociado a un determinado descriptor de fichero. Sí que existe una función para averiguar esto. Es lseek().

F La función POSIX a emplear para modificar un directorio se llama writedir(). No existe una función que directamente nos permita hacer tal cosa. Las modificaciones de un directorio son resultados colaterales de otras operaciones: crear un fichero, borrar el nombre de un fichero, crear un subdirectorio, borrar un subdirectorio, cambiar el nombre de una entrada etc

V Es posible que al ejecutar la llamada open(“nuevo”,O_WRONLY|O_CREAT|O_TRUNC,0666) se cree un ficero con la palabra de protección "rw-r-----“ Sí que resulta posible. Aunque con la llamada especificada se estaría solicitando la creación con una palabra de protección "rw-rw-rw-", sería posible obtener la que indicaba el enunciado si tuviéramos la máscara de creación de ficheros adecuada. Por ejemplo, la 026

V La función open () no debe ser utilizada para abrir un directorio. Cierto Para abrir un directorio necesitamos la función opendir()

V Un fichero de tipo directorio tiene como mínimo dos enlaces físicos que hacen referencia a él. Cierto. Al crear el directorio, éste ya tiene dos enlaces que no podremos eliminar hasta haberlo borrado: su nombre en el directorio padre y la entrada "." en él mismo

Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F)

Ej.

4-15

V Una forma de comprobar si un proceso tiene permisos de acceso sobre un fichero consiste en abrir éste en el modo adecuado y comprobar el resultado de tal llamada. Verdadero. Por ejemplo, si queremos saber si el proceso puede escribir sobre un fichero, podemos abrirlo en modo "sólo escritura". Si la llamada no falla, eso querrá decir que el acceso está permitido. Si fallara indicaría que no tenemos ese permiso.

V La función POSIX a emplear para abrir un directorio se llama opendir() Verdadero. No podemos utilizar la llamada open(), pues fallaría.

F Es posible que al ejecutar la llamada open(“nuevo”,O_WRONLY|O_CREAT|O_TRUNC,0666) se cree un fichero con la palabra de protección "rw-r-x---". Falso. En el tercer argumento de open() no hemos especificado el permiso de ejecución para el grupo, que si aparece en la palabra de protección que daba el enunciado. Es imposible que el sistema "añada" algún derecho a lo que pida el proceso creador. El valor de la máscara de creación de ficheros justificaría el hecho de que no aparezcan los derechos de escritura ni en el grupo ni en el campo del resto de usuarios, así como la ausencia del derecho de lectura en esa última tripleta.

V La función close () no debe ser utilizada para cerrar un directorio. Verdadero, debe usarse closedir()

F Un fichero de tipo especial (de bloques o de caracteres) tiene como mínimo dos enlaces físicos que hacen referencia a él. Falso. El número mínimo de enlaces para los ficheros especiales es uno.

Page 40: Recopilacion 163 Ejercicios de So2

Dado el siguiente listado del contenido de un directorio UNIX: -rwsr--r-x 1 calif grupo1 1014 May 17 11:10 miprog -r---w---- 1 calif grupo1 14487 Jun 25 09:11 datos -rw----r-- 1 calif grupo1 2099 Jun 25 08:49 punt2 Y los usuarios “ramon” (perteneciente al grupo “grupo1”), “marta” (perteneciente al grupo “grupo3”) y “juan” (perteneciente al grupo “grupo2”). Indique, de entre los tres usuarios citados, quienes podrán utilizar el programa “miprog”, y con él, procesar la información existente en los ficheros “datos” y “punt2” (para ello, únicamente se necesita leer el contenido de ambos ficheros).

Ej.

4-16

Grupo 1, no puede ejecutar miprog

Grupo 2 y 3 pueden ejecutar miprog y leer en ejecución los ficheros datos y punt2.

Page 41: Recopilacion 163 Ejercicios de So2

5. Ejercicios sobre hilos y sección crítica

5.1 Dificultad de los ejercicios

Nivel de dificultad Ejercicios Principiante

Medio Avanzado

5.2 Ejercicios Conteste de forma concreta y concisa a las siguientes preguntas:

a) ¿Qué es un programa concurrente? b) ¿Qué es una condición de carrera? c) ¿Qué es una sección critica? d) ¿Qué condiciones deben cumplir los protocolos de las secciones críticas?

Ej.

5-1 a) Es un único programa constituido por varias actividades concurrentes que pueden ejecutarse de forma

independiente. Dichas actividades trabajan en común para resolver un problema y se coordinan y comunican entre sí.

b) Una condición de carrera ocurre cuando un código que ejecutado secuencialmente es correcto, deja de serlo al ejecutarlo concurrentemente. Una condición de carrera se produce cuando la ejecución de un conjunto de operaciones concurrentes sobre una variable compartida deja a la variable en un estado inconsistente con las especificaciones de corrección. Además el resultado de la variable depende de la velocidad relativa en que se ejecuten las operaciones.

c) Son las zonas de código en las que se acceden para lectura o escritura a las variables compartidas. Y por tanto zonas de código que hay que sincronizar su ejecución d) El protocolo tiene que cumplir tres condiciones: Exclusión mutua, progreso y espera limitada.

Sabiendo que la llamada necesaria para crear un hilo es: int pthread_create(pthreadt *al, pthreadattrt *a2, void *(*a3)(void *), void *a4); Escribir el código necesario para crear un hilo cuya función principal se llamará "sumador" y cuya misión será aplicar una determinada función matemática a los cinco números enteros que se le suministrarán como argumento. Declare las estructuras de datos necesarias para ello y muestre cómo quedaría la línea empleada para la creación.

Page 42: Recopilacion 163 Ejercicios de So2

Ej.

5-2

Tenemos dos opciones: utilizar un vector donde dejaremos los cinco enteros o declarar una estructura con cinco campos de tipo entero. 1ª opción: int vector[5]; pthread_t tid; pthread_create( &tid, NULL, sumador, vector); 2ª opción: pthread_t tid; typedef struct { int ni, n2, n3, n4, n5; } args; args valores; pthread_create( &tid, NULL, sumador, &valores);

Cite al menos tres atributos que podríamos encontrar en el "bloque de control de hilo".

Ej.

5-3 Sólo podremos encontrar atributos relacionados con la identidad del hilo, con la gestión de sus

recursos (apenas tiene recursos, pues comparte los asignados al proceso) y con su planificación Ejemplos de estos atributos pueden ser: identificador del hilo, identificador del proceso en el que se encuentra el hilo, estado de planificación, mapa de memoria referente a su pila, prioridad de planificación (en caso de utilizar un algoritmo basado en prioridades)

Indique las cadenas que imprime el programa en la Terminal tras su ejecución. Justifique su respuesta. Nota: retraso(n) realiza un retraso de n milisegundos

void * funcion_hilo1(void * arg) { retraso(4000+rand()%1000); printf(“Hola Don Pepito\n”); return null; }

void * funcion_hilo2(void * arg) { retraso(4000+rand()%1000); printf(“Hola Don Jose\n”); return null; }

int main (void) { pthread_t th1,th2; pthread_attr_t atrib; pthread_attr_init( &atrib ); printf(“Eran dos tipos requeeeteeefinos...\n”); pthread_create( &th1, &atrib, function_hilo1, null); pthread_create( &th2, &atrib, function_hilo2, null); exit(0); }

Page 43: Recopilacion 163 Ejercicios de So2

Ej.

5-4 Imprimirá únicamente: “Eran dos tipos requeeeteeefinos...

El motivo es que tras la creación de los dos hilos, el hilo principal (main) termina de inmediato (exit) y provoca la terminación de todos los hilos. Como los hilos tienen un retraso importante antes de realizar sus respectivas sentencias impresión, no tendrán tiempo a imprimir nada (a menos que el algoritmo de planificación retrase anormalmente la ejecución de exit por parte del hilo principal). Debe tenerse en cuenta que la llamada exit() termina completamente al proceso, por lo que aunque haya otros hilos activos en él, estos también son eliminados al realizar el exit().

¿Qué imprime por la salida estándar el siguiente programa? #include <pthread.h> pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; void proc1(char *arg){ pthread_mutex_lock(&mutex); printf(arg); pthread_mutex_unlock(&mutex); if (strcmp(arg,”2”)) pthread_exit(0); }

main (){ pthread_t id1,id2,id3; pthread_mutex_lock(&mutex); pthread_create(&id1,NULL,proc1,"S"); pthread_create(&id2,NULL,proc1,"O"); pthread_create(&id3,NULL,proc1,"1"); proc1("2"); pthread_mutex_unlock(&mutex); exit(0); }

Ej.

5-5

No imprime nada. Todos los hilos, incluyendo al principal, se suspenden en la operación pthread_mutex_lock(&mutex) de la función proc1 al estar mutex cerrado y adquirido por el hilo principal.

El siguiente programa pretende crear un hilo para realizar la copia del fichero file.in en el fichero file.out. Para ello el programa crea un hilo que ejecuta la función copy_file. Esta función leerá bytes del fichero file.in y los escribirá en el fichero file.out.

1: #include <stdio.h> 2: #include <sys/types.h> 3: #include <sys/stat.h> 4: #include <fcntl.h> 5: #include <string.h> 6: #include <pthread.h> 7: 8: #define BUFFERSIZE 100 9: 10: void *copy_file(void *arg) 11: { 12: int infile, outfile; 13: int bytes_read = 0; 14: int bytes_written = 0; 15: static int bytes_copied = 0; 16: char buffer[BUFFERSIZE]; 17: char *bufp; 18: 19: infile = *((int *)(arg)); 20: outfile = *((int *)(arg) + 1); 21: for ( ; ; ) { 22: bytes_read = read(infile, buffer, BUFFERSIZE); 23: if ((bytes_read == 0) || ((bytes_read < 0) && (errno != EINTR))) 24: break;

Page 44: Recopilacion 163 Ejercicios de So2

25: else if ((bytes_read < 0) && (errno == EINTR)) 26: continue; 27: bufp = buffer; 28: while (bytes_read > 0) { 29: bytes_written = write(outfile, bufp, bytes_read); 30: if ((bytes_written < 0) && (errno != EINTR)) 31: break; 32: else if (bytes_written < 0) 33: continue; 34: bytes_copied += bytes_written; 35: bytes_read -= bytes_written; 36: bufp += bytes_written; 37: } 38: if (bytes_written == -1) 39: break; 40: } 41: close(infile); 42: close(outfile); 43: pthread_exit(&bytes_copied); 44: } 45: 46: void main(void){ 47: pthread_t copy_tid; 48: int error; 49: int miarg[2]; 50: int *bytes_copied_p; 51: 52: if (( miarg[0] = open("file.in", O_RDONLY)) == -1) 53: perror("No se puede abrir file.in"); 54: else if (( miarg[1] = open("file.out",O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) == -1) 55: perror("No se puede abrir file.out"); 56: else if (error=pthread_create( &copy_tid ,NULL, copy_file, (void *)miarg)) 57: fprintf(stderr,"No se pudo crear el hilo correctamente: %s\n", strerror(error)); 58: if (error=pthread_join(copy_tid, (void **)&(bytes_copied_p))) 59: fprintf(stderr, "Ningun hilo para hacer el join : %s \n", strerror(errno)); 60: else 61: printf("El hilo ha copiado %d bytes de file.in a file.out \n", *bytes_copied_p); 62: exit(0); 63: }

Este programa no funcionará correctamente si la llamada write de la función copy_file falla. Indique qué modificaciones habría que hacer para que la función main mostrara un mensaje de error cuando se produjese esta situación. Este mensaje debe incluir el valor de la variable errno producido en la ejecución del hilo. NOTA: Téngase en cuenta que en un programa con varios hilos, cada uno de ellos tiene una copia propia de la variable “errno”. SE ESTÁ PIDIENDO QUE EL MENSAJE DE ERROR LO MUESTRE EL HILO PRINCIPAL, NO EL HILO QUE LO HA GENERADO.

Ej.

5-6

Se ha de tener en cuenta que cada hilo tiene una copia privada de la variable errno, con lo cual el hilo principal tendrá un valor de errno diferente al errno del hilo copy_file. Por tanto, el hilo copy_file, en lugar de devolver solo los bytes escritos, debería devolver el puntero a una estructura que contuviera tanto los bytes escritos como el valor de errno del hilo.

El siguiente pseudocódigo ilustra la manera en que se quiere proteger la Sección Crítica de los procesos 1 y 2 que comparten algunas variables entre ellas la variable llave inicializada a 0.

Línea

Pseudocódigo

Page 45: Recopilacion 163 Ejercicios de So2

A B C D E

while (llave); printf(“Modificamos llave a 1\n”); llave=1; /* Sección Crítica */ llave=0; /* Sección restante */

Indique si es posible el siguiente orden de ejecución de las instrucciones para dichos hilos A1, B1, A2, B2, C2, D2, C1, D1, E1, E2 Justifique su respuesta. Nota: A1 indica que el hilo 1 ejecuta la línea de código referenciada como A, A2 indica que el hilo2 ejecuta la línea referenciada como A, etc.

Ej.

5-7 Si que es posible que se produzca la secuencia indicada.

Esta forma de proteger la sección crítica no es adecuada ya que pueden producirse escenarios decambio de contexto que violen la exclusión mutua. La secuencia presentada en el ejercicio es uno deestos casos.

El siguiente programa implementa un algoritmo que pretende resolver el problema de la Sección Crítica de dos hilos de ejecución: hilo_0 e hilo_1. ¿Es correcta la solución propuesta? Nota: Analice la implementación en términos de exclusión mutua, progreso y espera limitada.

#include <pthread.h> #include <stdlib.h> #include <stdio.h> int turno=0; void *hilo_0(void *p) { while(1) { while (turno != 0); /* Sección crítica */ turno = 1; /* Sección restante */ } }

void *hilo_1(void *p) { while(1) { while (turno != 1); /* Sección crítica */ turno = 0; /* Sección restante */ } } int main(int argc, char **argv){ pthread_t h_0; pthread_t h_1; pthread_create(&h_0,NULL,hilo_0,(void *)0); pthread_create(&h_1,NULL,hilo_1,(void *)0); pthread_join(h_0, NULL); pthread_join(h_1, NULL); }

Ej.

5-8

Garantiza la exclusión mutua pero no el progreso, pues los hilos pueden tener diferentes “velocidades” de ejecución y solicitar la ejecución de sus secciones críticas en órdenes no alternativos. En esos casos no se cumpliría el requisito de progreso pues se estaría asignando de manera estática la entrada a la sección crítica a un hilo que no lo ha solicitado y se impediría la entrada al que sí lo pidió.

Page 46: Recopilacion 163 Ejercicios de So2

Razone adecuadamente si el siguiente código es una buena solución al problema de la sección crítica para dos hilos, con identifícadores (valores de la variable i) 0 y 1, asumiendo que las variables flag[0] flag[1] están compartidas e inicialmente valen cero.

hilo_i(void) { while ( 1 ) { sección_restante; flag[i] = 1; while(flag[(i+1) % 2] ) ; sección_crítica; flag[i] = 0; } }

int turno = 0; /* Compartida */ hilo_i(void){ while ( 1 ) { sección_restante; flag[i] = 1; turno = (i+1)%2; while(flag[(i+1)%2] && (turno==(i + 1) %2) ) ; sección_crítica; flag[i] = 0; } }

a) Ambos hilos ejecutan el código de la columna izquierda

Ej.

5-9 Una buena solución al problema de la sección critica debe cumplir exclusión mutua, progreso y espera

limitada. Veamos que ocurre en este caso: a) Exclusión mutua: El hilo "i" sólo podrá estar en su sección crítica si el flag del otro hilo estaba a valor falso. Es decir, sólo si el otro no ha pedido entrar. Por tanto, si uno ejecuta su sección crítica el otro no podía estar ejecutando su sección cuando aquél entró. Si después ha intentado entrar el que estaba fuera, tampoco ha podido lograrlo porque el flag del que ya está dentro lo evita. Por tanto, sí que se cumple la exclusión mutua. b) Progreso: Para cumplir la condición de progreso la decisión sobre qué hilo puede entrar debe considerar como candidatos sólo a aquéllos que hayan solicitado la entrada y tal decisión debe tomarse en un tiempo finito. Desafortunadamente, éste último "detalle" puede no cumplirse en este ejemplo. Si los dos hilos han conseguido fijar sus flags a cierto (por ejemplo, debido a un cambio de contexto tras haber ejecutado "flag[i]=1;" en el primero que intentaba entrar), el protocolo de entrada utilizado es incapaz de elegir qué hilo debe entrar, bloqueándose ambos mutuamente en dicho protocolo. Por tanto, como no se toma la decisión en un tiempo finito, la propiedad de progreso no siempre se cumple y ésta no es una buena solución al problema de la sección crítica. c) Espera limitada: Para que esta condición se cumpla, si un hilo llega al protocolo de entrada debe existir una cota sobre el número de veces que el otro hilo consiga entrar "adelantándole". Está claro que si un hilo llega al protocolo de entrada, logra poner su flag a cierto. Con ello garantiza que el otro ya no podrá entrar. Por tanto, si que existe una cota sobre el número de veces que otros hilos pueden adelantar a un candidato, y dicha cota es cero. Por desgracia, aunque esto garantiza la propiedad de espera limitada, también puede conducir a violar la propiedad de progreso, como ya hemos visto arriba

a) Ambos hilos ejecutan el código de la columna derecha

Page 47: Recopilacion 163 Ejercicios de So2

Ej.

5-10

Esta solución cumple las tres propiedades, como veremos seguidamente: a) Exclusión mutua: Si un hilo está ejecutando su sección crítica, seguro que ha puesto su flag a cierto. Para que esté ejecutándola, ha tenido que ocurrir algo más: o bien el otro hilo no había intentado entrar y tenía su flag a falso, o bien había intentado entrar y le había cedido el turno. En cualquiera de las dos situaciones, el otro hilo no conseguirá entrar. Por tanto, se garantiza la exclusión mutua, pues como máximo puede haber un hilo dentro de la sección crítica. Progreso: Aquí el progreso también se cumple. Si sólo desea entrar un hilo él ser el único que habrá fijado su flagy en ese caso, no tendrá que quedarse iterando en el bucle while del protocolo de entrada. Si los dos hilos hubieran pedido la entrada, entonces la variable "turno " servirá para deshacer el empate y dejar que sólo entre uno de ellos. El otro conseguirá entrar cuando el que estaba dentro ponga su flag a cero. Si ninguno de los dos está en el protocolo de entrada cuando sale el que ya estaba en la sección crítica, ésta queda disponible para el primero que llegue. Espera limitada: La gestión de la espera limitada es similar a la vista en la cuestión 3. Ahora, debido al uso de la variable turno sería posible que un hilo que ya ha llegado al protocolo de entrada debiera ceder dicha entrada al otro en el caso de que llegara inmediatamente después y consiguiera fijar también su flag a cierto. No obstante, el número de veces que podrá adelantarlo será como máximo uno. Por tanto, sigue cumpliéndose la espera limitada. No obstante aunque se satisfacen las tres propiedades, pueden darse ciertas situaciones con algunos algoritmos de planificación que conducirían a una situación de bloqueo. Por ejemplo, si utilizáramos planificación por prioridades expulsivas estáticas, teniendo el hilo 0 menor prioridad que el hilo 1 y asumiendo que el hilo 0 fue creado antes que el hilo 1, podríamos llegar a la siguiente situación: 1) El hilo 0 ejecuta toda su sección restante y también la instrucción flan[0]=1. 2) Se crea el hilo 1 y empieza a ejecutarse, como es el más prioritario no tendrá por que parar. Como flag[0] está a 1 y él mismo se ha encargado de poner turno=0, este hilo 1 no consigue superar su protocolo de entrada y queda perpetuamente en su bucle "while" de dicho protocolo. Esta situación parece violar la propiedad de progreso, pues ninguno de los dos hilos supera su respectivo protocolo de entrada. Sin embargo, no es así. Los protocolos de entrada con su estado obtenido a partir del anterior paso 2), han decidido que quien debe entrar es el hilo 0 . Si no lo hace es porque el algoritmo de planificación que hemos utilizado es INACEPTABLE (Ya se dijo en SOI que un algoritmo de ese estilo es bastante peligroso pues podía producir inanición para los procesos menos prioritarios). En las clases de teoría hemos visto que una buena solución al problema de la sección crítica debía cumplir exclusión mutua, progreso y espera limitada, pero siempre que se respetaran dos restricciones adicionales: que los procesos o hilos avanzaran todos ellos a velocidad no nula y que nuestra solución no dependiera de la velocidad relativa de tales procesos o hilos. Aquí se está violando la primera de esas restricciones, pues el hilo menos prioritario jamás podrá avanzar. Es decir, un algoritmo como el explicado no lo podemos utilizar como base para fijarnos en el cumplimiento de LAS TRES propiedades. Podría servirnos para demostrar que la espera limitada no se cumple, pero jamás para demostrar que el progreso no es viable.

Sea un proceso con un conjunto de hilos como se muestra en el código siguiente de manera esquemática. Las funciones “seccion_critica_i()” hacen referencia a un conjunto de instrucciones que deben ejecutarse en exclusión mutua por el hilo H[i].

a) Indique las características del protocolo de entrada. Analice los inconvenientes y/o ventajas que presenta este tipo de protocolo teniendo en cuenta tanto el tiempo de ejecución de los procesos como los diferentes algoritmos de planificación de procesos del Sistema Operativo. b) Indique de forma justificada la validez o no de este protocolo como solución al problema de la sección critica.

NOTA: Suponga que tanto las operaciones de incremento y decremento como las de comparación son atómicas.

#include <unistd.h> #include <stdio.h> #define N 5 int S[N];

main(int argc, char *argv[]) { pthread_t H[N];

Page 48: Recopilacion 163 Ejercicios de So2

void *hilos_I (void *arg) {int id; id = (int) arg; //PROTOCOLO ENTRADA SECC. CRITICA if (id>0) { while(S[id]==S[id-1]);} if (id==0) { while(S[0]!=S[N-1]);} if (id==0) seccion_critica_0(); if (id==1) seccion_critica_1(); if (id==2) seccion_critica_2(); if (id==3) seccion_critica_3(); if (id==4) seccion_critica_4(); //PROTOCOLO SALIDA SECC. CRITICA if (id>0) { S[id]=S[id-1];} if (id==0) { S[0]=(S[0]+1)mod 2;} pthread_exit(0); }

int i; for (i=0; i<N; i++) S[i]=0; for (i=0; i<N; i++) pthread_create(&H[i],NULL, hilos_I, (void *) i); for (i=0; i<N; i++) pthread_join(H[i],NULL); }

Ej.

5-11

A) Se trata de un espera activa con todos los inconvenientes y ventajas de la misma. La espera activa presenta problemas de infrautilización del procesador, tanto en sistemas de planificación por prioridades como en los de turno rotatorio. B) Hay un derecho rotativo a entrar en la sección crítica. El proceso que entra en la sección crítica es el que tiene su variable S distinta del proceso que le precede, salvo para el hilo H[0] que entra en sección critica cuando su variable S coincide con la del proceso que le precede. Inicialmente todas las S están inicializadas a cero y el único que puede entrar en la sección critica es el H[0] que al salir pone S[0]=1 y de esta forma sólo puede entrar en la sección críticas H[1], y así sucesivamente. En general, las soluciones de turno rotatorio no cumplen con la condición de progreso del problema de la sección crítica.

Dado el siguiente código de un programa POSIX

Variables globales. */ int a=10, b=15; void *f1(void *arg) { int b=10; a = (int) arg; while( b > 0 ) { /* Leemos b desde teclado. */ scanf( “%d”, &b ); a = a + 1; } }

void *f2(void *arg) { int *c; c = &a ; while(*c > 0) { *c = *c – 1; sleep(1); } }

Si tanto f1 como f2 van a utilizarse dentro de la función main() como funciones principales de los hilos de ejecución que se crean en este programa, marque dentro del código presentado arriba qué regiones (es decir, grupos de una o más líneas consecutivas) deberían considerarse secciones críticas dentro de cada una de esas dos funciones.

Page 49: Recopilacion 163 Ejercicios de So2

Ej.

5-12

SOLUCIÓN: Se han marcado las líneas de las secciones críticas en negrita, itálica y subrayado. En el hilo f1() se realiza una asignación del argumento recibido sobre la variable compartida "a" y posteriormente en su bucle se incrementa esa misma variable. En el hilo f2 se utiliza el puntero "c" para acceder a la variable global "a". Todas las instrucciones que posteriormente utilicen "c" para leer o modificar el contenido de "a " deben considerarse secciones críticas.

Razone adecuadamente si el siguiente código es una buena solución al problema de la sección crítica para dos hilos, con identifícadores (valores de la variable i) 0 y 1, asumiendo que las variables flag[0] flag[1] están compartidas.

hilo_i(void) { while ( 1 ) { /* Sección restante */ while(flag[(i+1) % 2] ) ; flag[i] = 1; /* Sección crítica */ flag[i] = 0; } }

int turno = 0; /* Compartida */ hilo_i(void){ while ( 1 ) { /* Sección restante */ flag[i] = 1; turno = (i+1)%2; if (flag[(i + 1)%2]) while(turno==(i + 1)%2) ; /* Sección crítica */ flag[i] = 0; } }

a) Ambos hilos ejecutan el código de la columna izquierda

Ej.

5-13

No es una buena solución pues no siempre garantiza la exclusión mutua. Por ejemplo, si los dos hilos han superado sus respectivos bucles while() pero han sido expulsados del procesador antes de poner sus flags a 1 ambos podrían llegar a entrar violando la exclusión mutua. Sí que cumple progreso, pues si un hilo estaba ejecutando su sección y el otro quería entrar al abandonar la sección se estará borrando el flag, permitiendo que el otro hilo pueda entrar si logra obtener la CPU. Además, si sólo hay un hilo que quiere entrar puede hacerlo sin problemas. No se cumple tampoco la espera limitada, pues el código presentado no impone ninguna cota al número de veces que un hilo puede entrar mientras el otro está esperando en su protocolo de entrada Esto sólo dependerá del planificador utilizado.

a) Ambos hilos ejecutan el código de la columna derecha

Ej.

5-14

Esta solución cumple la exclusión mutua y la espera limitada. La exclusión mutua se cumple porque si ambos intentan entrar, pondrán los dos flags a cierto, pero sólo uno de ellos conseguirá entrar pues la variable "turno " sólo permitirá la entrada del primero que había llegado al protocolo de entrada. Además, la construcción de este protocolo de entrada también evita que un hilo entre en la sección crítica si el otro ya estaba en ella. La espera limitada se cumple puesto que el uso de la variable "turno " evita que, cuando los dos hilos pretenden entrar, uno pueda adelantar al otro más de una vez Por el contrario, la propiedad de progreso no se cumple, pues hay algunos casos en los que la sección crítica queda libre y el hilo que pretende entrar no puede hacerlo debido a que la variable "turno " se lo impide. Por ejemplo, si el hilo 0 estaba en la sección crítica y entonces el hilo 1 llega a su protocolo de entrada, tendremos ambos flags a 1 y la variable "turno" al valor 0. Cuando el hilo 0 sale de la sección crítica pone flag[0] a O, pero esto no abre el paso del hilo 1. Por ello, no se está respetando la propiedad de progreso pues el único candidato a la entrada en la sección crítica no conseguirá entrar.

Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F)

Page 50: Recopilacion 163 Ejercicios de So2

Ej.

5-15

V Mediante la función pthread_self() se puede obtener el identificador de un hilo. Cierto Esta función es la que debe utilizarse para obtener el identificador.

F El código visto en la cuestión 2 utiliza espera activa. Falso. En el código visto en la cuestión 2 no existían protocolos de entrada o protocolos de salida para acceder a las secciones críticas pues éstas no estaban protegidas Por ello, no podemos hablar de que haya o no espera activa pues este problema sólo puede aparecer en los protocolos de entrada

F Si los hilos están soportados por el sistema operativo, siempre cuesta más un cambio de contexto entre procesos que entre hilos. Falso. Cuando los hilos pertenezcan a procesos diferentes el cambio de contexto costará igual o más que al dar un cambio de contexto entre procesos, en un sistema sin soporte a hilos. Esto se debe a que la información del contexto ahora está distribuida entre el PCB y el bloque de control de hilo.

F En los programas escritos para ejecutarse a nivel de usuario, resulta recomendable utilizar la inhabilitación/habilitación de interrupciones para solucionar el problema de la sección crítica. Falso. Aunque podría ser una solución válida al problema de la sección crítica, no es viable a nivel de usuario, puesto que un uso inadecuado de las operaciones necesarias para esta gestión de las interrupciones podría dejar "coleado " al equipo

V Si el soporte a hilos se encuentra en el núcleo, al suspenderse uno de los hilos, los demás podrán seguir ejecutándose. Cierto. Si los hilos están soportados por el núcleo, el planificador de éste puede gestionar de manera independiente a cada uno de ellos. Por ello, si se suspende alguno, el resto mantendrán el estado que previamente tuvieran. Si alguno de ellos estuviera preparado podría entonces pasar a ejecutarse.

Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F)

Ej.

5-16

V Mediante la función pthreadjoin() se puede obtener el valor retornado por la función principal de un hilo. El segundo argumento de esta función sirve para obtener dicho valor.

V Las soluciones vistas en las cuestiones 3 y 4 utilizan espera activa Cierto, pues en los protocolos de entrada, los hilos que no puedan acceder a la sección crítica estarán ejecutando un bucle vacío, malgastando inútilmente el tiempo de CPU que les otorgue el planificador.

F Utilizando soluciones al problema de la sección crítica con apoyo del hardware (por ejemplo con la instrucción testandset) se puede eliminar la espera activa Utilizando sólo esos apoyos del hardware, la espera activa no podrá eliminarse. Para eliminarla por completo necesitaríamos que el proceso o hilo que está en el protocolo de entrada permaneciera ahí en estado suspendido hasta que realmente pueda acceder a la sección crítica. Para ello necesitamos algún mecanismo facilitado por el sistema operativo. Serán los semáforos, que estudiaremos en el tema 8

F Todos los programas que utilizan múltiples hilos de ejecución presentan condiciones de carrera No tienen por qué. Podríamos escribir programas multihilo que sincronizaran sus accesos sobre las variables compartidas que pudiera haber. En ese caso se evitan las condiciones de carrera

F Si el soporte a hilos se encuentra a nivel de usuario al suspenderse uno de los hilos los demás podrán seguir ejecutándose Si el soporte se da a nivel de usuario sólo existe una unidad de ejecución dentro del proceso gestionada por el sistema operativo. Si alguno de los hilos de nivel de usuario utiliza alguna llamada que suspende a dicha unidad de ejecución, no quedan ya otras unidades disponibles para ejecutar al resto de los hilos de nivel de usuario. A consecuencia de esto, todo el proceso ha quedado suspendido y ninguno de los otros hilos podrá continuar.

Page 51: Recopilacion 163 Ejercicios de So2

Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F).

Ej.

5-17

V

El cambio de contexto entre hilos de ejecución soportados a nivel de usuario es más rápido que elde hilos soportados a nivel de núcleo.

F

El responsable de la planificación de hilos, tanto sin son creados a nivel de usuario como de núcleo, es el planificador del sistema.

F

Siempre que un hilo ejecuta la llamada pthread_exit(), el hilo finaliza voluntariamente su ejecución y se libera toda la memoria correspondiente al proceso pesado que soporta al hilo.

F

Los hilos de ejecución creados a nivel de usuario pueden compartir datos, pero no código, mientras que los hilos soportados a nivel de núcleo pueden comparten datos y código.

V

Un hilo de ejecución (o proceso ligero) se puede definir como una unidad básica de utilización de CPU con su propio contador de programa, juego de registros y su propia pila .

F

Una llamada al sistema bloqueante (read(), write()) por parte de un hilo, implica que todos los hilos pertenecientes al mismo proceso se bloqueen, con independencia de si han sido creados en modo núcleo o usuario.

Se desea desarrollar una aplicación que ponga en funcionamiento dos tareas que se deben ejecutar en orden. Los códigos de estas dos tareas se encuentran definidos en dos funciones cuyos prototipos en lenguaje C son los siguientes:

void Tarea_1(void) void Tarea_2(void)

Se pide programar la aplicación anterior utilizando dos modelos distintos: un programa que crea procesos para ejecutar cada una de las tareas y un programa que realiza las tareas utilizando procesos ligeros (hilos posix). En cualquiera de los dos casos la aplicación debe terminar cuando las tareas hayan acabado. La aplicación se debe programar de forma que no se usen más de dos procesos ligeros ni más de dos procesos pesados según el caso.

Ej.

5-18

Procesos Pesados Procesos Ligeros int main (void){ if (fork() == 0) Tarea_1(); else { wait(NULL); Tarea_2(); } }

int main(void){ pthread_1 hilo1; pthread_create(&hilo1, NULL, Tarea_1,NULL) pthread_join(&hilo1) Tarea_2(); exit(0) }

Page 52: Recopilacion 163 Ejercicios de So2

6. Ejercicios sobre semáforos y monitores

6.1 Dificultad de los ejercicios

Nivel de dificultad Ejercicios Principiante

Medio Avanzado

6.2 Ejercicios Observe el siguiente fragmento de código correspondiente a dos hilos que pertenecen al mismo proceso y se ejecutan concurrentemente. Indique qué recursos compartidos aparecen en el código y qué mecanismos se emplean para evitar las condiciones de carrera. Nota: Las variables y funciones que no están definidas dentro de las funciones agrega y resta son definidas como globales.

Void *agrega (void *argumento) { int ct,tmp; for (ct=0;ct<REPE;ct++) { while(test_and_set(&llave)==1); tmp=V; tmp++; V=tmp; llave=0; } printf("->AGREGA (V=%ld)\n",V); pthread_exit(0); }

void *resta (void *argumento) { int ct,tmp; for (ct=0;ct<REPE;ct++) { while(test_and_set(&llave)==1); tmp=V; tmp--; V=tmp; llave=0; } printf("->RESTA (V=%ld)\n", V); pthread_exit(0); }

Ej.

6-1 Recursos compartidos: variables “llave” y “V”.

La variable “llave”, al ser accedida únicamente mediante operaciones atómicas (test_and_set y asignación, que pueden ser completadas en una sola instrucción máquina y, por tanto, son indivisibles) no plantea ningún problema de acceso concurrente. Por otra parte, la variable “V” está protegida por un protocolo de entrada y otro de salida, implantados con la variable “llave” mencionada en el párrafo anterior.

La llamada al sistema operativo yield() permite a un proceso ceder lo que le queda de su cuanto de tiempo a cualquier otro que esté en la cola de PREPARADOS. De esta forma, el proceso que la invoca (que permanece en estado PREPARADO) libera la CPU, y da al planificador la oportunidad de seleccionar otro proceso para su ejecución.

a) ¿Cuál podría ser su aplicación para mejorar el comportamiento de la solución a la sección crítica basada en la instrucción test_and_set?

b) Impleméntese los protocolos de entrada y salida a sección crítica, utilizando las funciones test_and_set y yield(). Úsese como base la solución basada en test_and_set con espera activa.

Page 53: Recopilacion 163 Ejercicios de So2

Ej.

6-2 a) reducir el tiempo desperdiciado en la espera activa.

b) int llave=0; //sr while(test_and_set(&llave)) yield(); //sc llave=false; //sr

Suponiendo que la variable global llave tiene valor inicial 0 y que la función test_and_set está definida correctamente, comenta las diferencias entre las siguientes dos soluciones al problema de la sección crítica atendiendo al tiempo de ejecución observado en cada una de ellas. Nota: la función usleep() suspende al hilo que la invoca una cantidad de microsegundos.

/* Solución a */ void *hilo(void *p) { while(1) { while (test_and_set(&llave)); /* Sección crítica */ llave = 0; /* Sección restante */ } } /* Solución b */ void *hilo(void *p) { while(1) { while (test_and_set(&llave)) usleep(1000); /* Sección crítica */ llave = 0; /* Sección restante */ } }

Ej.

6-3

En la solución “a” el hilo que espera a que se libere la sección crítica ocupada consume todo su cuanto de tiempo comprobando la variable “llave” que no cambiará de valor en ese tiempo ya que la CPU permanecerá ocupada. Podemos considerar que este tiempo ha sido malgastado en una “espera activa”. En la solución “b” el hilo que espera a que la sección crítica quede libre, abandona la CPU voluntariamente nada más comprobar que está ocupada. No consume todo su cuanto de tiempo comprobando la variable “llave” y por lo tanto es más eficiente. La solución “a” puede aplicarse en cualquier ámbito de programación ya que la única instrucción necesaria es test_and_set que es una instrucción máquina. La solución “b” puede aplicar sólo cuando programamos sobre un sistema operativo ya que usleep se apoya en una llamada a sistema y en el mecanismo de suspensión de procesos del SO.

Page 54: Recopilacion 163 Ejercicios de So2

En relación a la utilización de semáforos según la definición de Dijkstra, dar un ejemplo de qué problema podría resolver en función de su valor de inicialización. a) inicializado a 0 b) inicializado a 1 c) inicializado a un valor positivo mayor que 1

Ej.

6-4 a) establecer un orden de precedencia en la ejecución de zonas de código de distintos hilos.

b) Resolver la exclusión mutua c) Controlar el acceso a un recurso con instancias múltiples

Los semáforos no sólo sirven para resolver el problema de la sección crítica. Enumera al menos otras dos aplicaciones de los semáforos y ponga un ejemplo simple de cada una de estas aplicaciones:

Ej.

6-5

Pueden servir para sincronizar procesos en general, por ejemplo: (1) Establecer un cierto orden (precedencia) en la ejecución de zonas de código de diferentes procesos/hilos Supongamos dos hilos, “hilo1” e “hilo2”.Queremos que “hilo1” ejecute la función “F1” antes que “hilo2” ejecute la función “F2”; Definimos un semáforo compartido “sinc”, inicializado a cero void *hilo1(void *p) { ... F1; V(sinc); ... } void *hilo2(void *p) { ... P(sinc); F2; ... } (2) Establecer que un máximo número de procesos/hilos pueden pasar por un cierto punto del código Tenemos un código que ejecutan concurrentemente muchos hilos Queremos que, como mucho, 5 hilos pasen por un determinado lugar del código, donde se llama a la función “F” Definimos un semáforo compartido “max_5”, inicializado a cinco void *hilo_i(void *p) { ... P(max_5); F; V(max_5); ... }

Page 55: Recopilacion 163 Ejercicios de So2

Explique por qué razón no se puede inicializar un semáforo a un valor negativo.

Ej.

6-6

No se puede inicializar a dicho valor porque estaría indicando que el semáforo tiene un hilo suspendido en su cola, cuando eso no es cierto. El problema lo encontraríamos después si algún hilo o proceso intenta realizar una V antes de que ningún otro intente una P. Si eso ocurriera el sistema no sabría como responder a tal operación V, pues buscaría en la cola de hilos suspendidos en el semáforo y no encontraría nada.

Indique para cada una de las siguientes afirmaciones si es verdadera (V) o falsa (F).

Ej.

6-7 F Un proceso puede suspenderse al realizar una operación V sobre un semáforo.

F Un proceso siempre se suspende al realizar una operación P sobre un semáforo.

V Un proceso siempre se suspende al realizar una operación espera (wait) sobre una variable

condición. F Un proceso siempre se suspende al realizar una operación señal (signal) sobre una variable

condición.

F Un proceso siempre despierta a otro proceso al realizar una operación V sobre un semáforo.

F Un proceso siempre despierta a otro proceso al realizar una operación señal (signal) sobre una variable condición.

Justifique de forma razonada que las operaciones P(S) y V(S) se han de ejecutar de forma atómica para que los semáforos funcionen correctamente.

Ej.

6-8

Las operacione P y V de una semáforo se describen como siguen: P(S) S=S-1 if S<0 then suspender al proceso en la cola de S V(S) S=S+1 If S<=0 then despertar un proceso suspendido en S Supongamos que hay dos procesos Proc1 y Proc2 que protegen sus secciones críticas con un semáforo S compartido e inicializado a 1 como sigue: void Proc1() {…… P(S); Sección critica de Proc1(); V(S); …… }

void Proc2() {…… P(S); Sección critica de Proc2(); V(S); …… }

Si llega Proc1 y ejecuta S=S-1 y hay un cambio de contexto y luego llega Proc2 y ejecuta S=S-1 ambos podrán entrar en sus secciones críticas y no se cumplirá el requisito de exclusión mutua.

Page 56: Recopilacion 163 Ejercicios de So2

Dado el siguiente código, utilice semáforos (con la notación propuesta inicialmente por Dijkstra) para garantizar que los diferentes hilos ejecuten las funciones cuyo nombre empieza por “secuencia-” según el orden que sugieren los números empleados en tales nombres. Si hay varias funciones con el mismo nombre, ninguna de ellas debe empezar antes de que termine la función con el nombre anterior y todas ellas deben haber terminado antes de que empiece la función con el nombre siguiente. Además, ha de asegurarse que las funciones “sc()” se ejecuten en exclusión mutua. Indique qué valor inicial deberán tener los semáforos que haya utilizado.

Ej.

6-9 Valores iniciales de los semáforos:

S1(1), S2(0), S3(0). HILO 1 HILO 2 HILO 3

P(S1); sc(); V(S1); P(S2); secuencia2(); V(S3); P(S1); sc(); V(S1);

P(S1); sc(); V(S1); secuencia1(); V(S2);V(S2);P(S1); sc(); V(S1);P(S3);P(S3); secuencia3(); V(S2);

P(S1); sc(); V(S1); P(S2); secuencia2(); V(S3); P(S2); secuencia4();

Dado el siguiente código, utilice semáforos (con la notación propuesta inicialmente por Dijkstra) para garantizar que los diferentes hilos ejecuten las funciones cuyo nombre empieza por “secuencia-” según el orden que sugieren los números empleados en tales nombres. Si hay varias funciones con el mismo nombre, ninguna de ellas debe empezar antes de que termine la función con el nombre anterior y todas ellas deben haber terminado antes de que empiece la función con el nombre siguiente. Además, ha de asegurarse que las funciones “sc()” se ejecuten en exclusión mutua. Indique qué valor inicial deberán tener los semáforos que haya utilizado.

Ej.

6-10

Valores iniciales de los semáforos: S1(1), S2(0), S3(0) HILO 1 HILO 2 HILO 3

P(S1); sc(); V(S1); P(S2); secuencia2(); V(S3); V(S3); P(S1); sc(); V(S1);

P(S1); sc(); V(S1); Secuencia1(); V(S2); P(S1); sc(); V(S1); P(S3); secuencia3(); V(S2);

P(S1); sc(); V(S1); P(S3); secuencia3(); P(S2); secuencia4();

Dado el siguiente código, y suponiendo que todos los semáforos tenían valor inicial cero, que los hilos presentados están soportados por el núcleo del sistema operativo, se encontraban en la cola de preparados en el orden H1, H2, H3 (es decir, H1 será el primero en obtener la CPU y H3 el último) y que se utiliza un algoritmo de planificación FCFS:

H1 H2 H3 P(S1); P(S3);

P(S2); V(S4);

V(S2); V(S1);

Page 57: Recopilacion 163 Ejercicios de So2

V(S1); V(S3);

V(S3); P(S3);

P(S1); P(S4);

Indique el orden de terminación de dichos hilos. En caso de que algún hilo no termine, indique en qué operación se ha quedado suspendido.

Ej.

6-11

Se siguen estos pasos en la ejecución del código:

H1 realiza un P(S1) y se queda suspendido, pues S1=-1. H2 realiza un P(S2) y también queda suspendido, pues S2=-1. H3 realiza un V(S2), despertando a H2 que quedará el 1º en la cola de preparados. H3 realiza un V(S1), despertando a H1 que quedará el 2º en la cola de preparados. H3 realiza un P(S1) y queda suspendido, pues S1=-1. H2 realiza un V(S4), dejando tal semáforo a 1. H2 realiza un V(S3) y también deja ese semáforo a 1. H2 realiza el P(S3). Como dicho semáforo tiene valor 1, lo deja a cero y termina. H1 realiza un P(S3), lo deja a valor -1 y queda suspendido.

Por tanto, sólo termina H2 en el paso 8. H1 ha quedado suspendido en P(S3) y H3 también está suspendido, en este caso en P(S1).

Dado el siguiente código, y asumiendo que todos los semáforos tenían valor inicial cero, que los hilos presentados están soportados por el núcleo del sistema operativo, se encontraban en la cola de preparados en el orden H1, H2, H3 (es decir, H1 será el primero en obtener la CPU y H3 el último) y que se utiliza un algoritmo de planificación FCFS:

H1 H2 H3 P(S3); P(S1); V(S1); V(S3);

P(S2); V(S4); V(S3); P(S3);

V(S2); V(S1); P(S1); P(S4);

Indique el orden de terminación de dichos hilos. En caso de que algún hilo no termine, indique en qué operación se ha quedado suspendido.

Ej.

6-12

El orden de ejecución de estas operaciones será:

H1 ejecuta P(S3) y queda suspendido, pues S3=-1. H2 ejecuta P(S2) y queda suspendido, pues S2=-1. H3 ejecuta V(S2), reactivando a H2 y dejando S2 a cero. H3 ejecuta V(S1), dejándolo a 1. H3 ejecuta P(S1), dejándolo a cero. H3 ejecuta P(S4) y queda suspendido, pues S4=-1. H2 ejecuta V(S4), reactivando a H3 y dejando S4 a cero. H2 ejecuta V(S3), reactivando a H1 y dejando S3 a cero. H2 ejecuta P(S3) y queda suspendido, pues S3=-1. H3 sale del P(S4) y termina. H1 ejecuta P(S1) y queda suspendido, pues S1=-1.

Por tanto, sólo termina H3 en el paso 10. H2 queda suspendido en el P(S3), mientras H1 queda suspendido en el P(S1).

Se desea sincronizar la ejecución de dos hilos H1 y H2, definidos como sigue:

Page 58: Recopilacion 163 Ejercicios de So2

H1 H2 while (true) {A} while (true) {B}

Donde A y B representan fragmentos de código. Para realizar la sincronización, no se puede asumir nada sobre la duración de los fragmentos A y B, ni sobre la velocidad relativa de H1 y H2. El primer hilo en completar su fragmento de código (A o B) debe espere a que el otro hilo complete el suyo, es decir, ningún hilo empieza una nueva iteración del bucle hasta que el otro ha completado la suya ( el más rápido espera al más lento). Diseñe una solución utilizando semáforos que cumpla dichos requisitos.

Ej.

6-13

Variante 1 Semaforo S1=0; Semaforo S2=0; while(true) { A V(S2); P(S1); } ------------------------- while(true) { B V(S1); P(S2); }

Variante 2 Semaforo S1=1; Semaforo S2=1; while(true) { P(S1); A V(S2); } ------------------------- while(true) { P(S2); B V(S1); }

Comente qué valores posibles tendrían las variables x e y al finalizar la ejecución de los siguientes tres procesos concurrentes. Los valores iniciales son los siguientes: x=1, y=4, S1=1, S2=0 y S3=1.

Proceso A P(S2); P(S3); x = y * 2; y = y + 1; V(S3);

Proceso B P(S1); P(S3); x = x + 1; y = 8 + x; V(S2); V(S3);

Proceso C P(S1); P(S3); x = y + 2; y = x * 4; V(S3); V(S1);

Ej.

614

Existen dos posibles soluciones que vienen dadas por las siguientes secuencias de ejecución: 1) x = x + 1; y = 8 + x; x = y * 2; y = y + 1; ⇒ x = 20, y = 11 2) x = y + 2; y = x * 4; x = x + 1; y = 8 + x; x = y * 2; y = y + 1; ⇒ x = 30, y = 16

¿Cuales son los posibles valores que tomará x como resultado de la ejecución concurrente de los siguientes hilos?

#include <semaphore.h> int main()

Page 59: Recopilacion 163 Ejercicios de So2

#include <pthread.h> #include <stdlib.h> #include <stdio.h> sem_t s1,s2,s3; int x; void *func_hilo1(void *a) { sem_wait(&s1); sem_wait(&s2); x=x+1; sem_post(&s3); sem_post(&s1); sem_post(&s2); } void *func_hilo2(void *b) { sem_wait(&s2); sem_wait(&s1); sem_wait(&s3); x=10*x; sem_post(&s2); sem_post(&s1); }

{ pthread_t h1,h2 ; x = 1; sem_init(&s1,0,1); /*Inicializa a 1*/ sem_init(&s2,0,1); /*Inicializa a 1*/ sem_init(&s3,0,0); /*Inicializa a 0*/ pthread_create(&h1,NULL,func_hilo1,NULL); pthread_create(&h2,NULL,func_hilo2,NULL); pthread_join(h1,NULL); pthread_join(h2,NULL); }

Ej.

6-15

De la ejecución del código anterior se obtiene dos posibles valores de x, x=20 y x=1. En el segundo resultado ocurre un interbloqueo. El semáforo s3, inicializado a 0, obliga a que la sentencia x= 10 * x se ejecute después de la sentencia x = x+1. Si se ejecuta func_hilo1 (sin interrupción) y, a continuación, se ejecuta func_hilo2, el resultado es 20. Si el hilo h1 ejecuta sem_wait(&s1) de func_hilo1 y otro hilo h2 ejecuta sem_wait(&s2) de func_hilo2 antes que h1 ejecute sem_wait(&s2) de func_hilo1 entonces se tiene un interbloqueo y el resultado es x=1.

Dado el siguiente programa escrito en C con primitivas de sincronización POSIX, donde se supone que la función printf() escribe directamente en memoria de vídeo y, por tanto, no llega a implicar la suspensión de los procesos (ya que no realiza una E/S que necesite espera):

#include <pthread.h> #include <stdio.h> int a=0; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_cond_t seg = PTHREAD_COND_INITIALIZER; void *hilo1(void *a1) { 14) pthread_mutex_lock(&mut); 15) printf( “Hilo 1 espera.\n” ); 16) if (a==0) pthread_cond_wait(&cond,&mut); 17) printf( “Hilo 1 avisa.\n” ); 18) pthread_cond_signal(&seg); 19) pthread_mutex_unlock(&mut); 20) printf( "Termina hilo 1.\n" ); 21) pthread_exit(0); } void *hilo2(void *a2) { 10) pthread_mutex_lock(&mut); 11) printf( “Hilo 2 espera.\n” ); 12) if (a!=0) 13) pthread_cond_wait(&seg,&mut);

23) printf( "Termina hilo 2.\n" ); 24) pthread_exit(0); } int main(void) { pthread_t h1,h2; 1) pthread_create(&h2,NULL, hilo2,NULL); 2) pthread_create(&h1,NULL, hilo1,NULL); 3) printf( "Hilos creados.\n" ); 4) pthread_mutex_lock(&mut); 5) pthread_cond_signal(&cond); 6) a=15; 7) pthread_mutex_unlock(&mut); 8) printf( "Esperando hilos.\n" ); 9) pthread_join(h1,NULL); 25) printf( "Hilo 1 terminado.\n" ); 26) pthread_join(h2,NULL); 27) printf( "Hilo 2 terminado.\n" ); 28) return 0;

Page 60: Recopilacion 163 Ejercicios de So2

22) pthread_mutex_unlock(&mut); } Indique qué se mostrará en pantalla al ejecutar el programa anterior, cuántos hilos terminarán la ejecución del código mostrado y en qué orden. Si algún hilo no consigue terminar indique por qué motivo no logra hacerlo. Asuma un algoritmo de planificación FCFS.

Ej.

6-16

Se muestra en el propio listado del enunciado el orden de ejecución de las diferentes instrucciones. El algoritmo de planificación FCFS no sustituye al hilo actualmente en ejecución hasta que éste se suspenda. Además, atiende a los hilos según su orden de llegada a la cola de preparados. Así, en los pasos 1 y 2 se han creado los hilos H2 y H1 y se han dejado en ese orden en dicha cola. En el paso 5, el aviso no tiene ningún efecto, pues no había ningún hilo suspendido en tal condición. En el paso 6, la variable a toma el valor 15 y en el paso 7 se abre de nuevo el mútex. En el paso 9, el hilo principal queda suspendido, pues el hilo 1 todavía no había terminado. Ahora empieza el hilo 2, que puede cerrar el mútex en el paso 10 y se suspenderá en el paso 13, pues “a” era diferente a cero. Con ello vuelve a dejar el mútex abierto. Así, el hilo 1, en el paso 14 podrá cerrarlo y en el paso 16 no se suspenderá pues no se cumple la condición. En el paso 18 avisa a hilo 2, con lo que éste abandona la cola de la condición “seg” y pasa a la del mútex “mut” que de momento está cerrado por hilo 1. En el paso 19, al abrir el mútex, hilo 1 permite que hilo 2 vuelva a estar preparado, convirtiéndose en el propietario de “mut”. En el paso 21, hilo 1 termina y deja preparado al hilo principal. Pero el primero dentro de la cola de preparados era hilo2, que en el paso 22 deja definitivamente a “mut” abierto y en el 24 termina, dejando el paso libre al hilo principal. Este va superando sin suspenderse todas las instrucciones que le quedaban, pues los dos hilos que debía esperar ya han terminado. La salida por pantalla quedaría: Hilos creados. Esperando hilos. Hilo 2 espera. Hilo 1 espera. Hilo 1 avisa. Termina hilo 1. Termina hilo 2. Hilo 1 terminado. Hilo 2 terminado.

Explique qué ventajas aporta el uso de monitores sobre el uso de semáforos.

Ej.

6-17

Como los monitores son una construcción lingüística que ya proporciona de forma automática la exclusión mutua, el programador obtendría con ellos un modelo de programación mucho más sencillo. Si sólo utiliza variables compartidas por los hilos que estén protegidas por los monitores, ya no tendrá que preocuparse por el problema de la sección crítica. Utilizando semáforos lo tendría mucho más complicado.

Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F).

Page 61: Recopilacion 163 Ejercicios de So2

Ej.

6-18

F Una operación V(S) puede llegar a suspender al proceso que la ejecute, dependiendo del valorque tenga el contador del semáforo. Una operación de este tipo jamás suspenderá a un proceso o a un hilo.

F Las variables condición permiten suspender a los hilos fuera de las regiones de código protegidas mediante mútex. Las variables condición deben utilizarse SOLAMENTE dentro de regiones de códigoprotegidas mediante un mútex. De otra forma, deberían devolver un error al realizar laoperación pthread_mutex_wait() (véase la documentación del estándar POSIX). En algunas versiones de Linux no se devuelve tal error, pero el programa acaba abortando al intentaravisar al hilo suspendido.

F Utilizando sólo mútex POSIX se pueden realizar las mismas labores de sincronización quecon los semáforos propuestos por Dijkstra. Aunque sí que sirven para garantizar exclusión mutua, no sirven para la segunda tarea deutilización de los semáforos: garantizar un orden de ejecución entre dos o más funciones dediferentes hilos o procesos. Para ello, deberíamos combinar los mútex con las variables de tipo condición.

F No existe ninguna forma de averiguar, sin suspenderse, si actualmente un mútex tiene un hilopropietario o está abierto. Sí que puede hacerse mediante la función pthread_mutex_trylock().

V Los monitores son un ejemplo de construcción lingüística utilizable para sincronizar hilos oprocesos.

Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F).

Ej.

6-19

F Una operación pthread_mutex_lock() siempre suspende al hilo que la ejecuta. Si el mútex estaba abierto, no le suspende.

F Una operación pthread_cond_signal() puede suspender al hilo que la ejecuta. Por ejemplo, si había hilos suspendidos en la condición utilizada y éstos son todos más prioritarios que el que la ha ejecutado. La operación pthread_cond_signal() nunca suspende. En el ejemplo citado, no se realiza una suspensión, sino una expulsión. La expulsión conlleva pasar al hilo al estado PREPARADO, pero no al SUSPENDIDO.

F La operación pthread_cond_broadcast() no debería emplearse, pues puede llegar a despertar múltiples hilos y ello conllevaría violar la exclusión mutua de la región en la que estaba incluida la pthread_cond_wait(). Falso. El problema citado se evita pasando como segundo argumento la dirección del mútex al realizar una espera y obligando a que el hilo despertado deba readquirir el mútex.

V Al utilizar la operación V(S) sobre un semáforo, no siempre se despertará algún hilo o proceso. Cierto, pues depende del valor que tuviera el contador del semáforo.

V La espera activa puede eliminarse utilizando protocolos de entrada y salida de la sección crítica basados en mútex o en semáforos. Cierto, pues ambas herramientas suspenden al hilo que intenta entrar en la sección cuando ésta ya está ocupada. En los semáforos se debe tomar la precaución de haberlos inicializado con el valor 1.

Razone adecuadamente si el siguiente código es una buena solución al problema de la sección crítica, donde S es un semáforo compartido por todos los procesos o hilos con acceso a dicha sección crítica, con valor inicial 1. Además, se sabe que la política utilizada por el sistema operativo para reactivar a los procesos suspendidos es LIFO (Last In, First Out).

Page 62: Recopilacion 163 Ejercicios de So2

while(1) { P(S); Sección crítica V(S); Sección restante }

Ej.

6-20

No es una buena solución, pues no satisface todas las condiciones exigibles a una solución correcta (exclusión mutua, progreso y espera limitada). De hecho, no satisface la espera limitada, pues un proceso puede haber llegado a su protocolo de entrada y, si no paran de llegar otros procesos a sus respectivos protocolos de entrada, no existiría ninguna cota sobre el número de veces que otros procesos en competencia con él le superan a la hora de acceder a la sección crítica. Esto se dará si el número de procesos o hilos en el protocolo de entrada es siempre igual o superior a 2. El primero que llegó encontrando la sección crítica ocupada no podría entrar jamás.

El siguiente pseudocódigo utiliza semáforos (según la nomenclatura original de Dijkstra), para sincronizar n hilos en una barrera, de forma que todos pueden continuar con la sección posterior únicamente cuando los n han completado la sección previa. Sin embargo, ésta solución es incorrecta. Diseña de nuevo el código entre la sección previa y la posterior.

int n, c=0; // Nombre de fils, comptador sem mutex=1, barrera=0; secció_prévia (...) P(mutex); c=c+1; V(mutex); if (c == n) V(barrera); P(barrera); secció_posterior (...)

Ej.

6-21

El valor del semáforo barrera será -(n-1) cuando todavía falte llegar el hilo n, que lo dejaría con el valor –(n-2) sin conseguir el objetivo propuesto en el enunciado. Para resolverlo, bastaría con añadir esta línea tras la P(barrera) original del enunciado. Con ello se obtiene un efecto similar al de la reactivación en cascada que se daba en algunos modelos de monitor. 6: V(barrera)

Rellene el código siguiente, para proporcionar un protocolo de entrada y salida a la sección crítica del problema de los filósofos, usando espera activa. Úsese la función "int test_and_set (int * objetivo);".

#include <stdlib.h> #include <stdio.h> #include "testandset.h" /* test_and_set: devuelve 1 (cierto) si llave esta siendo utilizada, devuelve 0 (falso) si llave esta libre. */ int test_and_set (int * objetivo);

Page 63: Recopilacion 163 Ejercicios de So2

#define NUMERO_FILOSOFOS 5 int tenedores[NUMERO_FILOSOFOS]; void *Filosofo(void *arg) { int nfilo=(int)arg; int izq=(nfilo+1) % NUMERO_FILOSOFOS; int der=nfilo; while(true) { //PONER AQUI EL protocolo de entrada ***** //comer(); //PONER AQUI EL protocolo de salida ***** } } int main(int argc, char **argv) { pthread_attr_t atrib; pthread_attr_init( &atrib ); for(int i=0;i<NUMERO_FILOSOFOS;i++) tenedores[i]=0; pthread_t hilos[NUMERO_FILOSOFOS]; for (int i=0;i<NUMERO_FILOSOFOS;i++) pthread_create(&hilos[i], &atrib, Filosofo, (void*)i); pthread_join(&hilos[0]); return 0; }

Ej.

6-22

//protocolo de entrada bool pasar; pasar=true; do { while (test_and_set(&tenedores[der])==1) { } if (test_and_set(&tenedores[izq])==1) { tenedores[der]=0; pasar=false; }else pasar=true; } while (!pasar); .... //protocolo de salida tenedores[izq]=0; tenedores[der]=0;

El código que se proporciona a continuación es parte de una solución POSIX al problema de los lectores y escritores.

/****protocolo de entrada para Lectores ***/ void pre_leer() { pthread_mutex_lock(&mutex_monitor); while ( (nesc>0) || (w_esc>0) ) { w_lec++; pthread_cond_wait(&cond,&mutex_monitor); w_lec--; }

/***protocolo de entrada para Escritores ***/ void pre_escribir() { pthread_mutex_lock(&mutex_monitor); while((nesc>0)||(nlec>0)) { w_esc++; pthread_cond_wait(&cond,&mutex_monitor); w_esc--; }

Page 64: Recopilacion 163 Ejercicios de So2

nlec++; pthread_mutex_unlock(&mutex_monitor); } /*****Protocolo de salida para Lectores ***/ void post_leer() { pthread_mutex_lock(&mutex_monitor); nlec--; if (nlec==0) pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex_monitor); }

nesc++; pthread_mutex_unlock(&mutex_monitor); } /****Protocolo de salida para Escritores ***/ void post_escribir() { pthread_mutex_lock(&mutex_monitor); nesc--; pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex_monitor); }

¿Es correcto? En caso de no serlo indicar por qué. En caso de serlo, ¿qué tipo de proceso posee mayor prioridad?

Ej.

6-23

Para que la solución pueda considerarse correcta debe darse exclusión mutua entre escritores, por una parte, y entre lectores y escritores, por otra. En esta solución, si hay un escritor accediendo, “nesc” valdrá 1 y en ese caso, si comprobamos las condiciones de pre_leer() y pre_escribir() veremos que ningún otro escritor, ni lector podrán acceder. Por otra parte, si hay algún lector accediendo, no podrá haber ningún escritor, pues en la condición de pre_escribir() también se comprueba tal cosa. Además, múltiples lectores deben poder acceder al mismo tiempo. Esto también se cumple, pues para entrar un lector no se comprueba en ningún caso si hay o no otros lectores accediendo. Por todo ello, esta solución ES CORRECTA. El tipo de proceso con mayor prioridad en esta solución serán los escritores, pues ningún lector puede entrar mientras haya algún escritor esperando. Por su parte, para que entre un escritor no importa si hay lectores esperando o no.

Escriba el código necesario para implantar una nueva primitiva de sincronización a la que vamos a llamar “barrera”, con sólo tres operaciones accesibles: suspender, liberar e inicializar. Su comportamiento es el siguiente. “Suspender” suspende siempre al hilo que la invoque. “Liberar” reactiva a uno de los hilos si había menos de cinco hilos suspendidos, o a todos ellos si había cinco o más; si no hay hilos suspendidos, no debe tener ningún efecto. “Inicializar” se encarga de inicializar esta herramienta, fijando el número de hilos suspendidos a cero. Implemente el código utilizando mútex y variables condición del estándar POSIX:

Page 65: Recopilacion 163 Ejercicios de So2

Ej.

6-24

typedef struct { pthread_mutex_t mut; pthread_cond_t cond; int hilos; } barrera; void inicializar( barrera *b ) { pthread_mutex_init( b->mut, NULL ); pthread_cond_init( b->cond, NULL ); b->hilos=0; } void suspender( barrera *b ) { pthread_mutex_lock( b->mut ); b->hilos++; pthread_cond_wait( b->cond, b->mut ); b->hilos--; pthread_mutex_unlock( b->mut ); } void liberar( barrera *b ) { pthread_mutex_lock( b->mut ); if (b->hilos<5) pthread_cond_signal( b->cond ); else pthread_cond_broadcast( b->cond ); pthread_mutex_unlock( b->mut ); }

En el código para los lectores y escritores anterior, ¿para qué sirve la variable condición “cond”? ¿Qué ocurre cuando se realiza una operación “pthread_cond_broadcast(&cond);”?

Ej.

6-25

Esa variable condición sirve para suspender a los hilos que, por el tipo de acceso que se esté llevando a cabo en ese momento, no tengan permitido el acceso cuando ellos lo intentaban. Al realizar un aviso múltiple sobre tal condición, se despertarán consecutivamente todos los hilos suspendidos en tal condición. Como la suspensión en la condición está asociada a un bucle while, y en la expresión lógica evaluada en dicho bucle se vuelve a comprobar si dicho tipo de hilo tiene permitido el acceso, la implementación presentada es correcta.

En el siguiente programa se pretende producir y visualizar imágenes usando hilos independientes. Cada hilo producirá una imagen, para ello invertirá un tiempo de cómputo diferente en cada caso. El objetivo del monitor que se pide implementar es sincronizar la sección de visualización de los diferentes hilos. Para ello el monitor ofrece la función “Punto_de_sincronización()”. Cuando un hilo invoca dicha función su comportamiento está definido por las siguientes reglas:

• Si el hilo no es el último que llega al punto de sincronización, deberá suspenderse. • Si ya han llegado todos los demás hilos al punto de sincronización, deberá despertarlos y preparar el

monitor para la siguiente iteración. Se pide implementar la clase “Monitor”.

#include <pthread.h> #include <stdlib.h>

void hilo(void *p) {

Page 66: Recopilacion 163 Ejercicios de So2

#include <stdio.h> #define N 5 class Monitor { // implementar // en la solución }; Monitor mon;

int id = (int) p; while(true) { // Sección A. Produce una imagen del tipo id Producir_Imagen(id); // fin de la sección A (producción de la imagen) mon.Punto_de_sincronizacion(); //Sección B. Visualiza una imagen del tipo id Visualizar_Imagen(id); // fin de la sección B (visualización de la imagen) } } main() { int i; pthread_t hilos[N]; for (int i=0; i<N; i++) pthread_create(&hilos[i],NULL,hilo, (void *) i); for (int i=0; i<N; i++) pthread_join(hilos[i],NULL); }

Ej.

6-26

class Monitor { pthread_mutex_t mutex; pthread_cond_t cond; int cuantos; void Monitor() { pthread_mutex_init(&mutex,NULL); pthread_cond_init(&cond,NULL); cuantos=0; } void Punto_de_sincronizacion() { pthread_mutex_lock(&mutex); if (cuantos<N-1) { cuantos++; pthread_cond_wait(&cond,&mutex); } else { pthread_cond_broadcast(&cond); cuantos=0; } pthread_mutex_unlock(&mutex); } }; //fin del monitor

Se desea controlar el uso de una pantalla TFT de 24” utilizada para visualizar los resultados de dos tipos de procesos, los Pi (P1,….,Pn trabajos de programación) y los Ji (J1,……..,Jm juegos didácticos). El único requisito para utilizar la pantalla es que sea en exclusión mutua por parte de uno u otro proceso.

a) Indique de forma justificada si el siguiente monitor cumple con dicha especificación, teniendo en cuenta que ambos procesos invocarían las funciones, Pre_utilizacion() y Post_utilizacion(), del monitor antes y después de utilizar la pantalla respectivamente.

class Monitor {private:

Page 67: Recopilacion 163 Ejercicios de So2

pthread_mutex_t mutex; int visualizando; public: void Monitor() { pthread_mutex_init(&mutex,NULL); visualizando=0; } void Pre_utilizacion() { pthread_mutex_lock(&mutex); visualizando++; pthread_mutex_unlock(&mutex); } void Post_utilizacion() { pthread_mutex_lock(&mutex); visualizando--; pthread_mutex_unlock(&mutex); } }; //fin del monitor

Ej.

6-27

El monitor no cumple con el requisito de exclusión mutua, se necesita una variable condición para suspender a los procesos, cuando no puedan acceder al monitor. El código quedaría de la siguiente forma: class Monitor {private: pthread_mutex_t mutex; pthread_cond_t cond; int visualizando; public: void Monitor() { pthread_mutex_init(&mutex,NULL); pthread_cond_init(&cond,NULL); visualizando=FALSE; } void Pre_utilizacion() { pthread_mutex_lock(&mutex); if (visualizando==TRUE) pthread_cond_wait(&cond,&mutex); visualizando=TRUE; pthread_mutex_unlock(&mutex); } void Post_utilizacion() { pthread_mutex_lock(&mutex); Visualizando=FALSE; pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); } }; //fin del monitor

b) Modifique el código del monitor propuesto, añadiendo tanto código nuevo como funciones dentro del monitor, de manera que: además de cumplir el requisito de exclusión mutua en el uso de la pantalla tengan prioridad los procesos Pi para su utilización frente a los Ji.

NOTA1: Se trata de una solución en la que puede haber inanición para los procesos Ji sino dejan de llegar procesos Pi. NOTA2: Al tener diferente prioridad los procesos, hay que modificar el interfaz del monitor para que incluya las funciones: Pre_utilizacion_P(), Pre_utilizacion_J(), Post_utilizacion_P() y Post_utilizacion_J()

Page 68: Recopilacion 163 Ejercicios de So2

Ej.

6-28

class Monitor {private: pthread_mutex_t mutex; pthread_cond_t cond; int espera_P, visualizando; public: void Monitor() { pthread_mutex_init(&mutex,NULL); pthread_cond_init(&cond,NULL); espera_P=0; visualizando=FALSE; } void Pre_P() { pthread_mutex_lock(&mutex); while (visualizando==TRUE) {espera_P++;pthread_cond_wait(&cond,&mutex);espera_P--;} visualizando=TRUE; pthread_mutex_unlock(&mutex); } void Post_P() { pthread_mutex_lock(&mutex); visualizando==FALSE; pthread_cond_broadcast(&cond) pthread_mutex_unlock(&mutex); } void Pre_J() { pthread_mutex_lock(&mutex); while ((visualizando==TRUE) || (espera_P>0) ) pthread_cond_wait(&cond,&mutex); visualizando=TRUE; pthread_mutex_unlock(&mutex); } void Post_J() { pthread_mutex_lock(&mutex); visualizando==FALSE; pthread_cond_broadcast(&cond) pthread_mutex_unlock(&mutex); } }; //fin del monitor

Suponemos un tampón de talla MAX compartido por productores y consumidores. En cada operación de inserción/extracción se indica como argumento el número de ítems a insertar/extraer: void deposita (int n) {..} // deposita n items (se retrasa si no hay huecos suficientes) void extrae (int n) {..} //extrae n items (se retrasa si no hay elementos suficientes) Para simplificar, indicamos únicamente el número de items a insertar/extraer, pero no es necesario mantener los valores en el tampón (ignoramos los detalles de implantación del tampón y de paso de valores/obtención de resultados). Diseñe el mecanismo de sincronización necesario utilizando un monitor con primitivas POSIX. Nota: Como ayuda al uso de la nomenclatura se aportan los prototipos de algunas llamadas. pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);

Page 69: Recopilacion 163 Ejercicios de So2

Ej.

6-29

/**** Declaración de variables globales*******/ int MAX /***número máximo de items que caben en el tampón ****/ int e=0; // num. actual elementos pthread_mutex_t mutex; pthread_cond_t lleno, vacio; void deposita (int n) { pthread_mutex_lock(&mutex); while (e+n > MAX) {pthread_cond_wait(&lleno, &mutex);} e+=n; /** código de inserta valores (SE SUPONE HECHO)***/ pthread_cond_broadcast(&lleno); pthread_mutex_unlock(&vacio); } void extrae (int n) { pthread_mutex_lock(&mutex); while (n > e) {pthread_cond_wait(&vacio,&mutex);} e-=n; /**código que extrae y devuelve resultado (SE SUPONE HECHO)**/ pthread_cond_broadcast(&lleno); pthread_mutex_unlock(&mutex); }

En un sistema existen dos procesos: C (cliente) y S (servidor), con el siguiente patrón de comportamiento:

void *Cliente(void *arg) { while(true) { ... x.EsperarS(); ... } }

void *Servidor(void *arg) { while(true) { ... x.EsperarC(); Servicio(); x.ActivarC(); ... } }

Donde x es una instancia de un monitor denominado “ClienteServidor” cuyas operaciones son:

• EsperarS(): suspende el proceso C hasta que el proceso S invoque ActivarC(). • EsperarC(): suspende el proceso S hasta que el proceso C invoque EsperarS(). • ActivarC(): activa el proceso C (suspendido en EsperarS())

Se pide implementar el monitor “ClienteServidor” utilizando mutex y variables condición de POSIX.

/*********************************** Cita C/S con 1 Cliente y 1 Servidor ***********************************/ #include <pthread.h> #include <stdlib.h> #include <stdio.h>

void *Cliente(void *arg){ while(true) { retraso(200+rand()%1); printf("Cliente: invoca servicio.\n"); x.EsperarS(); printf("Cliente: fin invocacion.\n"); }

Page 70: Recopilacion 163 Ejercicios de So2

void retraso(int ms) { struct timespec ts; if(ms>0) { ts.tv_sec = ms/1000; ts.tv_nsec = (ms%1000)*1000000; nanosleep(&ts,NULL); } } Class ClienteServidor { // implementar en la solución } ClienteServidor x; void Servicio() { retraso(10+rand()%1); }

} void *Servidor(void *arg) { while(true) { retraso(100+rand()%1); printf("Servidor: esperar cliente.\n"); x.EsperarC(); printf("Servidor: inicia servicio.\n"); Servicio(); printf("Servidor: fin servicio.\n"); x.ActivarC(); } } int main(int argc, char **argv) { pthread_t cli, ser; pthread_create(&cli,NULL,Cliente,NULL); pthread_create(&ser,NULL,Servidor,NULL); pthread_join(cli, NULL); pthread_join(ser, NULL); }

Ej.

6-30

Nota: La solución presentada solo precisa una variable condición, utilizada para suspender tanto al Cliente como al Servidor para esperarse mutuamente). Es igualmente correcta una solución en la que se utilicen dos variables condición: una para suspender al Cliente y otra para suspender al Servidor. class ClienteServidor { private: int C_esp, S_esp; pthread_cond_t cita; pthread_mutex_t mutex; public: ClienteServidor() { C_esp=0; S_esp=0; pthread_cond_init(&cita,NULL); pthread_mutex_init(&mutex,NULL); } void EsperarS() { pthread_mutex_lock(&mutex); if (S_esp) pthread_cond_signal(&cita); C_esp=1; pthread_cond_wait(&cita,&mutex); C_esp=0; pthread_mutex_unlock(&mutex); } void EsperarC() { pthread_mutex_lock(&mutex); if (!C_esp){ S_esp=1; pthread_cond_wait(&cita,&mutex); S_esp=0; } pthread_mutex_unlock(&mutex); } void ActivarC() { pthread_mutex_lock(&mutex); pthread_cond_signal(&cita); pthread_mutex_unlock(&mutex); } };

Page 71: Recopilacion 163 Ejercicios de So2

El siguiente fragmento de código es una versión simplificada de la solución para el problema de los 5 filósofos basada en variables condición.

1: while(1) 2: { 3: // Entrada en el comedor. 4: pthread_mutex_lock(&mutex_del_contador); 5: while (cuantos_dentro>=4) 6: { 7: printf(“Puedo soñar.\n”); 8: pthread_cond_wait(&cond_x,&mutex_del_contador); 9: printf(“Soy yo.\n”); 10: } 11: cuantos_dentro++; 12: pthread_mutex_unlock(&mutex_del_contador); 13: // Dentro del comedor comiendo. 14: retraso(5000+rand()%1000); // son muy comilones 15: printf(“Como.\n”); 16: // Saliendo del comedor. 17: pthread_mutex_lock(&mutex_del_contador); 18: cuantos_dentro--; 19: pthread_cond_signal(&cond_x); 20: pthread_mutex_unlock(&mutex_del_contador); 21: retraso(40000+rand()%1000); // son muy dormilones. 22: }

Rellene la tabla indicando para cada evento(hilo:linea) de la primera columna: el estado del mutex y los hilos bloqueados en la cola de cada primitiva de sincronización. Nota: Suponga que los hilos, los mutex y las variables condición están correctamente inicializados.

Un sistema está formado por dos recursos: R1 (con tres instancias) y R2 (con dos instancias). En él se ejecutan tres procesos: P1, P2 y P3, inicialmente sin ninguna instancia asignada. En el sistema se da la siguiente secuencia de solicitudes de recurso: P3 pide 1 instancia de R1, P2 pide 1 instancia de R1, P1 pide 1 instancia de R1, P3 pide 1 instancia de R2, P2 pide 1 instancia de R2, P2 pide 1 instancia de R1, P1 pide 2 instancias de R2. Asumiendo que ningún proceso llega a liberar ninguna instancia, dibuje el grafo de asignación de recursos resultante e indique si en el sistema hay o no un interbloqueo.

Ej.

6-32

No hay interbloqueo, pues P3 puede terminar y al hacerlo libera una instancia de cada recurso. Con la instancia de R1 se pueden atender todas las peticiones pendientes de P2, que así también podrá terminar. Gracias a esto quedan libres dos instancias de R1 y R2, con lo que se puede conceder lo que pedía P1 y éste también podrá terminar.

Ej.

6-31

Situación: Existen 5 hilos en marcha. F1..F4 se encuentran ejecutando la línea 14: (F1:14,F2:14,F3:14,F4:14) y F5 acaba de lanzarse.

Traza de los hilos mutex_de_contador Estado del mutex, hilos en cola

cond_x Hilos en cola de la var. condición

F5:1 libre, cola vacía -- F5:4 asignado a F5, cola vacía -- F5:8 libre, cola vacía F5 suspendido en la cola F1:17 asignado a F1, cola vacía F5 suspendido en la cola F1:19 asignado a F1, F5 en la cola -- F1:20 asignado a F5, cola vacía --

Page 72: Recopilacion 163 Ejercicios de So2

Dado un sistema con tres recursos R1, R2, R3 y donde se ejecutan tres procesos que realizan las siguientes operaciones:

P1 P2 P3 1: SOLICITA(R1) 2: LIBERA(R1) 3: SOLICITA(R3) 4: LIBERA(R3)

1: SOLICITA(R2) 2: SOLICITA(R3) 3: LIBERA(R3) 4: LIBERA(R2)

1:SOLICITA(R3) 2: SOLICITA(R2) 3: LIBERA(R2) 4: LIBERA(R3)

Justificar si existe la posibilidad de que se produzca un interbloqueo y la secuencia de operaciones que pueden darse para que ello ocurra (indicar dicha secuencia mediante elementos Px-y donde x es el identificador del proceso e y el nº de operación).

Ej.

6-33

Se puede producir interblequeo al estar anidadas las operaciones SOLICITA en los procesos P2 y P3 sin seguir ninguna ordinalidad en los recursos. P2,1 P3,1 P2,2 bloqueo P3,2 bloqueo P1,1 P1,2 P1,3 bloqueo

Sean tres procesos P1, P2 y P3 que ejecutan el código que se presenta en la siguiente tabla. Suponga que los valores iniciales de los semáforos son: S1=1, S2=2, S3=3. Diga si es posible que se produzca un ínterbloqueo. En caso afirmativo, represente el grafo de asignación de recursos que correspondería a dicha situación y en caso negativo demuéstrelo, haciendo uso de las condiciones de Coffman.

/*Variables globales*/ S1=1; S2=2; S3=3;

T P1 P2 P3

0 P(S1) P(S2) P(S3)

1 P(S2) P(S3) P(S3)

2 P(S3) P(S1) P(S2)

3 A1 B1 C1

4 V(S3) V(S1) V(S2)

5 V(S2) V(S3) V(S3)

6 V(S1) V(S2) V(S3)

Page 73: Recopilacion 163 Ejercicios de So2

Ej.

6-34

Si. P1

P2

P3

S1S2

S3

Sea el estado de asignación de recursos a procesos que se muestra en la figura. Suponiendo que para que P1 finalice necesita apropiarse del una instancia de R2 ¿hay riesgo de interbloqueo de los procesos?

P1

R2

P2

R1

R3

P3

Ej.

6-35

No existe riesgo de interbloqueo, porque no se puede dar una espera circular. No existe ninguna evolución de este estado que termine en un interbloqueo. Después de la petición de P1, el proceso P3, que tiene asignados todos los recursos necesarios puede terminar, con lo cual P1 puede obtener todos los recursos necesarios y terminar también. Una vez ha terminado P1, P2 puede obtener todos los recursos necesarios y terminar. Por tanto, existe al menos una secuencia segura de terminación: P3, P1, P2.

Considere la siguiente solución para el problema de los 5 filósofos. Indique si es posible que se produzca un interbloqueo razonando la respuesta en términos de las condiciones de Coffman.

#include <pthread.h> #include <stdlib.h> #include <stdio.h> #define NUMERO_FILOSOFOS 5 pthread_mutex_t tenedores[NUMERO_FILOSOFOS]; void *Filosofo(void *arg) { int nfilo=(int)arg;

Page 74: Recopilacion 163 Ejercicios de So2

while(true) { int t1, t2; if (nfilo>((nfilo+1)%NUMERO_FILOSOFOS) ){ t1=(nfilo+1)%NUMERO_FILOSOFOS; t2=nfilo; } else { t1=nfilo; t2=(nfilo+1)%NUMERO_FILOSOFOS; } pthread_mutex_lock(&tenedores[t1]); pthread_mutex_lock(&tenedores[t2]); usleep(400000); // un tiempo comiendo... pthread_mutex_unlock(&tenedores[t1]); pthread_mutex_unlock(&tenedores[t2]); usleep(300000); // un tiempo pensando... } }

int main(int argc, char **argv) { pthread_t th_filo[NUMERO_FILOSOFOS]; // Se inicializan los tenedores for(int i=0;i<NUMERO_FILOSOFOS;i++) pthread_mutex_init(&tenedores[i],NULL); //Se arrancan los filosofos for(int i=0;i<NUMERO_FILOSOFOS;i++) pthread_create(&th_filo[i], NULL,Filosofo,(void*)i); for(int i=0;i<NUMERO_FILOSOFOS;i++) pthread_join(&th_filo[i]); }

Ej.

6-36

Con el código presentado, el filósofo 4 será el único que haga cierta la condición (nfilo>((nfilo+1)%NUMERO_FILOSOFOS)). Por ello, todos los filósofos cogerán primero el tenedor de su izquierda antes que el de su derecha (o al revés, eso depende de cómo se distribuyan los filósofos y los tenedores en la mesa), excepto el filósofo 4. Esto evitará el interbloqueo, pues si todos los filósofos intentaran coger a la vez su primer tenedor, todos lo conseguirían excepto uno (el 4 o el 0). Gracias a esto, cuando intentaran coger el segundo tenedor, uno de ellos lo podría hacer (el “otro” vecino del que antes se quedó sin nada, es decir, el que no tenía el tenedor que él intentó coger en primer lugar). Como resultado, no podrá haber espera circular, pues habrá un filósofo que no retendrá nada (el 4 o el 0) y otro que no esperará nada (el 3 o el 4), rompiendo así el ciclo dirigido. Otra explicación más sencilla se basa en la propiedad de prevención de interbloqueos que decía que para evitar la espera circular había que ordenar todos los recursos y pedirlos siguiendo el orden establecido. En el ejemplo presentado, todos los filósofos están pidiendo los tenedores en orden creciente. Por ello, no podrá cerrarse jamás un ciclo dirigido.

En un sistema se encuentran en ejecución cinco procesos: P0, P1, P2, P3 y P4 que utilizan los recursos R0, R1, R2, R3, R4 y RC. Inicialmente la cantidad de recursos de cada tipo es la indicada en la siguiente tabla:

Recurso R0 R1 R2 R3 R4 RCCantidad 1 1 1 1 1 n

El perfil de ejecución de un proceso Pi es distinto para los procesos pares e impares y es el indicado en la tabla siguiente:

Perfil de los procesos pares Perfil de los procesos impares while TRUE do Peticion(RC); Peticion(Ri); Peticion(R((i+1) mod 5))); UsoDeLosRecursos(); Libera(Ri); Libera(R((i+1) mod 5)); Libera(RC); SeccionRestante(); end while;

while TRUE do Peticion(RC); Peticion(R((i+1) mod 5))); Peticion(Ri); UsoDeLosRecursos(); Libera(Ri); Libera(R((i+1) mod 5)); Libera(RC); SeccionRestante(); end while;

Page 75: Recopilacion 163 Ejercicios de So2

Nota: Cada petición de recursos solicita un solo ejemplar del recurso en concreto y bloquea al proceso solicitante si el recurso no está disponible. Suponiendo que n=5 ¿Es posible que en el sistema se produzca un interbloqueo? Razone la respuesta. En caso afirmativo describa un escenario.

Ej.

6-37

No se producirá, ya que los procesos no intentan coger todos ellos los mismos recursos en el mismo orden por lo que es imposible que se cumpla la condición de espera circular (La condición de retención y espera puede llegar a cumplirla el proceso 4, pues puede haber obtenido el recurso 4 y después quedarse esperando el recurso 0, previamente concedido al proceso 0).

Analice la siguiente implementación para las operaciones P y V sobre un semáforo S:

P(S) V(S) { while (test_and_set( &llave )) { }; while (S<0) {}; S=S-1; llave = false; }

{ while (test_and_set( &llave )) { }; S=S+1; llave =false; }

Exponga de manera justificada, analizando con detalle cada una de las condiciones estudiadas, si dicha implementación es válida para resolver el problema de la sección crítica. Considere que inicialmente S=1 y llave=false.

Ej.

6-38

Las condiciones que debe garantizar una solución a la sección crítica son: 1) Exclusión mutua 2) Progreso 3) Espera limitada.

1) Exclusión mutua, si hay un proceso ejecutando sección crítica ningún otro puede ejecutar la suya. Como en el protocolo de entrada se ejecuta la operación P(S), esto dejaría S= 0 y llave= false. Por tanto otro proceso podría ejecutar el protocolo de entrada y pasar a la sección crítica.

2) El problema está, en que un proceso se puede quedar en el bucle “while (S<0) “ con lo cual no suelta la llave y por tanto ningún otro podrá ejecutar el protocolo de salida para variar el valor de S.

3) Esta solución no garantiza la espera limitada, ya que el primero que consiga la llave, entra. Compruebe si la siguiente solución al productor/consumidor es correcta o no. Si no es correcta, indique cuál es el problema y modifique el código para que funcione.

void *func_prod(void *p) { int item; while(1) { item = producir(); P(mutex); while (contador == N) /*bucle vacio*/ ; buffer[entrada] = item; entrada = (entrada + 1) % N; contador = contador + 1; V(mutex); } }

void *func_cons(void *p) { int item; while(1) { P(mutex); while (contador == 0) /*bucle vacio*/ ; item = buffer[salida]; salida = (salida + 1) % N; contador = contador - 1; V(mutex); consumir(item); } }

Page 76: Recopilacion 163 Ejercicios de So2

E

j. 6-

39 No es correcta la solución.

Esta solución no funciona, porque un productor o consumidor que haya cerrado “mutex” y se quede en el bucle “while”, deja a todos los demás hilos bloqueados!! Se definen dos semáforos más “lleno”, inicializado a cero, suspende a los consumidores cuando esté vacío “vacio”, inicializado a N, suspende a los productores cuando el buffer está lleno

Se define un evento como un tipo de datos, utilizado para la sincronización de procesos, que admite las siguientes dos operaciones

• sleep() suspende la ejecución del proceso que la invoca • wakeup() desbloquea a todos los procesos previamente suspendidos sobre ese evento

Implemente el concepto de evento mediante un monitor

Ej.

6-40

SOLUCION class Evento { private: pthread_cond_t c; pthread_mutex_t m; public: Evento() { c=PTHREAD_COND_INITIALIZER; m=PTHREAD_MUTEX_INITIALIZER; } void sleep(int x) { pthread_mutex_lock(&m); pthread_cond_wait(&c,&m); pthread_mutex_unlock(&m); } void wakeup() { pthread_mutex_lock(&m); pthread_cond_broadcast(&c); pthread_mutex_unlock(&m); } }

Sea un sistema con 3 recursos R1, R2 y R3 de los cuales se dispone de 2, 3 y 2 instancias respectivamente. Partiendo de una situación en la que todos los recursos se encuentran disponibles, en dicho sistema se ejecutan tres procesos P1, P2 y P3 que realizan la siguiente traza de peticiones:

1. P1: solicita R1 2. P1: solicita R1 3. P2: solicita R2 4. P2: solicita R1 5. P3: solicita R2 6. P3: solicita R2 7. P1: solicita R2 8. P1: solicita R3 9. P1: solicita R3

Page 77: Recopilacion 163 Ejercicios de So2

Indique de forma razonada si es posible o no que se produzca un interbloqueo, para ello represente el grafo de asignación de recursos que correspondería a dicha situación y en caso negativo demuéstrelo, haciendo uso de las condiciones de Coffman.

Ej.

6-41

R1 R2 R3

P1 P2 P3

R1 R2 R3

P1 P2 P3

R1 R2 R3

P1 P2 P3

No hay interbloqueos porque no hay espera circular, y hay un proceso que tiene todos los recursos (P3) necesarios para continuar y por tanto no retiene y espera. Secuencia posible: finaliza P3, luego P1 y después P2.

Siguiendo la estructura del microshell desarrollado en prácticas, dado un proceso (“ush”) y dos procesos hijos de aquel (que denominaremos “hijo1” e “hijo2”) creados para ejecutar la orden

$ ls | sort Se pide determinar el estado (entre Ejecutándose, Terminado, Bloqueado, Zombie o Huérfano) en que se encuentran dichos procesos hijos si:

Ej.

6-42

Situación hijo1 hijo2El proceso “ush” ya ha terminado su ejecución, mientras que los hijos aún continúan.

H H

El padre y el “hijo2” no cierran descriptores y siguen en ejecución. T B El padre no ha ejecutado ningún “wait” cuando los hijos ya han terminado su ejecución.

Z Z

El padre y el “hijo1” no cierran descriptores y no terminan. E B

Se pretende codificar una barrera que no sea sobrepasada hasta que no hayan llegado N hilos, es decir, en la que los N-1 primeros hilos deban pararse hasta la llegada del hilo N. El código siguiente forma parte de dicha barrera la cual se encuentra dentro de un bucle infinito. La barrera está implementada usando semáforos POSIX.

sem_t mutex, barrera; int c= 0; // Comtador de hilos sem_init(&mutex, 0, 1); sem_init(&barrera, 0, 0); ... while (1) { // Para cada hilo // Sección fuera de la barrera 1: c++; 2: if (c == N) sem_post(&barrera); 3: sem_wait(&barrera); 4: sem_post(&barrera); // Sección dentro de la barrera 5: c--; 6: if (c == 0) sem_wait(&barrera); // Secció posterior } // Bucle infinito

a) Indique cuáles de las líneas numeradas del código tendrían que estar protegidas entre sem_wait(&mutex) y sem_post(&mutex) obligatoriamente para evitar posibles condiciones de carrera.

Page 78: Recopilacion 163 Ejercicios de So2

b) Suponiendo que el código está correctamente protegido frente a condiciones de carrera, comente la corrección funcional del mismo. Observe que dicha barrera se puede utilizar repetidamente por un mismo hilo, ya que su uso se encuentra en el interior de un bucle infinito.

Ej.

6-43

(a): Deberían estar protegidas por el mutex los grupos de líneas 1-2 y 5-6. En estas líneas se puede producir una condición de carrera. En ningún caso hay que proteger las líneas 3 ni 4.

(b): La barrera es totalmente correcta a no ser que se use en el interior de un bucle infinito como es el caso. Con este código un hilo “rápido” podría finalizar su sección dentro de la barrera, su sección posterior y volver a entrar por la barrera modificando “c” antes de que los demás hilos finalicen su código dentro de la barrera. En este supuesto, la solución propuesta es totalmente incorrecta.

Un ordenador tiene n unidades de cinta idénticas (n instancias de un mismo recurso), con p procesos compitiendo por ellas. Cada proceso realiza el siguiente programa: solicita 2 unidades de cinta, escribe datos en ellas y las libera. ¿Cuál es el número máximo de procesos p para asegurar que el sistema está libre de interbloqueos? Justifíquelo con las condiciones de Coffman.

Ej.

6-44

Se trata de impedir la espera circular, para ello lo único que hay que garantizar, es que el número de procesos sea igual o inferior al número de cintas menos uno. Esto garantiza que siempre hay un proceso que pueda tener las dos cintas que necesita y no se produzca interbloqueos. P<= n-1

Un semáforo limitado se define como un semáforo cuyo contador no puede superar un valor máximo predefinido n (si el contador vale n, una operación V supone esperar). Se pide escribir un monitor que implemente las operaciones P y V sobre un semáforo limitado completando el siguiente código:

Ej.

6-45

SOLUCION class SemafLimitado { private: int c,n; pthread_cond_t a,b; pthread_mutex_t m; public: SemafLimitado(int n0, int c0) { //maximo y valor inicial c=c0; n=n0; a = b = PTHREAD_COND_INITIALIZER; m=PTHREAD_MUTEX_INITIALIZER; } void P() { pthread_mutex_lock(&m); while (c<=0) pthread_cond_wait(&a,&m); c--; pthread_cond_signal(&b); pthread_mutex_unlock(&m); } void V() { pthread_mutex_lock(&m); while (c>=n) pthread_cond_wait(&b,&m); c++; pthread_cond_signal(&a); pthread_mutex_unlock(&m); } }

Page 79: Recopilacion 163 Ejercicios de So2

7. Problemas de sincronización

7.1 Dificultad de los ejercicios

Nivel de dificultad Ejercicios Principiante

Medio Avanzado

7.2 Ejercicios Tenemos un puente levadizo sobre un río con las siguientes condiciones de utilización:

• Los barcos tienen siempre prioridad de paso, pero para levantar el puente han de esperar a que no haya ningún coche sobre él.

• Los coches pueden utilizar el puente si no hay ningún barco pasando (en cuyo caso el puente estará levantado) o esperando.

Escribir los algoritmos para barcos y coches utilizando:

a) semáforos b) monitores.

Ej.

7-1 a) Solución con semáforos:

#include <pthread.h> #include <semaphore.h> #define NUM_COCHES 5 #define NUM_BARCOS 5 void bajar_puente(void) {} void pasar(void) {} void levantar_puente(void) {} /*-----------------------------------------------------*/ /* Variables compartidas. */ /*-----------------------------------------------------*/ sem_t turno; /* Necesario para dar prioridad */ /* a los barcos. */ sem_t mutex1; /* Exclusión mutua entre coches.*/ sem_t mutex2; /* Exclusión mutua entre barcos.*/ sem_t libre; /* Indica si el puente está */ /* libre. */ int barcos = 0; /* Número de barcos intentando */ /* pasar. */ int coches = 0; /* Número de coches intentando */ /* pasar. */ /*-----------------------------------------------------*/ /* Código de los coches. */ /*-----------------------------------------------------*/ void *coche(void *arg) { sem_wait(&turno); /* Verificar si podemos pasar. */ sem_post(&turno); sem_wait(&mutex1); /* Exclusión mutua en el acceso */ /* a la variable coches. */ coches++; /* Incrementar el nº de coches. */ if (coches==1) /* Bloquear paso a los barcos, o*/ sem_wait(&libre); /* a los siguientes coches, si */ /* ya hubiese barcos. */ sem_post(&mutex1); /* Fin de la S.C. sobre coches. */

Page 80: Recopilacion 163 Ejercicios de So2

bajar_puente(); /* Funciones ya definidas. */ pasar(); sem_wait(&mutex1); /* S.C. para acceder a coches. */ coches--; /* Decrementar nº de coches. */ if (coches==0) /* Si es el último, libera el */ sem_post(&libre); /* paso a los barcos. */ sem_post(&mutex1); /* Fin de la S.C. sobre coches. */ } /*-----------------------------------------------------*/ /* Código de los barcos. */ /*-----------------------------------------------------*/ void *barco(void *arg) { sem_wait(&mutex2); /* S.C. para acceder a "barcos".*/ barcos++; /* Incrementar nº de barcos. */ if (barcos==1) { /* Si es el primero, bloquea el */ sem_wait(&turno); /* paso a los coches, o al resto*/ sem_wait(&libre); /* de barcos, mientras haya co- */ } /* ches. */ sem_post(&mutex2); /* Fin de la S.C. sobre barcos. */ levantar_puente(); pasar(); sem_wait(&mutex2); /* S.C. para acceder a barcos. */ barcos--; /* Decrementar nº de barcos. */ if (barcos==0) { /* Si es el último, libera el */ sem_post(&turno); /* paso a los coches. */ sem_post(&libre); } sem_post(&mutex2); /* Fin de la S.C. sobre barcos. */ } int main(void) { /* Identificadores de los hilos.*/ pthread_t id_barcos[NUM_BARCOS]; pthread_t id_coches[NUM_COCHES]; int i,j; /* Contador para la creación de */ /* hilos. */ /* Inicializar todos los semá- */ /* foros como no compartidos, y */ /* con valor 1. */ sem_init(&turno, 0, 1); sem_init(&mutex1, 0, 1); sem_init(&mutex2, 0, 1); sem_init(&libre, 0, 1); /* Crear los hilos. */ i=NUM_BARCOS; j=NUM_COCHES; while (i>0 || j>0) { if (i>0) { pthread_create( &id_barcos[i], NULL, barco, NULL ); i--; } if (j>0) { pthread_create( &id_coches[j], NULL, coche, NULL ); j--; } } /* Esperar su terminación. */ for (i=0; i<NUM_BARCOS; i++) pthread_join( id_barcos[i], NULL ); for (j=0; j<NUM_COCHES; j++) pthread_join( id_coches[j], NULL ); return 0; /* Terminar. */ } b) Solución mediante monitores (pseudo-Pascal):

Page 81: Recopilacion 163 Ejercicios de So2

TYPE puente_levadizo = MONITOR; VAR coches, barcos, barcos_bloqueados : integer; OkCoche, OkBarco : condition; PROCEDURE ENTRY entrar_coche; BEGIN (* Para dar prioridad a los barcos, los coches se suspenden si hay algún barco esperando. *) IF (barcos>0) OR (barcos_bloqueados>0) THEN OkCoche.wait; coches := coches + 1; OkCoche.signal; (* Liberamos en cascada al resto de coches, si los hay. *) bajar_puente END; PROCEDURE ENTRY salir_coche; BEGIN coches := coches – 1; (* Si ya no quedan coches, dejamos pasar a los barcos. *) IF coches=0 THEN OkBarco.signal END; PROCEDURE ENTRY entrar_barco; BEGIN (* Si ya hay coches pasando, el barco se suspende. *) IF coches>0 THEN BEGIN barcos_bloqueados := barcos_bloqueados + 1; OkBarco.wait; barcos_bloqueados := barcos_bloqueados – 1 END; barcos := barcos + 1; (* Reactivamos al resto de barcos, en cascada. *) OkBarco.signal; levantar_puente END; PROCEDURE ENTRY salir_barco; BEGIN barcos := barcos – 1; (* Si es el último barco, deja pasar a los coches. *) IF barcos=0 THEN OkCoche.signal END; BEGIN (* Inicialización del monitor. *) barcos := 0; coches := 0; barcos_bloqueados := 0 END; Solución mediante monitores (implantación en POSIX): #include <pthread.h> /*-----------------------------------------------------*/ /* Variables globales. */ /*-----------------------------------------------------*/ pthread_mutex_t mutex_monitor; pthread_cond_t ok_barco; pthread_cond_t ok_coche; int coches = 0; int barcos = 0; int barcos_bloqueados = 0; /*-----------------------------------------------------*/ /* Procedimientos de entrada. */ /*-----------------------------------------------------*/ void entrar_coche(void) { pthread_mutex_lock(&mutex_monitor); /* Damos prioridad a los barcos.*/ /* Si alguno ha pedido entrar y */ /* está suspendido, bloqueará al*/ /* coche que intenta entrar. */ while ((barcos>0) || (barcos_bloqueados>0)) pthread_cond_wait(&ok_coche, &mutex_monitor);

Page 82: Recopilacion 163 Ejercicios de So2

coches++; /* Es preferible utilizar broad-*/ /* cast en lugar de signal, pues*/ /* así garantizamos que se re- */ /* evalúe la condición. Con esta*/ /* instrucción reactivamos al */ /* resto de coches. */ pthread_cond_broadcast(&ok_coche); bajar_puente(); pthread_mutex_unlock(&mutex_monitor); } void salir_coche(void) { pthread_mutex_lock(&mutex_monitor); coches--; if (coches==0) pthread_cond_broadcast(&ok_barco); pthread_mutex_unlock(&mutex_monitor); } void entrar_barco(void) { pthread_mutex_lock(&mutex_monitor); while (coches > 0) { barcos_bloqueados++; pthread_cond_wait(&ok_barco, &mutex_monitor); barcos_bloqueados--; } barcos++; pthread_cond_broadcast(&ok_barco); levantar_puente(); pthread_mutex_unlock(&mutex_monitor); } void salir_barco(void) { pthread_mutex_lock(&mutex_monitor); barcos--; if (barcos==0) pthread_cond_broadcast(&ok_coche); pthread_mutex_unlock(&mutex_monitor); } int main(void) { /* En el programa principal, en-*/ /* tre otras, habría que inicia-*/ /* lizar las herramientas de */ /* sincronización. */ pthread_mutex_init(&mutex_monitor, NULL); pthread_cond_init(&ok_barco, NULL); pthread_cond_init(&ok_coche, NULL); ... }

Disponemos de dos caminos que separan dos sentidos de circulación: Norte y Sur. Ambos caminos convergen en uno solo cuando se trata de cruzar un río. Existen coches que quieren pasar de norte a sur, y de sur a norte. En cualquier momento, sólo pueden atravesar el puente uno o más coches que vayan en el mismo sentido (no se mezclan coches que van en sentidos opuestos). Implementar una solución mediante semáforos.

Ej.

7-2

#include <pthread.h> #include <semaphore.h> /*-----------------------------------------------------*/ /* Variables compartidas. */ /*-----------------------------------------------------*/ sem_t turno,libre; /* Necesarios para fijar el sen-*/ /* tido de paso. */

Page 83: Recopilacion 163 Ejercicios de So2

sem_t mutex1; /* Exclusión mutua en el acceso */ /* a la variable “norte”. */ sem_t mutex2; /* Ídem para la variable “sur”. */ int norte=0; /* Nº de vehículos en dirección */ /* norte-->sur. */ int sur=0; /* Nº de vehículos en dirección */ /* sur-->norte. */ /*-----------------------------------------------------*/ /* Funciones utilizadas en los hilos. */ /*-----------------------------------------------------*/ void pasar_puente(void) {} /*-----------------------------------------------------*/ /* Hilo que representa a un vehículo en sentido N-->S */ /*-----------------------------------------------------*/ void *norte_sur(void *arg) { sem_wait(&turno); /* Exclusión mutua a la hora de */ /* pedir el paso. */ sem_wait(&mutex1); /* S.C. para acceder a “norte”. */ norte++; if (norte==1) /* El primer vehículo del norte */ sem_wait(&libre); /* no deja pasar a los del sur. */ sem_post(&mutex1); /* Fin de la S.C. de “norte”. */ sem_post(&turno); /* Otro hilo podrá pedir paso. */ pasar_puente(); /* Proceder a cruzar. */ sem_wait(&mutex1); /* S.C. para acceder a “norte”. */ norte--; if (norte==0) /* Si es el último, permite que */ sem_post(&libre); /* los del sur pasen. */ sem_post(&mutex1); /* Fin de la S.C. de “norte”. */ } /*-----------------------------------------------------*/ /* Hilo que representa a un vehículo en sentido S-->N */ /*-----------------------------------------------------*/ void *sur_norte(void *arg) { sem_wait(&turno); /* Exclusión mutua a la hora de */ /* pedir el paso. */ sem_wait(&mutex2); /* S.C. para acceder a “sur”. */ sur++; if (sur==1) /* El primer vehículo del sur no*/ sem_wait(&libre); /* deja pasar a los del norte. */ sem_post(&mutex2); /* Fin de la S.C. de “sur”. */ sem_post(&turno) ; /* Otro hilo podrá pedir paso. */ pasar_puente(); /* Proceder a cruzar. */ sem_wait(&mutex2); /* S.C. para acceder a “sur”. */ sur--; if (sur==0) /* Si es el último, permite que */ sem_post(&libre); /* los del norte pasen. */ sem_post(&mutex2); /* Fin de la S.C. de “sur”. */ } /*-----------------------------------------------------*/ /* En el programa principal (no mostrado) tendríamos */ /* estas instrucciones de inicialización: */ /* sem_init(&turno, 0, 1); */ /* sem_init(&libre, 0, 1); */ /* sem_init(&mutex1, 0, 1); */ /* sem_init(&mutex2, 0, 1); */ /*-----------------------------------------------------*/

Una barbería dispone de un sillón para el corte de pelo, un barbero y una cantidad N de sillas en la sala de espera para los clientes. Si no hay clientes que atender, el barbero se echa una siesta. Cuando en este caso llega un cliente, tiene que despertar al barbero. Si llegan más clientes mientras el barbero atiende a alguien, pueden sentarse (si quedan sillas libres en la antesala) o irse a pasear (si no quedan sillas disponibles). Mientras el barbero atiende a un cliente, este último espera detenido hasta que finalice el corte de pelo.

Page 84: Recopilacion 163 Ejercicios de So2

El problema consiste en diseñar un programa adecuado para que se logre la sincronización adecuada. Debe resolverse implementando en POSIX el equivalente a un monitor, respetando la interfaz que se presenta en el ejemplo de uso siguiente:

#include <pthread.h> typedef enum {LLENO, CORTADO} respuesta; /*-----------------------------------------------------*/ /* Código del hilo que representa a un cliente. */ /*-----------------------------------------------------*/ void *cliente(void *arg) { respuesta res; do { /* Operación del monitor. */ entrar_barberia(&res); if (res==LLENO) dar_una_vuelta(); } while (res!=CORTADO); } /*-----------------------------------------------------*/ /* Código del hilo que representa al barbero. */ /*-----------------------------------------------------*/ void *barbero(void *arg) { while (1) { esperar_cliente(); /* Operación del monitor. */ cortar_pelo(); /* No pertenece al monitor, sólo*/ /* simula el corte. */ fin_corte(); /* Operación del monitor. */ } } int main(void) { int i; pthread_t id_clientes[NUM_CLIENTES]; pthread_t id_barbero; /* Crear clientes y barbero. */ pthread_create(&id_barbero, NULL, barbero, NULL); for (i=0; i<NUM_CLIENTES; i++) pthread_create(&id_clientes[i], NULL, cliente, NULL); /* Esperar a los clientes. */ for (i=0; i<NUM_CLIENTES; i++) pthread_join(id_clientes[i], NULL); return 0; /* Terminar. */ }

Ej.

7-3

El código que aparece a continuación formaría parte del programa mostrado en el enunciado. /*-----------------------------------------------------*/ /* Variables globales. */ /*-----------------------------------------------------*/ /* Para exclusión mutua en las */ /* operaciones del monitor. */ pthread_mutext_t m_monitor = PTHREAD_MUTEX_INITIALIZER; int n_clientes = 0; /* Número de clientes en la bar-*/ /* bería. */ /* Sillas de espera para los */ /* clientes (basta con una con- */

Page 85: Recopilacion 163 Ejercicios de So2

/* dición). */ pthread_cond_t silla = PTHREAD_COND_INITIALIZER; /* Sillón para el corte de pelo.*/ pthread_cond_t sillon = PTHREAD_COND_INITIALIZER; /* Litera del barbero. */ pthread_cond_t dormir = PTHREAD_COND_INITIALIZER; /*-----------------------------------------------------*/ /* Operaciones del monitor. */ /*-----------------------------------------------------*/ void entrar_barberia(respuesta *resp) { pthread_mutex_lock( &m_monitor ); if (n_clientes > N) *resp = LLENO; /* No cabe, que lo vuelva a in- */ /* tentar. */ else { n_clientes++; if (n_clientes==1) /* Despertar al barbero. */ pthread_cond_signal( &dormir ); /* Si no somos el primero, habrá*/ /* que esperar. */ else pthread_cond_wait( &silla, &m_monitor ); /* Esperar a que acabe el corte.*/ pthread_cond_wait( &sillon, &m_monitor ); *resp = CORTADO; } pthread_mutex_unlock( &m_monitor ); } void esperar_cliente(void) { pthread_mutex_lock( &m_monitor ); if (n_clientes == 0) /* Echar una siesta si no hay */ /* clientes. */ pthread_cond_wait( &dormir, &m_monitor ); else /* Sino, llamar al primero. */ pthread_cond_signal( &silla ); pthread_mutex_unlock( &m_monitor); } void fin_corte(void) { pthread_mutex_lock( &m_monitor ); /* Decirle al cliente que baje */ /* del sillón y salga de la bar-*/ /* bería (cuando quiera). */ pthread_cond_signal( &sillon ); n_clientes--; pthread_mutex_unlock( &m_monitor ); }

Sea un proceso que crea tres tipos de hilos que ejecutan las funciones Proceso1, 2 y 3, respectivamente. Cada uno de ellos ejecuta una sección crítica SC1, SC2 y SC3 tal como se muestra en el código de las dos tablas. Nótese que existen muchos procesos de los tres tipos 1, 2 y 3 y que todos ellos antes de acceder a su sección crítica ejecutan una función de entrada y cuando finalizan ejecutan una función de salida (comportamiento tipo monitor).

Page 86: Recopilacion 163 Ejercicios de So2

#include <semaphore.h> #include <pthread.h> #include <stdlib.h> #include <stdio.h> #define NUM_HILOS 20 void EntraA(); void EntraB(); void EntraC(); void SaleA(); void SaleB(); void SaleC(); pthread_mutex_t mutex; pthread_cond_t mutexA, mutexB, mutexC; int nA, nB, nC; void *Proceso1() { while(1) { SeccionRestante1; EntraA(); SC1; SaleA(); } }

void *Proceso2() { while(1) { SeccionRestante2; EntraB(); SC2; SaleB(); } } void *Proceso3() { while (1) {SeccionRestante3; EntraC(); SC3; SaleC(); } }

void EntraA() { pthread_mutex_lock(&mutex); while (nB>0) pthread_cond_wait(&mutexA, &mutex); nA=nA+1; pthread_mutex_unlock(&mutex); } void SaleA() { pthread_mutex_lock(&mutex); nA=nA-1; pthread_cond_signal(&mutexA); pthread_cond_signal(&mutexB); pthread_cond_signal(&mutexC); pthread_mutex_unlock(&mutex); } void EntraB() { pthread_mutex_lock(&mutex); while((nA>0)||(nC>0)) pthread_cond_wait(&mutexB,&mutex); mutexB.wait; nB=nB+1; pthread_mutex_unlock(&mutex); } void SaleB() { pthread_mutex_lock(&mutex); nB=nB-1; pthread_cond_signal(&mutexA); pthread_cond_signal(&mutexB); pthread_cond_signal(&mutexC); pthread_mutex_unlock(&mutex); }

void EntraC() { pthread_mutex_lock(&mutex); while ((nC>0) || (nB>0)) pthread_cond_wait(&mutexC, &mutex); nC=nC+1; pthread_mutex_unlock(&mutex); } void SaleC() { pthread_mutex_lock(&mutex); nC=nC-1; pthread_cond_signal(&mutexA); pthread_cond_signal(&mutexB); pthread_cond_signal(&mutexC); pthread_mutex_unlock(&mutex); } int main() { int i; pthread_t P1,P2,P3; nA:=0;nB:=0;nC:=0; pthread_mutex_init(&mutez,NULL); pthread_cond_init(&mutexA,NULL); pthread_cond_init(&mutexB,NULL); pthread_cond_init(&mutexC,NULL); for(i=1;i<NUM_HILOS; i++) {pthread_create(&P1,NULL,Proceso1,NULL); pthread_create(&P2,NULL,Proceso2,NULL); pthread_create(&P3,NULL,Proceso3,NULL); } for(i=1;i<NUM_HILOS; i++) {pthread_join(&P1,NULL); pthread_join(&P2,NULL); pthread_join(&P3,NULL); } }

a) Indique qué tipo de sincronización proporciona el monitor anterior. Para ello, conteste en la siguiente

tabla en la cual debe colocar una X en el elemento (i,j) cuando la sección crítica de un proceso de tipo i se pueda ejecutar concurrentemente con la sección crítica de un proceso de tipo j.

Page 87: Recopilacion 163 Ejercicios de So2

b) Modifique los procedimientos Proceso1, Proceso2 y Proceso3 (dejando intacta la implementación del las funciones EntraA, EntraB, EntraC, SaleA, SaleB, SaleC ) para conseguir el tipo de sincronización que se muestra en la siguiente tabla.

SC1 SC2 SC3 SC1 X X --- SC2 X --- --- SC3 --- --- X

Ej.

7-4

a) SC1 SC2 SC3SC1 X --- X SC2 --- X --- SC3 X --- ---

b)

#include <semaphore.h> #include <pthread.h> #include <stdlib.h> #include <stdio.h> #define NUM_HILOS 20 void EntraA(); void EntraB(); void EntraC(); void SaleA(); void SaleB(); void SaleC(); pthread_mutex_t mutex; pthread_cond_t mutexA, mutexB, mutexC; int nA, nB, nC; void *Proceso1() { while(1) { SeccionRestante1; EntraA(); SC1; SaleA(); } }

void *Proceso2() { while(1) { SeccionRestante2; EntraC(); SC2; SaleC(); } } void *Proceso3() { while (1) {SeccionRestante3; EntraB(); SC3; SaleB(); } }

El código que se proporciona a continuación constituye una solución al problema de un puente levadizo.

void entrar_coche(void) { sem_wait(&turno); sem_wait(&mutex1); c = c +1; if (c==1) sem_wait(&libre); sem_post(&mutex1); sem_post(&turno); bajar_puente(); entrar_puente(); } /* fin entrar_coche*/ void salir_coche(void) {

void entrar_barco(void) { sem_wait(&mutex2); b = b+1; if (b==1) { sem_wait(&turno); sem_wait(&libre); } sem_post(&mutex2); levantar_puente(); entrar_puente(); } /* fin entrar_barco*/ void salir_barco(void)

Page 88: Recopilacion 163 Ejercicios de So2

salir_puente; sem_wait(&mutex1); c = c-1; if (c==0) sem_post(&libre); sem_post(&mutex1); } /*fin salir_coche*/

{ salir_puente; sem_wait(&mutex2); b = b-1; if (b==0){ sem_post(&turno); sem_post(&libre); } sem_post(&mutex2); } /*fin salir_barco*/

Las condiciones de corrección mínimas que satisface esta solución son: • Los barcos pueden cruzar el puente cuando esté levantado. Para levantarlo no debe haber ningún coche

cruzando. • Los coches pueden cruzar el puente cuando esté bajado. Para bajarlo no debe haber ningún barco cruzando. Inicialmente, todos los semáforos valen 1 y los contadores valen 0. Conteste a las siguientes preguntas suponiendo que los semáforos tienen asociada una cola FIFO.

a) Indique el estado de las colas asociadas a los semáforos y los procesos que están utilizando el puente después de invocar las siguientes operaciones (se considerará que el instante final de la operación es cuando ésta acaba o cuando el proceso que la invoca se suspende).

• C0 invoca entrar_coche. • B0 invoca entrar_barco. • C1 invoca entrar_coche. • B1 invoca entrar_barco.

b) Indique el estado de las colas asociadas a los semáforos y los procesos que están utilizando el puente después de invocar las siguientes operaciones:

• Todos los coches / barcos que han entrado en el puente en el apartado anterior han invocado salir_coche/ salir barco.

• C2 invoca entrar_coche. • B2 invoca entrar_barco. • C3 invoca entrar_coche.

c) ¿Qué ocurre si, hay coches en el puente, hay barcos esperando y un nuevo coche invoca entrar_coche?. d) ¿Qué ocurre si hay barcos cruzando bajo el puente, hay coches esperando y un nuevo barco invoca entrar_barco? e) ¿Qué ocurre si hay coches y barcos esperando y el último barco bajo el puente invoca salir_puente? f) Como modificaría el código para que los coches pasaran de uno en uno (no necesita reescribirlo todo).

Page 89: Recopilacion 163 Ejercicios de So2

Ej.

7-5 a) C0 pasa, B0 se detiene en libre pero coge el turno, C1 se bloquea en turno que ha cogido B0, B1 no

puede entrar porque B0 no ha liberado mutex2. Es decir:

variable contador Procesos suspendidos mutex1 1 mutex2 -1 B1turno -1 C1libre -1 B0En el puente se encuentra el proceso C0

b) Al salir C0 da paso en primer lugar a B0 al realizar P(libre), B1 pasa al liberar B0 mutex2, C2 llega y se suspende en turno, que aún no ha sido liberado por los barcos, B2 llega y pasa, C3 llega y se suspende en turno.

Variable contador Procesos suspendidos mutex1 1 mutex2 1 turno -3 C1, C2, C3libre 1 En el puente se encuentran B0, B1, B2

c) El coche se suspende en el semáforo turno. d) El nuevo barco cruza bajo el puente. e) Situación imposible. f) En entrar_coche se suprime la llamada sem_post(&mutex1) y en salir_coche se suprime la llamada sem_post(&mutex1). Al no liberar el semáforo mutex1 hasta después de salir del puente, si llega un nuevo coche y tiene el turno, se suspenderá en mutex1.

Se pretende hacer un programa concurrente para simular un aeropuerto de pequeño tamaño en el que se dispone de una única pista, que utilizan tanto los aviones que despegan como los que aterrizan. El comportamiento ha de ser el siguiente:

- Sólo un avión puede estar utilizando la pista en cada momento, si un avión llega a la pista con intención de despegar y en ese momento hay otro avión despegando o aterrizando, entonces se ha de esperar.

- Igualmente, si un avión llega desde el aire y en ese momento hay otro avión utilizando la pista (despegando o aterrizando), también habrá de esperar.

- Cuando un avión termina de utilizar la pista, si había algún otro avión esperando podrá pasar a utilizarla, teniendo prioridad los aviones que desean aterrizar (la inanición de éstos podría llevar a que agotaran su combustible).

Se pide implementar el programa basándose en el monitor que se muestra a continuación. Completar las operaciones de acceso del monitor que aparecen en el código. Se pueden añadir más variables si fueran necesarias.

Ej.

7-6

/**** variables globales ****/ pthread_mutex_t mutex_monitor = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond_despegar = PTHREAD_COND_INITIALIZER; pthread_cond_t cond_aterrizar = PTHREAD_COND_INITIALIZER; int ocupado=FALSE; int w_aterriza=0; /*** métodos del monitor ****/ void inicia_despegue(){ pthread_mutex_lock(&mutex_monitor); while(ocupado || w_aterriza>0)

Page 90: Recopilacion 163 Ejercicios de So2

pthread_cond_wait(&cond_despegar, &mutex_monitor); ocupado=TRUE; pthread_mutex_unlock(&mutex_monitor); } void acaba_despegue(){ pthread_mutex_lock(&mutex_monitor); ocupado=FALSE; if (w_aterriza>0) pthread_cond_broadcast(&cond_aterrizar); else pthread_cond_broadcast(&cond_despegar); pthread_mutex_unlock(&mutex_monitor); } void inicia_aterrizaje(){ pthread_mutex_lock(&mutex_monitor); while(ocupado) { w_aterriza++; pthread_cond_wait(&cond_aterrizar,&mutex_monitor); w_aterriza--; } ocupado = TRUE; pthread_mutex_unlock(&mutex_monitor); } void acaba_aterrizaje(){ pthread_mutex_lock(&mutex_monitor); ocupado=FALSE; if (w_aterriza>0) pthread_cond_broadcast(&cond_aterrizar); else pthread_cond_broadcast(&cond_despegar); pthread_mutex_unlock(&mutex_monitor); }

Suponga que se encuentra en una discoteca donde está estropeado el servicio de las chicas y todos deben compartir el de los chicos. Se pretende establecer un protocolo de entrada al servicio utilizando semáforos en el que se cumplan los siguientes requisitos:

• Sólo puede haber una chica cada vez en el servicio. • Puede haber más de un chico a la vez, pero con un máximo de cinco. • Las chicas tienen preferencia sobre los chicos. Esto quiere decir que si un chico está esperando y

llega una chica, ésta debe pasar antes. Complete el código que se propone a continuación para que se cumpla dicho protocolo, utilice las variables que ya han sido declaradas e inicializadas en el mismo.

Ej.

7-7 #include <semaphore.h>

sem_t libre, turno, cinco,mutex_os,mutex_as; int num_chicos, num_chicas; //*****CHICOS**************// void chicos() { sem_wait(&cinco); sem_wait(&turno); sem_post(&turno); sem_wait(&mutex_os); /***COMPLETAR****/ /***ENTRADA CHICOS****/

//*****CHICAS**************// void chicas() { sem_wait(&mutex_as); /***COMPLETAR****/ /***ENTRADA CHICAS****/ num_chicas++; if (num_chicas==1)sem_wait(turno) sem_post(&mutex_as); sem_wait(&libre); utiliza_servicio();

Page 91: Recopilacion 163 Ejercicios de So2

num_chicos++; if (num_chicos==1)sem_wait(libre) sem_post(mutex_os); utiliza_servicio(); sem_wait(&mutex_os); /***COMPLETAR****/ /***SALIDA CHICOS****/ num_chicos--; if (num_chicos==0)sem_post(libre) sem_post(&mutex_os); sem_post(&cinco); }

sem_post(&libre); sem_wait(&mutex_as); /***COMPLETAR****/ /***SALIDA CHICAS****/ num_chicas--; if (num_chicas==0)sem_post(turno); sem_post(&mutex_as); } /***********************// int main() { int hilos_chicas, hilos_chicos; num_chicos=0; num_chicas=0; sem_init(&mutex_os,0,1); sem_init(&mutex_as,0,1); sem_init(&libre,0,1); sem_init(&turno,0,1); sem_init(&cinco,0,5); … … }

Diseñe un monitor que garantice la utilización de una piscina (recurso P) a la que sólo acceden entrenadores de natación (hilos entrenadores E) y niños (hilos niños N), según la siguiente normativa:

• Los hilos E invocan la operación 'entraE' para acceder a la piscina, permanecen en ella (ejecutan código arbitrario), y luego invocan 'saleE' para abandonarla

• Los hilos niños N invocan la operación 'entraN' para acceder a la piscina, permanecen en ella (ejecutan código arbitrario), y luego invocan 'saleN' para abandonarla.

• Los entrenadores, hilos E, pueden utilizar la piscina P simultáneamente. • Los niños, hilos N, sólo la pueden utilizar la piscina P si hay al menos un entrenador, hilo E,

utilizándola. • Los hilos N nunca pueden utilizar la piscina P sin hilos E.

o E....E ok o E....E N....N ok o N....N ilegal

Se pide diseñar una solución utilizando monitores para garantizar las condiciones anteriores.

Page 92: Recopilacion 163 Ejercicios de So2

Ej.

7-8

class Monitor {private: pthread_mutex_t mutex; pthread_cond_t cond_E, cond_N; int nE, nN; public: void Monitor() { pthread_mutex_init(&mutex,NULL); pthread_cond_init(&cond_E,NULL); pthread_cond_init(&cond_N,NULL); nE=0;nN=0; } void entraE() { pthread_mutex_lock(&mutex); ++nE; pthread_cond_broadcast(&cond_N); pthread_cond_broadcast(&cond_E); pthread_mutex_unlock(&mutex); } void saleE() { pthread_mutex_lock(&mutex); while(nN>0 && nE==1) pthread_cond_wait(&cond_E,&mutex); --nE; pthread_mutex_unlock(&mutex); } void entraN() { pthread_mutex_lock(&mutex); while(nE==0) pthread_cond_wait(&cond_N,&mutex); ++nN; pthread_mutex_unlock(&mutex); } void saleN() { pthread_mutex_lock(&mutex); --nN; if (nN==0) pthread_cond_broadcast(&cond_E); pthread_mutex_unlock(&mutex); } }; //fin del monitor

Sea una central de distribución eléctrica a la que se pueden conectar generadores y consumidores. Por requerimientos técnicos, sólo pueden conectarse un máximo de Ng generadores, mientras que no hay límite en la conexión de consumidores, aunque para evitar sobrecargas, sólo se aceptan nuevas conexiones de consumidores mientras el número de consumidores no supere el triple de los generadores conectados en ese

Page 93: Recopilacion 163 Ejercicios de So2

momento. Se pide programar un monitor que sincronice las peticiones de conexión de generadores y consumidores. Nota: Como ayuda al uso de la nomenclatura se aportan los prototipos de algunas llamadas. pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);

Ej.

7-9 /**** variables globales ****/

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond_gene = PTHREAD_COND_INITIALIZER; pthread_cond_t cond_cons = PTHREAD_COND_INITIALIZER; int n_gene=0; Int n_cons=0; /*** funciones a implementar ****/ void conexión_generador() { pthread_mutex_lock(&mutex); while(n_gene>=Ng) pthread_cond_wait(&cond_gene,&mutex); n_gene++; pthread_cond_broadcast(&cond_cons); pthread_mutex_unlock(&mutex); } void desconexion_ generador() { pthread_mutex_lock(&mutex); n_gene--; pthread_cond_signal(&cond_gene); pthread_mutex_unlock(&mutex); } void conexión_consumidor() { pthread_mutex_lock(&mutex); while(n_cons>=3*n_gene) pthread_cond_wait(&cond_cons,&mutex); n_cons++; pthread_mutex_unlock(&mutex); } void desconexión_consumidor() { pthread_mutex_lock(&mutex); n_cons--; pthread_cond_signal(&cond_cons); pthread_mutex_unlock(&mutex); }

Page 94: Recopilacion 163 Ejercicios de So2

Una línea de comunicaciones dispone de N canales para realizar llamadas telefónicas. Cada llamada telefónica usa un canal y lo libera al finalizar la conversación. Las llamadas telefónicas pueden ser de tres tipos:

• EMERGENCIA • VIP • ORDINARIA

Se pide diseñar un monitor que sincronice el uso del canal cumpliendo las siguientes reglas: • Toda llamada será servida mientras haya canales libres. • En el caso en que todos los canales estén ocupados, la prioridad de acceso a la comunicación será:

EMERGENCIA > VIP > ORDINARIA

Nota: Únicamente completar el código de las funciones “solicita_llamada()” y “fin_llamada()”.

Ej.

7-10

#include <stdio.h> #include <unistd.h> #include <pthread.h> #define N 10 #define N_HILOS 25 #define EMERGENCIA 0 #define VIP 1 #define ORDINARIA 2 int cuantos_dentro; int esperando[3]; int detenerse[3]; pthread_mutex_t m; pthread_cond_t cond_tipo[3]; void solicita_llamada(int tipo) { pthread_mutex_lock(&m); while( (cuantos_dentro>=N) || (detenerse[tipo]) ) { esperando[tipo]++; detenerse[VIP]=(esperando[EMERGENCIA]>0); detenerse[ORDINARIA]=((esperando[EMERGENCIA]>0)||(esperando[VIP]>0)); pthread_cond_wait(&cond_tipo[tipo],&m); esperando[tipo]--; detenerse[VIP]=(esperando[EMERGENCIA]>0); detenerse[ORDINARIA]=((esperando[EMERGENCIA]>0)||(esperando[VIP]>0)); } cuantos_dentro++; pthread_mutex_unlock(&m); } void fin_llamada() { pthread_mutex_lock(&m); cuantos_dentro--; if(esperando[EMERGENCIA]>0) {pthread_cond_broadcast(&cond_tipo[EMERGENCIA]);} else if (esperando[VIP]>0) {pthread_cond_broadcast(&cond_tipo[VIP]);} else if (esperando[ORDINARIA]>0) {pthread_cond_broadcast(&cond_tipo[ORDINARIA]);} pthread_mutex_unlock(&m); } void *abonado(void *arg) { int tipo;

Page 95: Recopilacion 163 Ejercicios de So2

tipo=((int)arg); solicita_llamada(tipo); printf("llamando tipo=%d: dentro=%d \n", tipo, cuantos_dentro); usleep(1000000); fin_llamada(); printf("Fin_llam tipo=%d: dentro=%d \n", tipo, cuantos_dentro); } main() { pthread_t h[N_HILOS]; pthread_attr_t attr; int i; pthread_attr_init(&attr); pthread_mutex_init(&m,NULL); cuantos_dentro=0; for(i=0;i<3;i++){ esperando[i]=0; detenerse[i]=0; pthread_cond_init(&cond_tipo[i],NULL); } for(i=0;i<N_HILOS;i++) { pthread_create(&h[i],&attr,abonado,(void*)EMERGENCIA); pthread_create(&h[i],&attr,abonado,(void*)VIP); pthread_create(&h[i],&attr,abonado,(void*)ORDINARIA); } for(i=0;i<N_HILOS;i++) { pthread_join(h[i],NULL); } }

8. Ejercicios sobre prácticas.

8.1 Dificultad de los ejercicios

Nivel de dificultad Ejercicios Principiante

Medio Avanzado

8.2 Ejercicios Recordando lo realizado en la práctica de microshell sobre el tratamiento de señales, se pide escribir las sentencias que implementan la siguiente gestión de señales:

• El interprete de comandos debe ignorar las señales SIGINT, SIGQUIT, SIGTTIN y SIGTTOU • Los procesos hijos deben ignorar las señales SIGINT y SIGQUIT y el resto de señales deben tener el

tratamiento por omisión.

Escribir el código del intérprete y de los hijos

Page 96: Recopilacion 163 Ejercicios de So2

Ej.

8-1

// codigo del interprete struct sigaction act; act.sa_handler = SIG_IGN; /*senyal interrupcion del teclado CTRL-C */ sigaction(SIGINT, &act, NULL); /*senyal terminacion del teclado */ sigaction(SIGQUIT, &act, NULL); /*proceso background intentando leer */ sigaction(SIGTTOU, &act, NULL); /*proceso background intentando escribir */ sigaction(SIGTTIN, &act, NULL); ... //codigo del hijo struct sigaction act; act.sa_handler = SIG_DFL; /* El procesamiento de SIGINT y SIGQUIT se hereda del padre, por lo que no hay que retocarlo. */ sigaction(SIGTTOU, &act, NULL); sigaction(SIGTTIN, &act, NULL);

Considere la siguiente solución al problema de los 5 filósofos. Analice las condiciones de Coffman. ¿Es posible que se produzcan interbloqueos?. En caso afirmativo modifique el código para que no se produzcan.

void *Filosofo(void *arg) { int nfilo=(int)arg; int tenedor=0; while(true) { if (nfilo==NUMERO_FILOSOFOS-1) tenedor=0; else tenedor=nfilo+1; pthread_mutex_lock (&tenedores[nfilo]); p_w->getView()->setEstadoFilosofo(nfilo, EF_TENEDOR_DERECHO); if (pthread_mutex_trylock (&tenedores[tenedor])!=EBUSY) { pthread_mutex_lock (&tenedores[tenedor]); p_w->getView()->setEstadoFilosofo(nfilo, EF_TENEDOR_IZQUIERDO); p_w->getView()->setEstadoFilosofo(nfilo, EF_COMIENDO); retraso(400+rand()%100); // un tiempo comiendo... // Ya esta saciado, deja los tenedores p_w->getView()->setEstadoFilosofo(nfilo, EF_DURMIENDO); pthread_mutex_unlock (&tenedores[nfilo]); pthread_mutex_unlock (&tenedores[tenedor]); retraso(400+rand()%100); // un tiempo dormido... } else pthread_mutex_unlock (&tenedores[nfilo]); } }

Page 97: Recopilacion 163 Ejercicios de So2

Ej.

8-2

Sí es posible que se produzca un bloqueo. Si “trylock” da como resultado que el recurso está ocupado (EBUSY) entonces se intenta coger mediante un “lock” bloqueante, que evidentemente va a suspender el proceso. Se dan por tanto todas las condiciones de Coffman y existe riego de interbloqueo. Para resolverlo bastaría con eliminar el segundo pthread_mutex_lock(), situado tras el “trylock”. Lo anterior es una utilización inadecuada de “trylock”. Casi parece un error de programación al intentar escribir un código correcto. Se dará como válida también la respuesta en la que se supone una utilización adecuada de “trylock”: En este último caso, no son posibles los interbloqueos porque se incumple la condición de retener y esperar (asignación gradual de recursos).

Explica para qué sirve la estructura CMDFD utilizada en la práctica de microshell. typedef struct { int infd; int outfd; }CMDFD[PIPELINE]

Ej.

8-3 La estructura CMDFD contiene los descriptores de entrada y salida de cada uno de los procesos

que conforman un procesamiento en tubería. Lo mejor es un ejemplo:

El siguiente fragmento de código corresponde a proyecto “microshell” desarrollado en prácticas. Teniendo en cuenta el código que se incluye y la funcionalidad que debe implementar la función “pipeline()” se pide escribir el código que se ha omitido en las posiciones indicadas con los comentarios “Completar 1”, “Completar 2” y “Completar 3”.

typedef struct { int infd; int outfd;

$ ls | grep txt | sort > hola

ls grep txt sort

tubo1 tubo2hola

/dev/tty

tubo1

tubo1

tubo2

tubo2

hola

cmdfd[0]

cmdfd[1]

cmdfd[2]

/dev/null si background

Page 98: Recopilacion 163 Ejercicios de So2

}CMDFD[PIPELINE]; CMDFD fd_pipes; CMDFD * pipeline(CMD * ordenes) { int nordenes; int i; int pfdes[2]; int fdin, fdout; iniciar_pipes(); nordenes=ordenes->num_ordenes; for(i=0;i<nordenes-1;i++) { pipe(pfdes); // Completar 1 } if(ordenes->fich_entrada[0]!='\0') { if ( (fdin=open(ordenes->fich_entrada,O_RDONLY)) == -1) {perror("No puedo abrir el fichero de entrada");exit(1);} // Completar 2 } else { // Completar 3 } // sigue el código que no se incluye en el ejercicio. return(&fd_pipes); } /* pipeline */

Ej.

8-4 Completar 1

fd_pipes[i].outfd=pfdes[1]; fd_pipes[i+1].infd=pfdes[0];

Completar 2

fd_pipes[0].infd=fdin;

Completar 3

a) fd_pipes[0].infd=0; b) No se hace nada ya que fd_pipes[i].infd se ha inicializado a cero en la función “iniciar_pipes”

Dado el siguiente fragmento de código del programa de prácticas “microshell” correspondiente a la función ejecutar:

int ejecutar (int nordenes , int *nargs , char **ordenes , char ***args , int bgnd) { for (i=0; i <nordenes; i++) { …… if ((pidhijo=fork())==0) { if (cmdfd[i].infd !=0 )

Page 99: Recopilacion 163 Ejercicios de So2

{ close(0); dup(cmdfd[i].infd); } If (cmdfd[i].outfd !=1 ) { close(1); dup(cmdfd[i].outfd); } cerrar_fd(); execvp(ordenes[i], args[i]); } cerrar_fd(); …. } …. }

Indicar:

a) El código ejecutado por el padre y el ejecutado por el hijo (márquelo en el mismo código) b) ¿Por qué se hace un close antes de las dos llamadas dup? c) ¿Por qué se llama a cerrar_fd en los puntos donde aparece?

Ej.

8-5 a.- el padre ejecuta sólo el último cerrar_fd().

b.- dup duplica el descriptor que se le pasa como parámetro y lo hace en el primer hueco de la tabla de descriptores que encuentra. Esta búsqueda del hueco se realiza por orden creciente de descriptores comenzando por 0. Por lo tanto el close se aplica sobre el descriptor de fichero destino de la duplicación. La combinación close;dup es equigalente a dup2. c.- la función cerrar_fd cierra todos los descriptores de fichero abiertos (tubos y ficheros) excepto los descriptores 0, 1 y 2. La llamada a esta función se realiza después de duplicar adecuadamante los descriptores que usará el proceso en cuestión y así evitar que un proceso mantenga descriptores de fichero abiertos que no va a utilizar. Esto evita que un proceso se quede bloqueado en una llamada read sobre un tubo al mantener otro proceso (o él minsmo) un descriptor de escritura sobre ese tubo que no va a usar.

Conocido el problema de los cinco filósofos y la solución del comedor:

void * Filosofo (void * arg) { int nfilo=(int)arg; while (true) { ptread_mutex_lock(&mutex_del_contador); while (cuantos_dentro>=4) pthread_cond_wait(&puerta_del_comedor, &mutex_del_contador); cuantos_dentro++; pthread_mutex_unlock(&mutex_del_contador); ……. pthread_mutex_lock(&mutex_del_contador); cuantos_dentro--; pthread_cond_signal(&puerta_del_comedor); pthread_mutex_unlock(&mutex_del_contador); } }

Page 100: Recopilacion 163 Ejercicios de So2

Explicar:

a) ¿Para qué sirve la variable cuantos_dentro? b) ¿Para qué sirve la variable puerta_comedor? c) ¿Para qué sirve la variable mutex_del_contador?

Ej.

8-6

a.- Para registrar el número de filósofos a los que se les ha permitido pasar a comer al interior del comedor. b.- Para retener a los filósofos a los que no se les permite comer. c.- Para que no se produzca condición de carrera en el acceso a “cuantos_dentro”. Además el uso de variables condición obliga al uso de mutex. Esto es necesario por que la evaluación de la condición booleana es siempre parte de la sección crítica de acceso a las variables internas del monitor.

Construya una línea de órdenes, tal que, al ejecutarla con el microshell desarrollado en prácticas, genere una estructura cmdfd con el siguiente contenido:

infd outfd

Cmdfd[0] 0 4

Cmdfd[1] 3 6

Cmdfd[2] 5 7

Considere que se dispone del siguiente fichero regular:

Nombre fichero Descriptor

fichero1.txt 7

Ej.

8-7

Cualquier linea con tres órdenes sin redirección de entrada y redirección de salida al fichero fichero1.txt es correcta. Por ejemplo: ls -l | grep hola | sort > fichero1.txt

En el código fuente del microshell, la función pipeline tiene como función principal inicializar e incluir en el vector cmdfd los descriptores de fichero necesarios para que las funciones de redirección trabajen correctamente. Tenga en cuenta la estructura típica de microshell desarrollado en el laboratorio y calcule, justificando su respuesta, el número de descriptores que tendrá abierto el proceso microshell (padre) y a qué dispositivos o archivos apuntan cada uno de ellos tras la invocación a la función pipeline. En particular, calcule estos descriptores cuando el usuario introduzca las siguientes órdenes por el teclado.

a) $cat < file1 | grep perico & b) $cat | grep perico c) $cat > file1 & d) $cat <file1 | grep perico >> file2 &

Nota: No es necesario que dibuje el vector cmdfd. Ejemplo: 0,1,2 /dev/tty ; 3 “fichentrada.txt”.

Page 101: Recopilacion 163 Ejercicios de So2

Ej.

8-8

En todos los casos tiene abiertos: 0 /dev/tty ;1 /dev/tty ;2 /dev/tty a) 3 file1; (4,5) tubo b) (3,4) tubo c) 3 /dev/null ; 4 file1 d) 3 file1; 4 file2 ; (5,6) tubo

En el problema de los 5 filósofos (filósofos numerados como 0..4, tenedores numerados como 0..4), suponemos que cada filósofo pide primero el tenedor de menor índice, y luego el de índice mayor. Indica cómo afecta esa decisión al problema de los interbloqueos, y porqué.

Ej.

8-9

La ordenación parcial de recursos hace que no se produzca espera circular, que es una condición de Coffman, y por lo tanto es imposible que se produzca un interbloqueo.

Al problema tradicional de los 5 filósofos, se le han incorporado 5 nuevos filósofos, en total 10 filósofos. Los 5 primeros filósofos, numerados del 0 al 4, siguen necesitando 2 tenedores para comer, mientras que los 5 filósofos nuevos, numerados del 5 al 9, sólo necesitan un tenedor cada uno, en concreto el tenedor derecho. La mesa que tienen para comer los 10 filósofos, es la tradicional con 5 platos y 5 tenedores, de manera que: para los filósofos 0 y 5 su tenedor derecho es el 0 y su izquierdo el 1, para los filósofos 1 y 6 su tenedor derecho es el 1 y su izquierdo el 2, etc…. Tomando como base la solución al problema tradicional de los cinco filósofos (en la que cada filósofo necesita dos tenedores para comer), donde se establece un protocolo de entrada al comedor de manera que sólo entran 4 filósofos simultáneamente, y cuyo código se presenta a continuación.

1 void filo(void * arg) 2 { 3 nfilo=(int) arg; 4 while(1){ 5 //PROTOCOLO ENTRADA en el comedor 6 pthread_mutex_lock(&m); 7 while(dentro==4) 8 pthread_cond_wait(&vc,&m); 9 dentro++; 10 pthread_mutex_unlock(&m); 11 //**FIN PROTOCOLO DE ENTRADA*******// 12 Si (nfilo<5) intento coger dos tenedores 13 Si (nfilo>4) intento coger el derecho 14 /***…como comiendo…**/ 15 Si (nfilo<5) dejo dos tenedores 16 Si (nfilo>4) dejo el tenedor derecho 17 //PROTOCOLO DE SALIDA del comedor. 18 pthread_mutex_lock(&m) 19 dentro--; 20 pthread_cond_signal(&vc); 21 pthread_mutex_unlock(&m); 22//**FIN PROTOCOLO DE ENTRADA*******// 23 …pienso en si existo… 24 } 25 }

Page 102: Recopilacion 163 Ejercicios de So2

Se pide: Modifique los protocolos de entrada y salida al comedor que aparecen en el pseudocódigo anterior. Para ello añada/elimine las instrucciones necesarias de manera que puedan comer los 10 filósofos con los 5 tenedores en las condiciones descritas, controlando la entrada al comedor para evitar interbloqueo y con una utilización lo más eficiente posible de los tenedores. Justifique su implementación.

Ej.

8-10

A) Protocolo de entrada

Pthread_mutex_lock(&m); while ((dentro==4)&&(nfilo<5)) pthread_cond_wait(&vc,&m); if (nfilo<5) dentro++; pthread_mutex_unlock(&m); o bien… lock(&m); if (nfilo<5) dentro++; while ((dentro==4)) pthread_cond_wait(&vc,&m); pthread_mutex_unlock(&m); B) Protocolo de salida

Pthread_mutex_lock(&m) if (nfilo<5) dentro--; pthread_cond_signal(&vc); pthread_mutex_unlock(&m);

Considere el microshell desarrollado en el laboratorio. Si el usuario que utiliza el microshell introduce por teclado la orden: “cat < file1 | sort > file2” y teniendo en cuenta el siguiente pseudocódigo correcto. ¿Qué ocurriría si se omite la línea 18? Justifique su respuesta.

Page 103: Recopilacion 163 Ejercicios de So2

1 int Ejecucion(….) 2{ 3 for (ct=0;ct<nordenes;ct++) 4 { 5 if (!fork()) // hijo 6 7 { 8 Redirigir_entrada(ct); 9 Redirigir_salida(ct); 10 Cerrar_fd(); 11 Res=Exec(......); 12 if (res<1) { 13 fprintf(2,”No puedo ejecutar la orden: %d\n”,ct); 14 exit(-1); 15 } 16 } 17 } 18 cerrar_fd(); 19 if (bg==FALSE) while(wait()!=-1); 20 return OK; 21}

Ej.

8-11

Si el padre no cierra los descriptores, en particular, los del tubo, el proceso asociado con la orden sort no terminará puesto que nunca recibirá una marca EOF del tubo. Esto es así porque un proceso que lee de un tubo nunca recibirá un EOF si hay algún descriptor de escritura sobre el tubo abierto. En este caso particular, la orden cat <file1 terminará cuando termine de leer el fichero file1 y dejará su contenido sobre el tubo cerrando, cuando termina el proceso, todos sus descriptores. En ese momento el proceso asociado con sort >file2 recibiría EOF y terminaría su trabajo pero no lo recibe porque el microshell mantiene el tubo abierto.

El siguiente fragmento de código corresponde a un programa con tres hilos: el primero ejecuta el código de la función agrega(), y suma una unidad a la variable global V. El segundo, que ejecuta la función resta(), decrementa en una unidad la misma variable V. Ambas funciones realizan el mismo numero de iteraciones. La función retraso(1) realiza un retraso de 1 milisegundo. void *agrega (void *argumento) { long int cont; int temp; while (test_and_set(&llave)==1) {}; for (cont = 0; cont < REPETICIONES; cont = cont + 1) { temp=V; temp=temp+1; V = temp; } llave=0; printf("-------> Fin AGREGA (V = %ld)\n", V); pthread_exit(0); } void *resta (void *argumento) { long int cont; int temp; retraso(1);

Page 104: Recopilacion 163 Ejercicios de So2

for (cont = 0; cont < REPETICIONES; cont = cont + 1) { while (test_and_set(&llave)==1) {}; temp=V; temp=temp-1; V = temp; llave=0; } printf("-------> Fin RESTA (V = %ld)\n", V); pthread_exit(0); } Se pide dibujar el cronograma de ejecución de los dos hilos, suponiendo que REPETICIONES=5 y que cada iteración del bucle for tarda 1 milisegundo en ejecutarse,

Ej.

8-12

Solución: Como agrega tiene el test_and_set fuera del bucle for, ejecuta las 10 iteraciones seguidas. Se ejecuta primero agrega debido al retraso inicial de resta. Cuando agrega termina, resta puede acceder y ejecuta las 10 iteraciones seguidas

En el problema de los filósofos, cada tenedor es un recurso compartido que únicamente puede ser accedido por un filósofo cada vez. En la implementación que se ha estudiado en las prácticas de la asignatura, cada tenedor se ha representado mediante un mutex. Para cada uno de los supuestos siguientes, indique qué llamada realiza el hilo filósofo y cómo afecta dicha llamada al estado de ejecución de los filósofos. a) Un filósofo intenta coger un tenedor que ya está siendo utilizado por otro filósofo b) Un filósofo intenta coger un tenedor que está libre c) Un filósofo suelta el tenedor

Ej.

8-13

a) Se realiza un pthread_mutex_lock sobre un recurso que ya está bloqueado por lo que, el invocante se suspende hasta que es despertado cuando el recurso sea liberado. b) Se realiza un pthread_mutex_lock sobre un recurso libre, el cual pasará ahora a estar bloqueado, siendo ahora propiedad del hilo invocante. c) Se realiza un pthread_mutex_unlock y se libera el recurso tenedor, despertando si hubiera algún suspendido esperándolo.

Dado el siguiente fragmento de código correspondiente al problema de los cinco filósofos:

void *Filosofo(void *arg) { int nfilo=(int)arg; bool los_tengo; int n; while(true) { los_tengo=false; while(!los_tengo)

Page 105: Recopilacion 163 Ejercicios de So2

{ //tomo el tendor derecho pthread_mutex_lock(&tenedores[nfilo]); // Toma el tenedor izquierdo if ((n=pthread_mutex_trylock(&tenedores[(nfilo+1)%NUMERO_FILOSOFOS]))!=0) { //tenedor ocupado

Punto 1 } else {

Punto 2 } } //ya tengo los tenedores. // p_w->getView()->setEstadoFilosofo(nfilo, EF_COMIENDO); retraso(300); // un tiempo comiendo... //

Punto 3 // y se va a dormir... p_w->getView()->setEstadoFilosofo(nfilo, EF_DURMIENDO); } }

d) Se pide completar el código en los puntos 1, 2 y 3.

Ej.

8-14

Punto 1 pthread_mutex_unlock(&tenedores[nfilo]); retraso(100); los_tengo = false; Como el tenedor izquierdo está ocupado, soltamos el derecho y nos esperamos

Punto 2 los_tengo = true; Si consigue coger el tenedor izquierdo debe actualizar la variable “_los_tengo”, para salir del bucle y poder indicar que está comiendo

Punto 3 pthread_mutex_unlock(&tenedores[nfilo]); pthread_mutex_unlock(&tenedores[(nfilo +1)%NUMERO_FILOSOFOS]); retraso(300); Una vez ya ha comido debe soltar ambos tenedores y esperar un tiempo durmiendo. La instrucción de retraso debe ser posterior a la de la actualización de la ventana con el fin de que la visualización sea correcta.

e) Explique el funcionamiento de la llamada al sistema pthread_mutex_trylock

Ej.

8-15

La llamada pthread_mutex_trylock intenta cerrar el mutex que se le pasa como parámetro, de manera que si el mutex está abierto, lo cierra, devolviendo como valor de retorno un 0. En el caso en el que el mutex esté cerrado cuando se invoca la llamada, ésta no suspende al hilo invocente, lo que hace es devolver el código de error EBUSY

Page 106: Recopilacion 163 Ejercicios de So2

El siguiente código muestra la ejecución del microshell visto en prácticas. ¿Es posible que se creen zombies? ¿En qué situaciones? Justifique la respuesta y, en caso afirmativo, indique cómo habría que modificar el código para que esto no ocurriera. int ejecutar (int nordenes , int *nargs , char **ordenes , char ***args , int bgnd) { int pid, i; for (i=0;i<nordenes; i++){ pid=fork(); if (pid == 0){ redirigir_ent(i); redirigir_sal(i); cerrar_fd(); execvp(ordenes[i],args[i]); fprintf(stderr, "%s no encontrado\n",args[i][0] ); exit(1); } } cerrar_fd(); if (!bgnd) while (wait(&estado) != pid); }

Ej.

8-16

Si se crean zombies ya que en este código el ush no espera a los hijos si la orden es en background. Para solucionarlo hay que crear un hijo auxiliar cuando la orden sea en bgnd, que no espere a los hijos y haga un exit ...

Recordando lo realizado en la práctica de microshell sobre el tratamiento de señales, se pide escribir las sentencias que implementan la siguiente gestión de señales: El intérprete de comandos debe ignorar las señales SIGINT, SIGQUIT, SIGTTIN y SIGTTOU Los procesos hijos deben ignorar las señales SIGINT y SIGQUIT y el resto de señales deben tener el tratamiento por omisión. Escriba el código del intérprete y de los hijos

Ej.

8-17

// codigo del interprete struct sigaction act; act.sa_handler = SIG_IGN; sigaction(SIGINT, &act, NULL); /* senyal interrupcion del teclado CTRL-C */ sigaction(SIGQUIT, &act, NULL); /* senyal terminacion del teclado */ sigaction(SIGTTOU, &act, NULL); /* proceso background intentando leer */ sigaction(SIGTTIN, &act, NULL); /* proceso background intentando escribir */ ... //codigo del hijo struct sigaction act; act.sa_handler = SIG_DFL; sigaction(SIGINT, &act, NULL); sigaction(SIGQUIT, &act, NULL);