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. 2. 3. 4. 5. 6. 7. 8. Ejercicios sobre procesos y ficheros .............................................................................................................................. 2 Ejercicios sobre ficheros, redirección y tubos .............................................................................................................. 10 Ejercicios sobre señales................................................................................................................................................ 23 Ejercicios sobre directorios y protección. .................................................................................................................... 34 Ejercicios sobre hilos y sección crítica ......................................................................................................................... 41 Ejercicios sobre semáforos y monitores ....................................................................................................................... 52 Problemas de sincronización ........................................................................................................................................ 79 Ejercicios sobre prácticas. ............................................................................................................................................ 95

1. Ejercicios sobre procesos y ficheros
1.1 Dificultad de los ejercicios
Nivel de dificultad Principiante Medio Avanzado Ejercicios
Ej. 1-1, Ej. 1-8

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. No puede fallar. Esta llamada termina al proceso que la utilice. 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.

exit() wait()

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), ... 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. 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.

open()

exit ()

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()) break; while ( wait(NULL) != -1 ); }

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.
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 Ej. 1-6

Ej. 1-4

Ej. 1-3

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); }

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.

} Ej.i). Podrá haber procesos huérfanos porque resulta posible que un proceso padre termine antes que alguno de sus hijos. por esa u otra causa. Dé el nombre de tres llamadas al sistema de la interfaz POSIX que nunca puedan fallar (es decir. } printf("Mensaje 5: valor %d \n”.i). exit(-1). 1-8 El primer “mensaje” siempre se escribe y entonces la variable i sigue valiendo cero. sus hijos que hayan terminado permanecerán en estado zombie. También es posible que haya procesos en estado zombie durante cierto intervalo de tiempo. por ejemplo. 4 y 5 serían impresos por el proceso padre. Como ninguno de los procesos padres llega a realizar un wait(). Por tanto.h> #include <unistd.i). exit(3). 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. switch(pid=fork()){ case -1: i++. jamás devolverán un valor -1 como resultado) y explique por qué ninguna de ellas falla jamás. indique que imprimirán por salida estándar cada uno de los procesos que se generan. con lo que éstos quedarían huérfanos. int i. si el fork() llega a fallar. . Sólo se imprimirá un “mensaje” 1. mientras no terminen estos padres.h> main(){ int pid. No obstante. 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. Considere todos los posibles casos de ejecución de fork(). Esto sucederá si un proceso hijo termina antes que su padre. case 0: i++.i). while (wait(&status)>0). Cuando su padre termine. #include <stdio. status. printf("Mensaje 1: valor %d\n”. 1-7 Ej. default: i++.i). Dado el siguiente código en C y POSIX. printf("Mensaje 4: valor %d \n”. printf("Mensaje 3: valor %d \n”. que devolverá un cero para el proceso hijo y el PID del hijo para el proceso padre. printf("Mensaje 2: valor %d\n". El orden entre los “mensajes” 3 y 4 podría ser diferente.Pueden llegar a generarse ambos tipos de procesos. el resultado obtenido habría sido: Mensaje 1: valor 0 Mensaje 2: valor 1 Mientras que si el fork() no falla. i=0. A continuación se llega al fork(). 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.

Debido a la presencia de la llamada wait(). entrando entonces en la iteración i=2. Indique cuántos procesos llegarán a generarse como resultado de la ejecución de este programa y cuántos de ellos terminarán. generando entonces al proceso P5 y queda esperando a que P5 acabe. entra en su iteración i=2. terminando su bucle y finalizando su ejecución. i<3. empleada únicamente por los procesos padres en cada iteración. 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(). P2 continúa. Justifíquelo adecuadamente. termina su bucle y finaliza su ejecución. 11) P6 puede continuar. 7) Con ello. 14) Gracias a esto. 12) P7 es el único que puede continuar y como ha terminado ya el bucle. los respectivos procesos hijos deberían terminar antes que sus padres. P6 sale del wait(). P2 sale también del wait(). con lo que ya puede también finalizar la ejecución del bucle y terminar.h> int main(void) { int i. getppid(). generando a un proceso P6 y espera a que éste termine. termina también su bucle y finaliza. 8) P5 puede continuar. 9) Con ello. getgid(). genera entonces al proceso P8 y espera a que acabe. realiza la iteración con i=2.Hay varios ejemplos. Impleméntalo sin utilizar dup()ni dup2(). por lo que termina de inmediato. P2 continúa. finalizando su bucle y terminando enseguida. 2) En la primera iteración del bucle genera al proceso P2 y espera a que termine. exit() también funciona en todos los casos. generando por ello al proceso P7 y debiendo esperar su finalización. } Se crearán 23 procesos y todos ellos terminan. etc. 1-9 Ej. for (i=0. 15) P8 continúa. Ej. P3 continúa. 16) Esto libera a P1. pero ya no debe generar a ningún proceso más. 1-10 #include <unistd. 10) Ahora P1 ya puede continuar. En el bucle ya no tiene que hacer nada más. 3) P2 continúa y genera cuando i=1 al proceso P3 y espera a que termine. pueden utilizarse otros cualesquiera): 1) Inicialmente se crea el proceso P1 que empieza a ejecutar el programa. La llamada para solicitar la terminación del propio proceso también funcionará. terminando enseguida. 6) Con ello. 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". i++) if (fork()) wait(NULL). getuid(). entra en la iteración i=1. ¿Puede ocasionar algún problema esta implementación de la redirección? . 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. geteuid(). pues es una acción que siempre debe permitirse. Así. 4) P3 continúa y genera cuando i=2 al proceso P4 y espera a que termine. getgid(). Por tanto. 13) Con ello. acaba su ejecución. 5) P4 continúa.

1-12 close (0). /* El descriptor cero queda libre. si la posterior llamada open() fallara (ejemplos: por no tener permisos de escritura sobre el directorio. 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. */ open (“ejemplo”. padres e hijos comparten una misma descripción de apertura. Cierto Para ello debe utilizar la opción W NOHANG en el último argumento Utilizando la llamada open(). el proceso puede averiguar si tiene procesos hijos que no han terminado sin necesidad de suspenderse. 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 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. /* El descriptor cero queda libre. F F Indica si las siguientes afirmaciones son verdaderas (V) o falsas (F) . Con ello. Cierto. 1-13 V V V En la llamada execlp()se busca el programa a ejecutar en todos los directorios de la variable de entorno PATH. Impleméntalo sin utilizar dup()ni dup2(). Tanto con esta llamada como con execvp() el ejecutable se busca en los directorios presentes en PA TH Utilizando la llamada waitpid(). */ 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". Los procesos hijos heredan los descriptores de fichero que tuvieran sus padres. Al haber especificado el mismo nombre de fichero en las dos redirecciones. se efectuarán dos aperturas. Es imposible que dos procesos distintos compartan una misma descripción de apertura Falso. /* Open utiliza el descriptor más bajo */ /* y en este caso es el cero. */ open (“ejemplo”. 0666).Ej. */ 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. o estar trabajando en un disco montado en modo sólo lectura) habríamos dejado al proceso sin salida de error. O_RDONLY). Indica si las siguientes afirmaciones son verdaderas (V) o falsas (F) Ej. /* Open utiliza el descriptor más bajo */ /* y en este caso es el cero. O_WRONLY | O_CREAT | O_TRUNC. La llamada dup() y su variante dup2() solucionan estos problemas. 1-11 Ej. ¿Puede ocasionar algún problema esta implementación de la redirección? close (2). un proceso puede crear un fichero y darle la palabra de protección inicial que considere conveniente. Con ello. Cierto.

así como cuál de ellos será el último que terminará. pid. total 9 procesos. if(pid==0) { pid2=fork(). pid2. i++) { pid=fork(). 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. Para realizar esto debe utilizarse la llamada waitpid() con el flag W_NOHANG Utilizando la llamada open(). Dado el siguiente código. 1-14 F F F F V En la llamada execve () se busca el programa a ejecutar en todos los directorios de la variable de entorno PATH. .Ej. if (pid2!=0) wait(NULL). Resulta imposible abrir dos veces un mismo fichero en un mismo proceso.} else {sleep(5).} return 0. } wait(NULL). Sí que es posible. indique cuántos procesos serán generados por la ejecución de este programa (incluyendo al original). 1-15 Se crean 8 procesos más el original. No. Suponga que ninguna de las llamadas al sistema utilizadas provoca errores y que los procesos generados no reciben ninguna señal. } Ej. No. el proceso puede averiguar si tiene procesos hijo que no han terminado sin necesidad de suspenderse.h> int main(void) { int i. #include <stdlib. Utilizando la llamada wait(). 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. pero éstos tienen como propietario al UID efectivo del proceso creador.h> int main() { if (fork() == 0) {sleep(10). Considere la ejecución del siguiente código: #include <stdio. } return 0. i<4. break. Las variantes que buscan el ejecutable en el PATH son las terminadas en "p": execvp() y execlp(). En algunos de los ejemplos vistos en clase ocurría. Cada hijo crea un único proceso y tanto los hijos como el proceso que crean hacen break. Es posible que dos procesos distintos compartan una misma descripción de apertura Sí que es posible. for (i=0. El último proceso en finalizar es el original. El resto necesitan la ruta completa del ejecutable.

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.} Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F) F V F F F Se genera un proceso hijo zombie Se genera un proceso hijo huérfano Se genera un proceso padre zombie Se genera un proceso padre huérfano No se genera ningún nuevo proceso Indica si las siguientes afirmaciones son verdaderas (V) o falsas (F) Ej. Es imposible que dos procesos distintos compartan una misma descripción de apertura Es posible que al ejecutar la llamada open(“nuevo”. 1-16 Ej. 1-17 F F V F F V Utilizando la llamada open(“archivo”.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). utilizando la llamada open(). } else { //código del padre } } // de for while ( wait(NULL) != -1 ). Ej. O_RDWR) el sistema nos retorna dos descriptores uno para lectura y otro para escritura para el fichero “archivo”.O_WRONLY|O_CREAT|O_TRUNC. Resulta imposible que un proceso ejecute dos veces la llamada open() en su código sobre un mismo fichero. } Comentario: no se producen procesos zombie ya que el padre realiza llamadas wait hasta que no queden hijos en ejecución. i++) { if (!fork()) { //código del hijo //introducir aquí la llamada exec exit(). La llamada open() no debe ser utilizada para abrir un directorio. Suponiendo que cada proceso hijo realiza la llamada exec correspondiente. for (i =0. comente en cada caso la posibilidad de evitar la aparición de procesos zombie. // si falla “exec”. 1-18 . Un proceso puede crear un fichero con un propietario diferente a su UID efectivo. i <3. a) padre b) padre hijo1 hijo1 hijo2 hijo3 hijo2 hijo3 (a) #include <unistd. el hijo debe terminar.h> int main(void) { int i.

es posible que aparezcan procesos zombie. //del padre a hijo 1 } } Comentario: En este esquema de creación de procesos.Ej. 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). 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.h> int main(void) { int i. 1-19 (b) #include <unistd. El padre puede esperar la finalización de Hijo1. .

2º) Crear un proceso hijo. if (hijo_pid==0) { dup2(fd[0]. } 4º) Asignarle a la salida standard del proceso padre al descriptor de escritura del tubo y cerrar los descriptores del tubo. close(fd[1]). . Ejercicios sobre ficheros.2. close(fd[0]). pipe(fd). padre e hijo. de manera que: todo lo que el padre escriba en su salida Standard.STDOUT_FILENO). close(fd[0]). Ponga en cada paso las primitivas POSIX e instrucciones C necesarias. Ej. hijo_pid=fork(). if (hijo_pid>0) { dup2(fd[1]. 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. el hijo lo lea desde su entrada Standard. mediante la llamada fork() que heredara la tabla de descriptores de ficheros.1 Dificultad de los ejercicios Nivel de dificultad Principiante Medio Avanzado Ejercicios 2. close(fd[1]).STDIN_FILENO). int fd[2]. redirección y tubos 2. 3º) Asignarle a la entrada standard del proceso hijo al descriptor de lectura del tubo y cerrar los descriptores del tubo.2 Ejercicios Indique ordenada y secuencialmente los pasos necesarios para establecer un mecanismo de comunicación entre dos procesos UNIX. } 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.

*/ /* Código del padre.txt”. */ /* Lo siguiente ya es código común. para cumplir lo pedido en el punto 3. fd1. Dado el siguiente código. para que lo herede. 2-3 int fds[2]. } else { dup2(tubo[0]. */ /* Cambiamos la entrada estándar. fde = open(“error. de manera que la salida de error del padre se escriba en el tubo y el hijo lea su entrada estándar de él. if(!fork()) /* Creamos un primer proceso hijo.txt”. */ dup2(fds[l]. */ /* El padre y los dos hijos cerrarán los descriptores que permiten acceder al tubo. if (fork() == 0) { /*CODIGO DEL HIJO */ dup2 (fd[0]. pipe(fd). MODE644). 2). 2-2 int tubo [2] pipe(tubo) if(fork()) { dup2(tubo[l]. */ /* Código del hijo. 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. close (fd[1]). Suponemos que no hay fallos. Así funcionará lo pedido en el punto 3). */ close(fds[0]). */ /* Cambiamos la salida de error. 0). Ej. MODE644)). NEWFILE. NEWFILE. /* Creamos el tubo antes de crear el proceso hijo. fd1 = open(“salida. dup2(fde. . 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).2) Se intercomunique con el hijo 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. 2 ) Los hijos deben intercomunicarse mediante un tubo. */ else if(¡fork()) /* Creamos un segundo proceso hijo. close(fds[l]). STDIN_FILENO). 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. close (fde). 2). /* Cerramos todos los descriptores del tubo. /* Este hijo utiliza el descriptor de lectura del tubo como entrada estándar. 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. fds=pipe(). fde. */ close(tubo[1]). */ close(tubo[0]). /* El nuevo hijo utiliza el descriptor de escritura en el tubo como salida de error. STDERR_FILENO). close (fd[0]). */ dup2(fds[0]. Ej. 0). int fd[2].

char leer[100]. } } else { while ( read(tubo[0]. close (fd[0]).leer. int main(){ int i. } Suponer que el estado inicial de la tabla de descriptores es el siguiente: [0] [1] [2] [3] [4] PADRE /dev/tty0 /dev/tty0 /dev/tty0 - Ej.dup2(fd1. close (fd1)..."datos". 2-4 [0] [1] [2] [3] [4] PADRE /dev/tty0 Tubo_w Error.leer). i<10. close (fd[1]).. } else /* CODIGO DEL PADRE*/ dup2 (fd[1].txt Indique si finaliza o no el siguiente programa (y por qué) y qué imprime por pantalla. } } } . if (fork()==0) { for (i=0. STDOUT_FILENO).txr [0] [1] [2] [3] [4] HIJO Tubo_r Salida. int tubo[2].6).txt Error.6) != 0){ printf("%s\n". pipe (tubo). // ---> A) ESTADO TABLA HIJO . STDOUT_FILENO). // ---> B) ESTADO TABLA PADRE . i++){ write(tubo[1]..

40). fd1 = open( “ejemplo1”. Por ello. … } Ej. fd3. se realiza una lectura de 323 bytes. read(fd1. fd2. 2-6 fd1: 523 fd2: 523 fd3: 40 fd4: 523 Hay que tener en cuenta que al realizar un dup() estamos duplicando los descriptores. write(fd3. buffer. Sobre ese fichero sólo se efectúa un acceso de escritura donde se transfieren 40 bytes. fd2 = open( “ejemplo2”. 100). buffer. 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. 40). buffer. 100). fd3 = open( “ejemplo2”. La posición en el fichero será la 40. fd4. read(fd1. char buffer[512]. */ write(fd2. 2-5 Imprime por pantalla 10 líneas con la palabra “datos” cada una. read(fd4. el read bloqueante no retorna cuando el hijo muere. al final la posición pedida será la 523. 323). write(fd1.Ej. O_RDWR ). int main(void) { int fd1. buffer. Sobre ese fichero se realiza una primera lectura de 100 byte y. buffer. 323). Como el puntero de posición avanza secuencialmente en cada uno de los accesos. buffer. buffer. una escritura de100 bytes. fd4 = dup(fd2). 100). a través de fd4. fd2 y fd4 acceden a un mismo puntero de posición. fd1 . fd1 = open( “ejemplo1”. pero todos ellos siguen haciendo referencia a la misma descripción de apertura. O_WRONLY ). El descriptor fd3 apunta a la descripción de apertura del fichero "ejemplo2". int main(void) { int fd1. /* A partir de esta línea hay dos procesos. fork(). fd2. 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. O_RDWR ). read(fd1. } . O_WRONLY ). El programa no acaba ya que como el padre no cierra el descriptor tubo[1]. 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. ligado a la única apertura del fichero "ejemplol". 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.. Posteriormente. .. fd2 = dup(fd1). char buffer[512].

write(fd2. y que su talla es infinita.Ej. sobre fd3) + 50 (tercer read. Para ello indique qué llamadas al sistema deben utilizarse y qué secuencia debe seguirse (no es necesario dar un ejemplo de código. write(fd1. fd1 = open( “exemple”. buffer. 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. 50). buffer. con lo que el valor final del puntero será 100+323+323=746. sobre fd2) + 45 (segundo write. write(fd2. La llamada a write() sobre fd1 fallará pues ese fichero no fue abierto en un modo que permitiera su escritura. Con ello. fd3: 50 (primer read sobre fd1) + 60 (segundo read. realizado por el otro proceso) = 140. exit(1). el puntero de posición vale finalmente 40+40=80. El siguiente programa genera distintos procesos que acceden a ficheros. sobre fd1. O_RDONLY ). O_WRONLY ). sobre fd2 realizado por el proceso padre o el hijo) + 45 (segundo write. fd3. Ambos realizan una lectura de 323 bytes. tanto el padre como el hijo tienen acceso a la misma descripción de apertura sobre "ejemplo1" con dicha variable. } Ej. todos los descriptores se comparten entre los procesos padre e hijo. . read(fd1. char buffer[100]. fd2. 50). buffer. Además. realizado por el otro proceso) = 210 fd2: 50 (primer write. fd3 = dup(fd1). sobre fd2. Debido a esto deberán tener el mismo valor para ambos procesos. Indique de qué forma se puede lograr que dos procesos compartan una misma descripción de apertura (para tener acceso al mismo fichero). 2-7 fdl: 746 fdl: 80 "fd1" ya valía 100 antes del fork(). 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. buffer. basta con dar el nombre de las llamadas). por lo que la escritura que aparece después de él es ejecutada tanto por el padre como por el hijo. Para cada proceso. fork(). Tras el fork(). 50). tras la llamada a fork(). "ejemplo2" también había sido abierto antes del fork(). fd2 = open( “exemple2”. 45). read(fd1. Los punteros de posición tendrán finalmente estos valores: fd1. sobre fd1) + 50 (tercer read. Suponemos que no existe problema alguno para abrir dichos ficheros. 20). read(fd3. 60). buffer. buffer.

dup2(fd3[1]. Sí.STDOUT_FILENO)..STDIN_FILENO). antes de efectuar el cierre. 2-10 F F V F Dado el siguiente código en el cual se generan al menos tres procesos P1.. close(fd[0]). Deben ser las mismas.STDIN_FILENO). No tiene por qué haberlo perdido para siempre. close(fd2[1]). dup2(fd2[0]. pipe(fd2). close(fd3[1]). Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F) F Si un proceso cierra su entrada. el contenido. close(fd2[1]). close(fd3[0]). utilizando "dup2(2. se llegaría a enviar SIGPIPE si no hubiera ningún descriptor que permitiera la lectura de la información existente en el tubo 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. Otra opción habría sido realizar un dup() o dup2() para conservar el acceso en otros descriptores. close(fd[1]).STDOUT_FILENO). Para crear la descripción de apertura habrá que crear o abrir un fichero mediante la llamada al sistema open(). close(fd2[0]).. pipe(fd).Ej. 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. if(fork() != 0){ close(fd2[0]). En ciertos casos. salida v salida de error estándares. Las llamadas necesarias para leer o escribir en la Terminal no son las mismas que la que deben emplearse para leer o escribir ficheros. P2 y P3: . close(fd2[1]). La caché de bloques que mantiene el sistema operativo puede evitar la suspensión en muchos casos Ej.0)" por ejemplo. pero no crea ninguna descripción nueva. close(fd3[0]). si es que tiene permisos. close(fd3[1]). Indique para los procesos P1. escribir en un tubo puede causar que el sistema operativo envíe una señal a ese proceso escritor. } } . Puede abrir el fichero especial correspondiente ("/dev/tty" o el que sea) más tarde. }else{ /***Proceso P3 ***/ dup2(fd3[0]. salida y salida de error estándares ya no puede te acceso a la terminal. Sí que puede conseguirse. No tiene por qué suspenderse. close(fd[1]). P2 y P3. basta con utilizar fork(). después de la ejecución de este código. en caso contrario no tendría ningún sentido todo lo que se ha explicado sobre las redirecciones de la entrada. pues genera un nuevo descriptor para acceder a una descripción ya existente. La llamada al sistema dup() no sirve para esto. la relación existe entre ellos y cuál es el esquema de comunicación resultante.STDIN_FILENO). Para generar un proceso hijo.. No tendría excesivo sentido hacerlo pero sí que es posible. close(fd2[0]). pipe(fd3). de sus respectivas tablas de descriptores de archivo. en primer lugar deberemos crear una descripción de apertura y después habrá que generar un proceso hijo. close(fd[0]). 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. dup2(fd2[1].STDOUT_FILENO). if(fork() != 0){ /***Proceso P1 ***/ dup2(fd[1]. . }else{ /***Proceso P2 ***/ dup2(fd[0]. Por ello.

dup2(f1. pipe(fd). El esquema de comunicación resultante es una especie de “anillo” entre P1. suponiendo que previamente se ha ejecutado con éxito la orden $ echo hola > hola: 1./a. &argv[1]) == -1) printf("El proceso 1 ha cometido un error\n"). $. STDIN_FILENO). i. close(fd[1]). f1. La relación existente entre los tres procesos es: P1 es el padre de P2 y P2 es el padre de P3. for(i=0. close(fd[1]).out hola siendo a. T1 sirve para pasar la salida estándar de P1 a la entrada estándar de P2. P2 y P3. T2 (asociado al vector fd2) y T3 (con el vector fd3). 2-11 En el código presentado se están creando tres tubos.Ej.out cat 2. f1 = open("hola". 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. printf("Soy el proceso p2 y digo: %s\n". O_RDONLY). char **argv) { int fd[2]. i++). STDOUT_FILENO). a los que llamaremos T1 (asociado al vector de descriptores fd). .out el ejecutable correspondiente al siguiente código en C: main(int argc. str). if (execvp(argv[1]. if (fork()) { dup2(fd[1]. como puede deducirse de la tabla de descriptores anterior. close(fd[0]). 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. $. (str[i] =getchar()) != EOF && i<80. Diga qué imprimirán por la salida estándar cada una de las órdenes que se detallan a continuación. char str[80]="adios". str[i-1]='###BOT_TEXT###'. También se muestra claramente que se creará un total de 3 procesos. STDIN_FILENO). } else { dup2(fd[0]. Así. close(fd[0]). } } NOTA: la función getchar() devuelve el siguiente carácter que lee de la entrada estándar./a.

el proceso hijo hereda todos los descriptores de fichero que pudiera tener el proceso padre. 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. pero tal fichero no es un ejecutable. Por tanto. se crea un proceso hijo. la salida obtenida será: Soy el proceso p2 y digo: hola 2: En este caso se pasa como argumento la cadena “hola”. ) que de manera atómica genera un proceso hijo y.. el programa a ejecutar en el proceso padre será un “cat” por lo que el hijo. únicamente implicaría el inconveniente de necesitar más código en el programa del proceso padre. Este proceso hijo. 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. obtendrá en su cadena “str” el contenido del fichero “hola”.. Por tanto. int crear_proceso(char *nombre_programa. el proceso padre fallará al intentar ejecutar tal fichero. No es excesivamente complicado hacer esto. el padre estaría obligado a: 1) Duplicar sus descriptores estándar en otras entradas para poderlos recuperar posteriormente. Además. 2-13 1: El código presentado trata de abrir en modo lectura el fichero “hola” y dejarlo como entrada estándar para el proceso padre. Esta cadena será recibida en la entrada estándar del hijo. En esta llamada. Por tanto. 2) Modificar sus descriptores estándar para que tengan los valores que espera el hijo para implantar sus redirecciones o tuberías. En POSIX. a la hora de implantar las redirecciones y tuberías. que es justamente esa misma palabra. 4) Recuperar los descriptores estándar del padre almacenados en el paso 1. si no hay errores para localizar y ejecutar el fichero. Debido a esto escribirá en su salida estándar la cadena “El proceso 1 ha cometido un error”. char *argumentos[]). Indique qué ventajas o inconvenientes plantea esta aproximación frente a la empleada en POSIX. 2-12 Ej. Mediante un tubo se consigue pasar la salida del proceso padre a la entrada del proceso hijo. las redirecciones y tuberías se suelen implantar una vez se ha creado el proceso hijo mediante fork(). Por ello. En este primer ejemplo. pero resulta bastante más cómodo hacerlo con el esquema propio de POSIX. Con la llamada que se comenta. 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. Se pretende implantar un esquema entre dos procesos (Proceso padre y Proceso hijo) como el que se muestra en la figura siguiente: . consigue que este nuevo proceso ejecute el programa suministrado en el primer parámetro con los argumentos facilitados en el segundo. al final.Ej. Éste ejecutará la orden facilitada como primer argumento al programa.”. 3) Llamar a crear_proceso() para generar el proceso hijo con las redirecciones apropiadas. heredando todos los descriptores del padre y antes de efectuar el exec() para cambiar el programa que ejecutará el nuevo proceso hijo.

2-14 else{ fd_open=open("/tmp/file. b) Escriba el código.O_WRONLY|O_TRUNC|O_CREAT. dup2(fd_open. dup2(fd[0].NULL). execlp(“/bin/sort”. Suponiendo que el resto de código no altera la redirección de la E/S de los procesos. pipe(fd). necesario para que se lleve a cabo la comunicación entre los procesos que intervienen en dicha línea de órdenes. en POSIX y C. Ej.txt. close(fd_open).Proceso hijo tubo Proceso padre /tmp/file.txt". close(fd[1]).. close(fd[1]). 2:/tty/console. /* 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. wc: 0:tubo2[0].txt Donde el proceso padre redirige su salida al fichero /tmp/file.0600). close(fd[1]). sort: 0:tubo1[0].STDIN_FILENO).STDOUT_FILENO).STDOUT_FILENO). Utilice las siguientes llamadas: execlp(“/bin/cat”. 2-15 3 proc 2 fich + 2 tubos cat: 0:/tty/console. close(fd[0]). .STDIN_FILENO). “sort”.NULL). 2:/tty/console. 1:tubo1[1]. “fich1”. execlp(“/usr/bin/wc”. En caso de no serlo escriba las modificaciones que son necesarias realizar en el código para que lo sea. 2:/tty/console.txt". fd_open=open("/tmp/file. “wc”. close(fd[0]). Para ello se propone el siguiente código: . “cat”. 1:tubo2[1].. indique si el código es correcto o no. if(fork() == 0){ dup2(fd[1]. 1:result. Ej. /* resto de codigo */ } . /* resto de codigo */ }else{ dup2(fd[0]..NULL).0600).O_WRONLY|O_TRUNC|O_CREAT.. “l”. close(fd[0]).

while(pid != wait(&status)). char *agrv[]) { int fd. execlp("/usr/bin/wc". } Completa el siguiente programa para que ejecute el mandato “ls –la”. "-la" }. char *Comando[2] = { "ls". "sort". close(tubo1[1]).0). Para ello utiliza las variables ‘Fichero’ y ‘Comando’. main(int argc. close(tubo1[0]). } close(tubo0[0]). /********** COMPLETAR ************/ } . close(tubo1[0]).close(tubo0[1]). close(tubo0[0]).0). redirigiendo la salida estándar al fichero ‘listado. 2-16 {int tubo0[2]. } if(!(pid=fork())){ dup2(tubo1[0]. "-l". } if(!(pid=fork())){ dup2(tubo0[0]. close(tubo0[0]). execlp("/bin/cat".close(tubo1[1]).txt’. close(tubo1[0]). tubo1[2]. close(tubo0[0]).h> <sys/stat. #include #include #include #include #include <sys/types.h> <fcntl. NULL).Ej. dup2(tubo1[1]. close(tubo0[1]). close(tubo0[1]). close(fd). if (!(pid=fork())) { dup2(tubo0[1].1).NULL).txt". pipe(tubo1). "fich1". close(tubo1[1]). close(tubo0[1]). close(tubo1[0]). fd=open(“result”.1). dup2(fd. pipe(tubo0). "cat". NULL).0666). execlp("/bin/sort". "wc".1).h> <stdio.O_WRONLY |O_CREAT|O_TRUNC.h> #define NEWFILE (O_WRONLY | O_CREAT | O_EXCL) #define MODE644 (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) char *Fichero = "listado.h> <unistd. int fd. close(tubo1[1]).

char *argv[]) { pipe(tubo0). "No es pot executar: %s\n".txt". } // Comandament llençat } Considere el código de la figura. exit(1). fitxer). main(int argc. &comandament[0]) < 0) { fprintf(stderr. execlp("/bin/echo".h> <stdio. execlp("/usr/bin/wc".1). fitxer). char *comandament[3] = { "ls". "hola".h> <fcntl.0). "wc". comandament[0]). exit(1). execlp("/bin/sort". char *argv[]) { int fd. } close(tubo0[0]). } .0). STDOUT_FILENO) == -1) { fprintf(stderr. "No es pot redirigir a: %s\n". close(tubo1[1]).out main(int argc. } if(!(pid=fork())){ dup2(tubo0[0]. "echo".Ej. if ((fd = open(fitxer. "-la".1). MODE644)) == -1) { fprintf(stderr.h> <sys/stat. 2-17 #include #include #include #include #include <sys/types. NULL). while(pid != wait(&status)). pipe(tubo1). close(tubo0[1]). "No es pot crear: %s\n". exit(1). } if(!(pid=fork())){ dup2(tubo1[0]. NULL).h> <unistd.h> #define NEWFILE (O_WRONLY | O_CREAT | O_EXCL) #define MODE644 (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) char *fitxer = "llistat. //Eixida estàndard redirigida a fitxer if (execvp(comandament[0]. NULL). } close (fd). } // Creació de nou fitxer if (dup2(fd. close(tubo1[0]). dup2(tubo1[1]. cuyo correspondiente fichero ejecutable se denomina a. NULL }. "sort". if (!(pid=fork())) { dup2(tubo0[1]. NEWFILE.

Así por ejemplo. Con el código correcto. Si el proceso echo no hubiese terminado de escribir al recibir sort la señal SIGPIPE. pues no han cerrado sus descriptores de fichero para escribir. los procesos sort y wc son escritores potenciales de tubo0 y tubo1. INIT adoptaría e invocaría wait. La causa de esto es que la tubería no está bien organizada: existen procesos que tienen descriptores de escritura abiertos sobre el tubo.La ejecución de dicho código pretende ser equivalente a la ejecución en el shell de: $ echo hola | sort | wc Sin embargo. no quedarían zombies permanentes. 2-18 Ej. . Ej. Quedan procesos huérfanos de forma permanente. El padre de estos procesos ha muerto.out permanecen sin acabar los siguientes procesos en el sistema: PID TTY TIME CMD 17466 pts/4 00:00:00 a. Es decir invocar: close(tubo0[0]). indique si quedarían en el sistema procesos zombies de forma permanente. antes de execlp. 2-19 El proceso 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. Al matar al proceso sort. pues si el padre muriese antes de ejecutar wait. que después de lanzada la ejecución de 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. La señal SIGPIPE se genera cuando un proceso intenta escribir en un tubo sin ningún descriptor de lectura abierto. La solución es que los procesos hijos de a. terminando así en cadena todos los procesos de la tubería b2) No quedan zombies. close(tubo1[1]). y los procesos están suspendidos esperando en la operación de lectura (no se produce EOF). b2) Si en vez de la orden anterior se ejecuta la orden $kill -9 17466 .out no termina porque está esperando la terminación de los procesos sort y wc. pero los procesos no han invocado exit.close(tubo0[1]). el tubo1 sigue teniendo como lector y escritor a wc. b1) No. Se observa. aunque no los utilizan. 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.out cierren los descriptores que no utilizan sobre el tubo. pues se encuentran suspendido esperando leer del tubo.close(tubo1[0]).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. Estos procesos no terminan porque la lectura desde su entrada estándar esta redirigida desde los tubos. Razone la respuesta. Con el código correcto se hubiera producido la señal SIGPIPE al matar el proceso wc e intentar escribir sort sobre el tubo. al ejecutar a. además. nuevamente la recibiría echo.

5).STDIN_FILENO). } ."ls". pipe(tubo). 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.leer). execlp("wc". 2-20 Ej. i<10. } } else { while(read(tubo[0]. while (wait(NULL)>0). while (wait(NULL)>0).5)!= 0) printf("%s\n".STDOUT_FILENO). close(tubo[1]). char leer[100]. Diga que mensajes se imprimirían en la pantalla. } else { dup2(tubo[0]. i++) { write(tubo[1]. int tubo[2]. if (fork()==0) {for (i=0. } //*****CODIGO_B**************// int main() {int i. int main(){ int tubo[2]. close(tubo[0]).leer. } close(tubo[1]). hace wait y termina. 2-21 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. close(tubo[0]).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).5)!= 0) printf("%s\n". close(tubo[0]). } } } Ej.NULL)."HIJO". } return 0. En este código el padre lee todo lo que el hijo ha escrito en el tubo y sigue intentando leer. If (fork()){ dup2(tubo[1].leer."HIJO". if (fork()==0) {for (i=0. 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. i<10. El padre imprime por pantalla 10 veces la palabra “HIJO”. close(tubo[0]). close(tubo[1]). int tubo[2]. close(tubo[1]). } else { close(tubo[1]). i++) { write(tubo[1].NULL). pipe (tubo). //*****CODIGO_A**************// int main() {int i.leer). while( read(tubo[0]. char leer[100]. pipe (tubo)."wc".5). 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. execlp("ls".

Dicha señal sólo se envía al propio proceso que ha utilizado la llamada. 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.1 Dificultad de los ejercicios Nivel de dificultad Principiante Medio Avanzado Ejercicios 3. Ejercicios sobre señales 3. 3-2 Las dos únicas funciones que permiten enviar señales a los procesos son kill() y alarm(). Conteste de forma concreta y concisa a las siguientes preguntas sobre señales: a) ¿Qué es una señal? . 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(). sigprocmask() es la función a utilizar para modificar permanentemente la máscara de bloqueo de señales que tiene 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.2 Ejercicios Indique qué funciones POSIX permiten enviar señales a un proceso. sigsuspend() permite especificar la máscara a utilizar mientras el proceso que solicite esta función permanezca suspendido. a todos los procesos del grupo del emisor (valor cero). Con el primer parámetro de kill() se puede especificar si la señal debe ser enviada a solo un proceso (valor positivo). Si el tratamiento a utilizar es la instalación de una función manejadora. sigaction() permite especificar el tratamiento de señales que se llevará a cabo para una determinada señal. 3-1 Ej. esta nueva máscara tiene un duración breve (no es permanente). Utilizando esta función. a todos los procesos (valor -1) o a todos los procesos de un grupo determinado (otros valores negativos). 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. Tan pronto como llegue alguna de las señales no bloqueadas en esa máscara temporal.3. 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. de qué manera (permanente o temporal) y cuándo tiene lugar la modificación. la señal se envía de inmediato. Indique qué funciones POSIX modifican la máscara de señales de un proceso. Por tanto.

/* Primera alternativa: */ sigset_t mascara /* Primero debemos crear una máscara donde sólo aparezca SIGHUP. para ello hay que instalarlo previamente mediante la llamada sigaction(). el de ignorar la señal SIG_ING. /* Ahora eliminamos las señales de esa máscara de la que tenga el proceso. Ejemplo CTRL-C • Por error en ejecución. hay que construir otra máscara con SIGHUP y SIGILL activos. reinstalamos la máscara que acabamos de construir. utilizando entonces sigprocmask() con la acción SIG_BLOCK. b) La generación de señales puede ser: c) Desde el Terminal. 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. kill(numero proceso. sigaddset()) e instalando la máscara (sigprocmask()). NULL). /* Finalmente. sigaddset(&mascara. NULL). queremos obtener la máscara actual.. SIGILL) sigprocmask(SIGBLOCK &mascara NULL) /* Segunda alternativa: Partir de la máscara actual.. */ Sigprocmask(SIGUNBLOCK. &mascara.. Este manejador pude ser: el de defecto(SIG_DFL) que normalmente finaliza el proceso. SIGHUP). 3-3 Ej. */ sigaddset(&mascara. */ sigemptyset(&mascara ). /* Y añadimos las que hay que bloquear. • Por software. /* La acción no importa. */ sigset_t mascara. &mascara. /* Otra solución consistiría en haber construido una máscara con solo SIGINT activo. . NULL. d) Un manejador es el código que se ejecuta como respuesta a una señal. Tras esto. sigaddset(&mascara. o el código de una función.*/ sigprocmask(SIG_SETMASK. SIGILL). SIGHUP). Ejemplo: alarm(10). /* Repetimos el trabajo para SIGILL. */ . utilizando sigprocmask() con la acción SIG_UNBLOCK. */ sigdelset(&mascara. 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. . pero ahora utilizando la acción SIGC */ sigemptyset( &mascara ). Ejemplo: División por cero SIGFPE. 3-4 a) Una señal es la notificación por software de que ha ocurrido un evento.. sigfillset(). */ sigprocmask(SIG_BLOCK. Para ello se ha de trabajar con la máscara definiéndola (sigemptyset(). numero de señal) • Para impedir temporalmente el depósito de una señal esta debe enmascararse. /* Ahora.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. &mascara). eliminamos SIGINT. SIGINT). sigaddset(&mascara.

Las señales SIGKILL y SIGCONT no pueden bloquearse. Ej.sa_mask). ni tener una función manejadora asociada. bloqueadas. Un proceso puede tener bloqueada la entrega de todas las señales existentes. En ambos casos.&act. SIGKILL y SIGCONT no pueden ser ignoradas. bloqueadas. 3-6 F F F F V Realizar una lectura en un tubo puede originar que el sistema operativo envíe la señal SIGPIPE al proceso lector. 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. 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.sa_flags = 0. exit(0). Lo que puede hacerse es añadir ciertas señales a la máscara de bloqueo. } d) int main(void){ codi(). sigaction(SIGHUP. No. No es lo mismo ignorar una señal que bloquearla Existen señales que no pueden ser ignoradas. No. ignoramos SIGINT */ act. act. sigemptyset(&act. } . a) int main(void){ codi(). Mediante la llamada al sistema sigsuspend() se puede solicitar que algunas señales sean ignoradas mientras dure la espera. Cuando un proceso envía una señal SIGKILL a otro.NULL). /* Ahora. return 0. Si algunas formas son equivalentes razona por qué producen el mismo resultado.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. fallaría. Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F) Ej.sa_handler = mane]ador. } c) int main(void){ codi().sa_handler = SIG_IGN. Esa señal sólo puede ser generada para un proceso que intente escribir en un tubo que no tenga ningún lector. kill(9. el sistema operativo nos garantiza SIEMPRE que el destinatario de dicha señal morirá. NULL). } b) int main(void){ codi().getpid()). ni tener una función manejadora. &act. sigaction(SIGINT. /* Instalamos en primer lugar la función manejadora para SIGHUP */ act. 3-5 struct sigaction act. Puede que el proceso especificado como destino de esa señal no exista o pertenezca a otro usuario.

pero no de manera voluntaria sino por la llegada de la señal 9.h> #include <unistd. ). Obsérvese también que tras la llamada a “alarm()”. que puede enmascararse ni tratarse. el proceso mostrado terminaría al cabo de aproximadamente cinco segundos de haberse iniciado. char *argv[]) { alarm(5). o cuando se desenmascare una ocurrencia anterior. a) Enmascarar una señal significa evitar la notificación de su ocurrencia al sistema operativo. 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. por lo que si no se hubiera enviado esa u otra señal. 3-9 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. b) Sí que se puede. el programa presenta un bucle infinito. Como este proceso no realiza ningún tratamiento para esa señal y por omisión su efecto es terminar el proceso. el proceso no terminaría. La variante d) también garantiza que el proceso terminará. 3-7 No hay diferencia entre las variantes a). 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. 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í. Ej. . la cual se ejecutará en cuanto se produzca una ocurrencia no enmascarada. } Indique de manera justificada cual será su tiempo aproximado de ejecución. b) y c) ya que todas ellas terminan haciendo una llamada a sistema exit con el valor 0 como parámetro. Tratar una señal significa asociar a la señal una función. la propia biblioteca se encarga de realizar la llamada exit. 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. impidiendo así cualquier tratamiento por parte de éste. Desde allí se llama a main() y cuando este retorna. explica por qué.h> #include <stdio. 3-8 Ej. for ( .h> int main (int argc. .c #include <stdlib.Ej. 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. ¿Cuál es el comportamiento de esta combinación? Si no resulta posible.

Si ocurriera así. Justificar la respuesta indicando los pasos que ha seguido la ejecución del proceso.h> #include <unistd. int status. éste es probable que se encuentre ya suspendido esperando la finalización del hijo. act. } } Ej. if (val = fork()) { while(wait(&status)!=val) printf("Espera no finalizada\n"). El proceso padre entra en un bucle de llamadas a la función “wait()” que concluirá cuando el proceso hijo haya terminado. indique cuál es la salida que producirá por pantalla al ser ejecutado.sa_flags = 0. instalando la función “handle_signal()” como manejadora de ésta. el proceso hijo expulse al padre (porque terminara su quantum o por ser el hijo más prioritario y emplear prioridades expulsivas. al realizar el fork(). Por tanto. Espera no finalizada. NULL).sa_handler = handle_signal. por lo que el proceso padre también escribe “Espera no finalizada”. se suspendería y sólo saldría de este estado cuando el hijo terminara. por ejemplo). De ser así. pues el valor devuelto por wait() habrá coincidido con el PID de su proceso hijo. el proceso padre ya no escribirá nada. cuando el proceso hijo envía la señal a su padre. sigemptyset(&act. O bien sólo el primero de estos dos mensajes.h> #include <signal.sa_mask).Dado el siguiente programa POSIX. Posteriormente llegaría al wait(). esperará aproximadamente un segundo y posteriormente terminará. Posteriormente se genera un proceso hijo. exit(0). act. el padre sólo escribiría “Se ha recibido otra señal”. sleep(1). wait() termina con error. Como consecuencia de la interrupción de la espera. Otra opción es que. debido al algoritmo de planificación que utilice el núcleo del sistema operativo. . En la siguiente iteración de ese bucle de espera. sigaction(SIGUSR1. } main() { pid_t val.SIGUSR1). El manejador escribirá el mensaje “Se ha recibido otra señal”. struct sigaction act.h> void handle_signal(int signo) { if (signo==SIGINT) printf("Se ha recibido una señal SIGINT\n"). De esta manera. } else { kill(getppid(). #include <sys/types. else printf("Se ha recibido otra señal\n"). Esta segunda vez. &act. el cual enviará dicha señal a su proceso padre. hasta que el hijo concluya también su pausa de 1 segundo y termine. envíe la señal SIGUSR1 y el padre todavía no se haya suspendido en el wait(). el proceso padre volverá a quedar suspendido. 3-10 El programa empieza cambiando el tratamiento de la señal SIGUSR1. se escribirá el mensaje “Espera no finalizada” si wait() ha fallado por algún motivo. En dicho bucle. los mensajes presentados serán: Se ha recibido otra señal. la llamada wait() será interrumpida y pasará a ejecutarse el manejador.

} main() { int i=0.sa_mask ). return 0. strcpy(buffer. sigaction(SIGALRM. dup2(fd. sigemptyset(&act.sa_flags = 0. sigemptyset(&act. el motivo de la posible terminación (terminación normal / anormal) y todas las salidas de texto que proporciona.h> #include <signal. 0666). int n=0.h> int a = 16. &act. tras haber ejecutado el sigaction() para asegurar que terminase? ¿Puede haber condiciones de carrera? . (n=read(0. void manejador(int signo) { int fd. act. } Indicar cuál será el resultado de ejecutar el programa correspondiente si éste no recibe nada en su entrada estándar durante 15 segundos. act.1)) >0 && buffer2[i] != '\n' && i<80 .sa_flags = 0.sa_handler = manejador. alarm(15). buffer).sa_mask). fd=open("f". x = 3 . "Error"). se produce SIGALRM Se ejecuta el manejador de SIGALRM que redirige la salida estándar al fichero “f” El manejador escribe “Resultado: Error” en la salida estándar de error. printf("La cadena leida es: %s\n". Describa la secuencia de ejecución del programa. NULL). fprintf(stderr. act. act. void manejador(int senyal ) { a = a . NULL ). struct sigaction act."Resultado: %s\n".&buffer2[i]. buffer2). &act. La llamada read retorna con un error (EINTR) Se escribe en el fichero “f”: "La cadena leída es: Error” Dado el siguiente ejemplo en POSIX #include <unistd. char buffer2[80] = “NADA”.1). 3-11 1) 2) 3) 4) 5) 6) 7) Se instala un manejador para SIGALRM El proceso se suspende en la llamada read Al cabo de 15 seg. O_WRONLY | O_CREAT. sigaction(SIGQUIT. for (i=0.sa_handler = manejador. detallando el efecto de las llamadas a sistema. i++).2. } ¿Cuántas señales SIGQUIT habría que enviarle al proceso resultante. while ( a > 0 ) a = a + x . Ej. } int main( void ) { struct sigaction act. si el proceso acaba.3.Dado el siguiente código: char buffer[80] = "". if (n>0) buffer2[i+1]='###BOT_TEXT###'.

} Dada la lista de afirmaciones que más bajo se relaciona.El proceso PADRE muere por una señal (acción por omisión) P4. sigemptyset(&act. El orden debe corresponderse con el orden de ejecución.. } else { /* HIJO */ dup2(fd[1].El proceso PADRE termina normalmente al ejecutar exit P2. "Hijo: leyendo\n"). while(1) write(1.. Al recuperar el control tras la ejecución de la función manejadora. 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. pues con cada señal. realice una lista ORDENADA con todas las afirmaciones que son ciertas para los procesos PADRE e HIJO.El proceso PADRE maneja la señal SIGINT P8. 3-12 Como mínimo 8.El proceso PADRE maneja la señal SIGPIPE P7.El proceso HIJO queda HUÉRFANO ejecutando un bucle infinito (INIT lo adopta) H3. } exit(1)... "Padre: esperando\n")..1). con 8 no tenemos una garantía total de que la variable "a" haya llegado al valor cero.1). Sin embargo.-C> AFIRMACIONES RELATIVAS AL PADRE P1. act.&c.El proceso PADRE ejecuta wait sin suspenderse P6. se perdería el cambio realizado en esa función manejadora Sea el siguiente código: struct sigaction act. permitiendo la salida del bucle "while" contenido en la función principal. NULL). &act.El proceso HIJO queda suspendido en estado WRITING al ejecutar write . read(fd[0].. después de imprimirse “Padre: esperando”. el valor de la variable global a se reduce dos unidades y el proceso empezaba con "a" igual a 16. close(fd[0])..&c.El proceso HIJO termina normalmente al ejecutar exit H2.. "Padre: terminando\n"). "Hijo: escribiendo\n"). wait(NULL). } main(int argc. Esto se debe a que pueden darse condiciones de carrera. void manejador(int signo){ close(fd[1]). /*sigue el main */ pipe(fd).Ej. Realice dicha lista en el siguiente supuesto: a) Se ejecuta el programa y.El proceso PADRE ignora la señal SIGINT AFIRMACIONES RELATIVAS AL HIJO H1.El proceso PADRE queda suspendido en estado WAITING al ejecutar wait P3..sa_flags = 0... if (fork()) { /* PADRE */ fprintf(stderr.. fprintf(stderr. char *argv[]) { char c.sa_mask). fprintf(stderr. act.El proceso HIJO queda suspendido en estado READING al ejecutar read H4. 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. sigaction(SIGINT.1). int fd[2].El proceso PADRE pasa del estado WAITING al estado ACTIVO P5. “Hijo: leyendo” se teclea <CTRL. pero antes de dejarlo en la posición de memoria asociada a dicha variable. pues tanto esa función principal como la función manejadora de la señal asignan valores a la variable "a". fprintf(stderr.sa_handler = manejador.

sigfillset(&sigset).El proceso HIJO maneja la señal SIGINT H11.. act.} fprintf(stderr.. H7. H8 Sea el código fuente siguiente: #include <unistd. CTRL-C (se teclea tres veces lo mismo) NOTA: tenga el cuenta que CTRL-C es la señal 2. CTRL-C. signal_received = 0. act.El proceso HIJO pasa del estado suspendido (READING) al estado ACTIVO H6.h> #include <stdio. H10. } } main(int argc.. P4. handmsg2.El proceso HIJO provoca la señal SIGPIPE al ejecutar write H8. fprintf (stderr. NULL). . if (argc != 2) {fprintf (stderr. P1 b) P2. sigfillset(&act. P7. P1 HIJO a) H3. 3-13 PADRE a) P2.sa_mask). H5. finalmente. NULL). &act.h> #include <signal. sigaction(SIGALRM. sigdelset(&sigset. alarm(15). argv[1]). int i. fprintf (stderr. a la señal: %s\n". char handmsg2[] = " han transcurrido 15 seg..H5.. luego tache las afirmaciones falsas y. ordene las verdaderas. P4.El proceso HIJO provoca la señal SIGPIPE al ejecutar read H7.. "Comienzo a trabajar\n"). sigaction(signum.sa_handler = handler. char *argv[]) { sigset_t sigset.El proceso HIJO maneja la señal SIGPIPE H10. strlen(handmsg1)). struct sigaction act. exit(0). "Programa terminado\n"). H5.El proceso HIJO muere por una señal (acción por omisión) H9. sigdelset(&sigset. P7. SIGALRM). H10. argv[0]). else{ signal_received=1. int signum.sa_flags = 0. while(signal_received == 0) sigsuspend(&sigset). void handler (int signo) { if (signo!=SIGALRM) write(2. Ej. signum=atoi(argv[1]).handmsg1. } Dicho código se corresponde con un ejecutable denominado senyal. H8 b) H3. write(2. H7. strlen(handmsg2)).\n". signum). "Usage: %s signo\n".El proceso HIJO ignora la señal SIGINT Recomendación: estudie el comportamiento del programa.. 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. &act.h> char handmsg1[] = "He capturado una señal\n". "Espera 15 seg.

h> int main (int argc.h> #include <unistd.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. 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. se encuentran enmascaradas todas las señales.h> #include <unistd.c #include <stdlib. . act. Ej. signum). alarm(15). . char *argv[]) { int val_pid. Durante la ejecución del manejador handler.h> #include <stdio.h> int main (int argc. sigdelset(&sigset. alarm(15). Programa terminado.c #include <stdlib. 3-14 A) Comienzo a trabajar Espera 15 seg. Por tanto mientras se ejecuta el manejador handler no se atenderá ninguna señal. val_pid =fork(). ). B) De cada uno de los procesos que se generan cuando es ejecutado senyal2. &act.sa_flags = 0. 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. sigaction(signum. } } senyal2. Dado los ficheros senyal1 y senyal2 que contienen los ejecutables de los códigos fuentes siguientes: senyal1.h> #include <stdio.sa_mask). NULL). &act. a la señal 2 He capturado una señal He capturado una señal He capturado una señal Han transcurrido 15 seg. NULL). char *argv[]) { int val_pid. sigaction(SIGALRM. /**********************************************************/ sigfillset(&sigset). sigdelset(&sigset. for ( . sleep(20). B) sigfillset(&act. val_pid=fork(). SIGALRM).

Ej. // (1) ESCRIBA EL CÓDIGO DEL MANEJADOR int main(int argc. crea el hijo y se suspende con sleep hasta que ocurra la alarma a los 15 segundos que finaliza el proceso padre. programa una señal que ocurre en seg segundos. &periodo).0 fin programa "). El proceso hijo se suspende con el sleep durante 20 segundos y luego finaliza. En el caso en que se introduzca un valor cero para el periodo. El programa principal tiene un bucle en el que se lee por consola este periodo de tiempo en segundos (variable periodo). Al ejecutar este código se generan dos procesos (padre e hijo) que ejecutan el mismo código. Este programa comprueba si existe correo nuevo a intervalos de tiempo regulares.h> struct sigaction act. 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. // (2) ESCRIBA EL CÓDIGO PARA INSTALAR EL MANEJADOR while (1) { // Lee el periodo desde consola printf("Periodo de consulta (segundos) . 3-15 } A) Para responder a esta pregunta son necesarios los conceptos siguientes: a) alarm(seg). deberá invocar a la función ComprobarCorreo(). if (periodo > 0) { // (3) COMPLETAR } else { // (4) COMPLETAR } } . b) la acción por omisión de una señal es terminar el proceso. char *argv[]) { int periodo. d) Los procesos atienden las señales incluso cuando están suspendidos.h> #include <signal. 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. scanf("%u". Proceso padre-15 segundos Proceso hijo -20 segundos Se pide completar el siguiente código de un programa lector de correo. El proceso padre programa la alarma. printf("Mi lector de correo\n"). El proceso padre programa la alarma. El manejador que se ejecutará al ocurrir la señal. el programa termina. 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. c) Los procesos hijos no heredan las señales pendientes. crea el hijo y ejecuta el bucle infinito hasta que ocurra la alarma a los 15 segundos que finaliza el proceso padre. Utilizad para ello la señal de alarma (SIG_ALRM).h> #include <stdio. #include <unistd.

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

" en el propio directorio. 4-1 Ej.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(). " en cada uno de los tres subdirectorios contenidos en él. • a entrada ". O_WRONLY ). 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? Aparece en la segunda columna. 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. . Para averiguar qué tripleta debe usarse hay que utilizar el UID y GID efectivos del proceso. 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. Dé un solo ejemplo de apertura de dicho fichero (es decir. Ejercicios sobre directorios y protección. 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. 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. el sistema operativo comprueba que el proceso esté autorizado para emplear el modo de apertura solicitado. Ej. Para ello.. Las entradas de directorio que corresponderían a estos cinco enlaces son: • El nombre de dicho directorio en su directorio padre. por lo que la apertura fallaría.1 Dificultad de los ejercicios Nivel de dificultad Principiante Medio Avanzado Ejercicios 4. la apertura open(“prueba”. • a entrada ". comparándolos con el UID y GID del fichero. Utilizando la orden "ls -l" se ha podido averiguar que un determinado directorio tiene 5 enlaces físicos. suponga que existe un fichero llamado “prueba” con la palabra de protección “rw-r--r--”.4. ¿En qué parte de la salida de dicha orden aparece tal información (es decir. 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. tal como se podía ver en el enunciadote la siguiente cuestión. 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. 4. Por ejemplo. en la segunda en otro caso) y en ambas no hay permiso de escritura. 4-2 Cuando se abre un fichero mediante la llamada “open()”.

vemos que resulta posible la ejecución. b) En este segundo caso. Por tanto. b) "nuevo fich1 f ich3". d) "nuevo f ich1 f ich3". con los atributos eUID=jose y eGID=so2.. al ejecutarlo adoptaremos como eUID el valor 0. con lo que la orden no podrá tener éxito. . con los atributos eUID=jose y eGID=grupo3. 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". 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. 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. ni el eUID es el del propietario del fichero ni el eGID es el del grupo del fichero. 916038 916040 916038 916040 -rw-r--r-lrwxrwxrwx -rw-r--r-lrwxrwxrwx 2 2 2 2 juan juan juan juan so2 so2 so2 so2 36 5 36 5 May May May May 23 23 23 23 20:46 20:47 20:46 20:47 fich1 fich2 -> fich1 fich3 fich4 -> fich1 Ej. todos los accesos que pida tal proceso se concederán. Al adoptar dicha identidad. con los atributos eUID=juan y eGID=grupo3 inicialmente Ej. ni coincide con el propietario del fichero “nuevo”. dándole a la copia el nombre recibido como segundo argumento. 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 drwxr-xr-x -rwsrw-r-x -rw-rw-r--rw-rw-r-2 11 1 1 1 fmunyoz fmunyoz root fmunyoz fmunyoz so2 so2 so2 so2 so2 4096 4096 1139706 634310 104157 may 8 mar 21 abr 9 abr 9 abr 9 2002 14:39 2002 2002 2002 . ¿Y las siguientes órdenes? c) "nuevo f ich1 f ich2". Como este programa tiene el bit SETUID activo. fich3 y fich4. fich2.Dada la salida proporcionada por la orden “ls –li”. sabiendo que han sido creados en ese orden. con los atributos eUID=juan y eGID=grupo3. inicialmente. En ella vemos que vara este proceso resultará imposible ejecutar el programa "nuevo". tendremos que utilizar la tripleta correspondiente al grupo. nuevo fich1 fich3 Donde el programa "nuevo" copia el fichero recibido como primer argumento. Indique de forma justificada qué tipo de enlace son fich1. la tripleta a utilizar para averiguar si podemos ejecutar "nuevo " será la tercera (correspondiente al resto de usuarios). correspondiente a "root" (el superusuario). En ella. 4-4 a) Como "jose" no es el superusuario. pero “so2” es el grupo del fichero. inicialmente. . inicialmente.

4-5 c) Como "jóse" no es igual a root. con los atributos eUID=pedro y eGID=fco. no se tendrá éxito. Ahora bastaría con tener permiso de escritura sobre dicho fichero. a) “copia1 fich1 fich2”. asumiendo que todas ellas se ejecutan en el directorio listado anteriormente. Allí comprobamos que sí que podemos ejecutar el programa. inicialmente." y comprobar que el proceso tenga permiso de escritura. Explique detalladamente si las siguientes órdenes podrán completarse con éxito o no. 4-6 a) Como copia2 tiene permisos de ejecución (concretamente para el grupo. Posteriormente intentaremos crear el fichero "fich2". No obstante la operación fallará al intentar crear el fichero fich2. Albert. pues ya existía y no necesitamos crearlo. dándole a la copia el nombre recibido como segundo argumento. También podremos leer el fichero fich1.Ej. Al hacerlo. 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 drwxr-xrwx -rwxrwxr-x -rwxrwsr-x -rw-r--rw-rw-r--rw2 11 1 1 1 1 juan juan root juan juan juan so2 so2 so2 so2 so2 so2 4096 4096 1139706 1128236 634310 104157 may mar abr abr abr abr 8 2002 . adoptaremos "pedro" como eUID. inicialmente. En este caso. ni "grupo3" es igual a so2. Seguimos estando en la categoría de otros. Por tanto. Ej. al intentar sobrescribir fich3. b) Como copia2 tiene activado el bit de SETGID. ni igual a pedro". Respuesta: FALLA. Este proceso está en la categoría de "otros" y en ella no aparece el permiso que se necesita. ya que eGID=so2) se podrá ejecutar. mientras que Blanca pertenece a grup2.. b) “copia2 fich1 fich3”. Albert y Carles pertenecen al grupo grup1. excepto en lo referente al acceso a "fich3". ya que fich3 no tiene permisos de escritura para el grupo (so2). Respuesta: FALLA. La salida del directorio actual es la siguiente: . 21 14:39 . ya que el grupo (eGID=so2) no tiene permisos de escritura en el directorio. pues este fichero tiene el derecho de lectura en todas sus tripletas. tendremos que utilizar la tercera tripleta de "nuevo". el grupo efectivo pasará a ser so2. Se dispone de un sistema con 3 usuarios. sino modificarlo. al ejecutarse copia2 con eGID=fco. 12 2003 copia1 12 2003 copia2 9 2002 fich1 9 2002 fich3 donde los programas “copia1” y “copia2” copian el fichero recibido como primer argumento. para ello hay que examinar la palabra de protección del directorio ". Intentaremos leer "fich1" y podremos hacerlo. con los atributos eUID=jose y eGID=so2. esta orden fallará d) Ocurre algo similar a la orden anterior. ya que tiene permisos de lectura. Blanca y Carles.

4-7 Londres: blanca Roma: Viena: carles albert albert albert albert Razone si un proceso puede modificar sus UID y GID efectivos durante su ejecución.h> #include <sys/stat. La segunda opción es ejecutar un programa que tenga en su palabra de protección el bit SETUID o el bit SETGID activos. Indica cual será el contenido de cada uno de los ficheros Londres.-rwxr-xr-x -rw-------rw-rw-r--r-------- 1 2 2 2 albert blanca carles albert grup1 grup2 grup1 grup1 1014 0 0 0 May Jun Jun Jun 17 27 27 27 11:10 09:11 08:49 09:11 viatge Londres Roma 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. 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.h> #include <fcntl. describa cómo podría conseguir hacerlo.h> main() { . basta con utilizar las llamadas seteuid() y setegid(). indique con qué permisos se crea dicho fichero al ejecutar el siguiente código: #include <sys/types. La primera sólo podrán utilizarla aquellos procesos cuyo UID efectivo es 0 (root).dat” no existe en el directorio actual. 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. Existen dos formas. Suponiendo que el fichero “dat. 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. Ej. Si fuera posible. Si es imposible. 4-8 Sí que puede hacerse. Para ello. según el bit que estuviera activo. indique por qué. Para ello.

mientras que la 6a proporcionaría el número menor Explique al menos una forma de obtener el tamaño de un fichero. 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 Una primera opción es utilizar la siguiente linea: tamaño = lseek(fd. 4-12 Se creará sólo con permiso S_IWUSR.dat". Una segunda opción es utilizar la llamada stat(). Ejemplo: mount /dev/fd0 /mnt (Montaría el disqueteen el directorion /mnt) .0. se podrá acceder al contenido de dicho dispositivo. a través de él. open("dat. En la salida proporcionada por dicha orden. Como resultado. estaríamos obteniendo el tamaño del fichero. 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. 4-9 Ej. Por ello. Nótese que el primer argumento de esta llamada es un descriptor de fichero obtenido previamente al abrir el fichero en cuestión. pasándole el nombre del fichero como primer parámetro y la dirección de una estructura "stat" como segundo.SEEK_END). mientras dure el montaje todo el contenido del directorio empleado como punto de montaje queda temporalmente inaccesible. el campo "st_size" de la estructura nos daría el tamaño (en bytes) del fichero. independientemente del número de dispositivos existentes en dicho equipo. Escriba un ejemplo de línea de órdenes en la que se realice una operación de montaje.O_CREAT. es decir. } Ej. Con ella obtendríamos el valor del puntero de posición resultante de mover éste en la posición final del fichero. Explique qué representan los números mayor y menor en un fichero especial ¿Cómo podemos averiguar sus valores para un fichero determinado? 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. Describa sus efectos en el grafo de directorios utilizado por el sistema. Aparte.umask(S_IWGRP|S_IWOTH). 4-10 Ej. la raíz del dispositivo montado quedará asociada al directorio empleado como punto de montaje y. Con ello conseguimos que el grafo de directorios en un sistema UNIX sea único. 4-11 Ej. Por ello. Explique en qué consiste la operación de montaje de un dispositivo en un sistema UNIX. Para ello.S_IWUSR|S_IWGRP|S_IWOTH ). la 5a columna nos daría el número mayor. pues ha sido "sustituido" por lo que hubiese en el dispositivo montado. El número menor representa el número de unidad dentro del tipo especificado por el número mayor. pues cualquier derecho presente en dicha máscara se elimina a la hora de que el proceso genere nuevos ficheros. dichos permisos no se conceden al crear un nuevo fichero. Podemos ver sus valores realizando un "ls -l" sobre el directorio "/dev". Posteriormente. 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.

En el tercer argumento de open() no hemos especificado el permiso de ejecución para el grupo. La función close () no debe ser utilizada para cerrar un directorio. 4-13 Si queremos emplear lseek() para desplazar el puntero de posición. La función POSIX a emplear para modificar un directorio se llama writedir(). sería posible obtener la que indicaba el enunciado si tuviéramos la máscara de creación de ficheros adecuada. éste ya tiene dos enlaces que no podremos eliminar hasta haberlo borrado: su nombre en el directorio padre y la entrada ". Ej. . eso querrá decir que el acceso está permitido. esta llamada fallará pues los tubos sólo admiten el acceso secuencial.O_WRONLY|O_CREAT|O_TRUNC. Razone adecuadamente por qué sucede eso. borrar el nombre de un fichero. Aunque con la llamada especificada se estaría solicitando la creación con una palabra de protección "rw-rw-rw-". No podemos utilizar la llamada open(). 4-15 V V F V F 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. la 026 La función open () no debe ser utilizada para abrir un directorio. cambiar el nombre de una entrada etc Es posible que al ejecutar la llamada open(“nuevo”. Si la llamada no falla. Sí que existe una función para averiguar esto. Es posible que al ejecutar la llamada open(“nuevo”. La función POSIX a emplear para abrir un directorio se llama opendir() Verdadero. Es lseek(). 4-14 F F V V V No existe ninguna función POSIX que permita obtener el valor del puntero de posición asociado a un determinado descriptor de fichero. debe usarse closedir() Un fichero de tipo especial (de bloques o de caracteres) tiene como mínimo dos enlaces físicos que hacen referencia a él. Falso. Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F) Ej. si queremos saber si el proceso puede escribir sobre un fichero. El número mínimo de enlaces para los ficheros especiales es uno. crear un subdirectorio.O_WRONLY|O_CREAT|O_TRUNC." en él mismo Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F) Ej. Verdadero. Cierto Para abrir un directorio necesitamos la función opendir() Un fichero de tipo directorio tiene como mínimo dos enlaces físicos que hacen referencia a él. 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. Es imposible que el sistema "añada" algún derecho a lo que pida el proceso creador. Cierto. No existe una función que directamente nos permita hacer tal cosa. Al crear el directorio. Por ejemplo. Por ejemplo. así como la ausencia del derecho de lectura en esa última tripleta. Las modificaciones de un directorio son resultados colaterales de otras operaciones: crear un fichero. podemos abrirlo en modo "sólo escritura". borrar un subdirectorio. pues fallaría. que si aparece en la palabra de protección que daba el enunciado. Verdadero.0666) se cree un fichero con la palabra de protección "rw-r-x---".0666) se cree un ficero con la palabra de protección "rw-r-----“ Sí que resulta posible. Falso. Si fallara indicaría que no tenemos ese permiso.Indique qué efecto tendrá realizar una operación lseek () sobre un tubo.

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

Declare las estructuras de datos necesarias para ello y muestre cómo quedaría la línea empleada para la creación. 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. progreso y espera limitada. 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.5. void *a4). Sabiendo que la llamada necesaria para crear un hilo es: int pthread_create(pthreadt *al.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. void *(*a3)(void *). Ejercicios sobre hilos y sección crítica 5. 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. c) Son las zonas de código en las que se acceden para lectura o escritura a las variables compartidas. deja de serlo al ejecutarlo concurrentemente. Además el resultado de la variable depende de la velocidad relativa en que se ejecuten las operaciones. b) Una condición de carrera ocurre cuando un código que ejecutado secuencialmente es correcto.1 Dificultad de los ejercicios Nivel de dificultad Principiante Medio Avanzado Ejercicios 5. pthreadattrt *a2. . Dichas actividades trabajan en común para resolver un problema y se coordinan y comunican entre sí. 5-1 a) Es un único programa constituido por varias actividades concurrentes que pueden ejecutarse de forma independiente.

&atrib. pthread_t tid. identificador del proceso en el que se encuentra el hilo. pthread_create( &tid. return null. 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. Sólo podremos encontrar atributos relacionados con la identidad del hilo. Justifique su respuesta. &valores). vector). pthread_create( &th1. } int main (void) { pthread_t th1.. Nota: retraso(n) realiza un retraso de n milisegundos void * funcion_hilo1(void * arg) { retraso(4000+rand()%1000). pthread_attr_t atrib. 5-2 Ej. NULL. pthread_create( &tid. 1ª opción: int vector[5]. null).. pthread_create( &th2. function_hilo2.Ej. sumador. con la gestión de sus recursos (apenas tiene recursos. n4. } void * funcion_hilo2(void * arg) { retraso(4000+rand()%1000). printf(“Hola Don Jose\n”). sumador. } args. typedef struct { int ni. function_hilo1.th2. null). } . exit(0). n5. mapa de memoria referente a su pila. &atrib. printf(“Eran dos tipos requeeeteeefinos. estado de planificación.\n”). n3. pthread_attr_init( &atrib ). NULL. Cite al menos tres atributos que podríamos encontrar en el "bloque de control de hilo". 5-3 Tenemos dos opciones: utilizar un vector donde dejaremos los cinco enteros o declarar una estructura con cinco campos de tipo entero. args valores. 2ª opción: pthread_t tid. n2. pues comparte los asignados al proceso) y con su planificación Ejemplos de estos atributos pueden ser: identificador del hilo. printf(“Hola Don Pepito\n”). return null.

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; pthread_mutex_lock(&mutex); void proc1(char *arg){ pthread_mutex_lock(&mutex); printf(arg); pthread_mutex_unlock(&mutex); if (strcmp(arg,”2”)) pthread_exit(0); } 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); } main (){ pthread_t id1,id2,id3;

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;

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

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 de cambio de contexto que violen la exclusión mutua. La secuencia presentada en el ejercicio es uno de estos 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> 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); }

int turno=0;

void *hilo_0(void *p) { while(1) { while (turno != 0); /* Sección crítica */ turno = 1; /* Sección restante */ } }

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ó.

debido a un cambio de contexto tras haber ejecutado "flag[i]=1. Por desgracia. /* Compartida */ hilo_i(void) { while ( 1 ) { sección_restante." en el primero que intentaba entrar). éste último "detalle" puede no cumplirse en este ejemplo. Por tanto. flag[i] = 1. la propiedad de progreso no siempre se cumple y ésta no es una buena solución al problema de la sección crítica. progreso y espera limitada. flag[i] = 0. while(flag[(i+1) % 2] ) . Si los dos hilos han conseguido fijar sus flags a cierto (por ejemplo. aunque esto garantiza la propiedad de espera limitada. si que existe una cota sobre el número de veces que otros hilos pueden adelantar a un candidato. } } hilo_i(void){ while ( 1 ) { sección_restante. también puede conducir a violar la propiedad de progreso. logra poner su flag a cierto. int turno = 0. como ya hemos visto arriba a) Ambos hilos ejecutan el código de la columna derecha Ej. Desafortunadamente. si uno ejecuta su sección crítica el otro no podía estar ejecutando su sección cuando aquél entró. como no se toma la decisión en un tiempo finito.Razone adecuadamente si el siguiente código es una buena solución al problema de la sección crítica para dos hilos. c) Espera limitada: Para que esta condición se cumpla. turno = (i+1)%2. Está claro que si un hilo llega al protocolo de entrada. Por tanto. 5-9 . sólo si el otro no ha pedido entrar. tampoco ha podido lograrlo porque el flag del que ya está dentro lo evita. sí que se cumple la exclusión mutua. y dicha cota es cero. Es decir. flag[i] = 0. Por tanto. 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". con identifícadores (valores de la variable i) 0 y 1. Si después ha intentado entrar el que estaba fuera. } } a) Ambos hilos ejecutan el código de la columna izquierda Una buena solución al problema de la sección critica debe cumplir exclusión mutua. flag[i] = 1. while(flag[(i+1)%2] && (turno==(i + 1) %2) ) . Por tanto. sección_crítica. 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. Con ello garantiza que el otro ya no podrá entrar. el protocolo de entrada utilizado es incapaz de elegir qué hilo debe entrar. 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. asumiendo que las variables flag[0] flag[1] están compartidas e inicialmente valen cero. bloqueándose ambos mutuamente en dicho protocolo. sección_crítica.

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. 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. NOTA: Suponga que tanto las operaciones de incremento y decremento como las de comparación son atómicas. como es el más prioritario no tendrá por que parar. #include <unistd. Si ninguno de los dos está en el protocolo de entrada cuando sale el que ya estaba en la sección crítica. pueden darse ciertas situaciones con algunos algoritmos de planificación que conducirían a una situación de bloqueo. o bien había intentado entrar y le había cedido el turno. si utilizáramos planificación por prioridades expulsivas estáticas. Aquí se está violando la primera de esas restricciones. sigue cumpliéndose la espera limitada. 2) Se crea el hilo 1 y empieza a ejecutarse. 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). Para que esté ejecutándola. Como flag[0] está a 1 y él mismo se ha encargado de poner turno=0. Esta situación parece violar la propiedad de progreso. Progreso: Aquí el progreso también se cumple. Sea un proceso con un conjunto de hilos como se muestra en el código siguiente de manera esquemática. seguro que ha puesto su flag a cierto. entonces la variable "turno " servirá para deshacer el empate y dejar que sólo entre uno de ellos.h> #include <stdio. Podría servirnos para demostrar que la espera limitada no se cumple. Las funciones “seccion_critica_i()” hacen referencia a un conjunto de instrucciones que deben ejecutarse en exclusión mutua por el hilo H[i]. pues ninguno de los dos hilos supera su respectivo protocolo de entrada. Si los dos hilos hubieran pedido la entrada. pues como máximo puede haber un hilo dentro de la sección crítica. han decidido que quien debe entrar es el hilo 0 . el otro hilo no conseguirá entrar. ha tenido que ocurrir algo más: o bien el otro hilo no había intentado entrar y tenía su flag a falso. este hilo 1 no consigue superar su protocolo de entrada y queda perpetuamente en su bucle "while" de dicho protocolo. ésta queda disponible para el primero que llegue.Esta solución cumple las tres propiedades. En cualquiera de las dos situaciones. Ej. 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. No obstante aunque se satisfacen las tres propiedades. 5-10 main(int argc. un algoritmo como el explicado no lo podemos utilizar como base para fijarnos en el cumplimiento de LAS TRES propiedades. Es decir. El otro conseguirá entrar cuando el que estaba dentro ponga su flag a cero. teniendo el hilo 0 menor prioridad que el hilo 1 y asumiendo que el hilo 0 fue creado antes que el hilo 1. char *argv[]) { pthread_t H[N]. a) Indique las características del protocolo de entrada. Si sólo desea entrar un hilo él ser el único que habrá fijado su flagy en ese caso. se garantiza la exclusión mutua. Los protocolos de entrada con su estado obtenido a partir del anterior paso 2). pero jamás para demostrar que el progreso no es viable.h> #define N 5 int S[N]. Ahora. No obstante. Por tanto. no es así. no tendrá que quedarse iterando en el bucle while del protocolo de entrada. Espera limitada: La gestión de la espera limitada es similar a la vista en la cuestión 3. Por ejemplo. . como veremos seguidamente: a) Exclusión mutua: Si un hilo está ejecutando su sección crítica. 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. Sin embargo. b) Indique de forma justificada la validez o no de este protocolo como solución al problema de la sección critica. el número de veces que podrá adelantarlo será como máximo uno. Por tanto. pues el hilo menos prioritario jamás podrá avanzar. 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. progreso y espera limitada.

CRITICA if (id>0) { while(S[id]==S[id-1]). id = (int) arg. &b ). B) Hay un derecho rotativo a entrar en la sección crítica. } Ej.NULL). for (i=0. } } 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. CRITICA if (id>0) { S[id]=S[id-1].} if (id==0) { S[0]=(S[0]+1)mod 2. i<N. a = (int) arg. y así sucesivamente.} pthread_exit(0). seccion_critica_4(). i<N. while(*c > 0) { *c = *c – 1. for (i=0. grupos de una o más líneas consecutivas) deberían considerarse secciones críticas dentro de cada una de esas dos funciones. seccion_critica_1(). seccion_critica_2(). La espera activa presenta problemas de infrautilización del procesador. i<N. tanto en sistemas de planificación por prioridades como en los de turno rotatorio. seccion_critica_3().NULL. void *hilos_I (void *arg) {int id. El proceso que entra en la sección crítica es el que tiene su variable S distinta del proceso que le precede. */ int a=10. En general. (void *) i). */ scanf( “%d”. marque dentro del código presentado arriba qué regiones (es decir. sleep(1). . i++) S[i]=0. Dado el siguiente código de un programa POSIX Variables globales.} if (id==0) if (id==1) if (id==2) if (id==3) if (id==4) seccion_critica_0(). c = &a . } } void *f2(void *arg) { int *c. las soluciones de turno rotatorio no cumplen con la condición de progreso del problema de la sección crítica. a = a + 1. 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]. i++) pthread_create(&H[i]. hilos_I.int i. } //PROTOCOLO SALIDA SECC. void *f1(void *arg) { int b=10. 5-11 A) Se trata de un espera activa con todos los inconvenientes y ventajas de la misma. for (i=0. while( b > 0 ) { /* Leemos b desde teclado.} if (id==0) { while(S[0]!=S[N-1]). salvo para el hilo H[0] que entra en sección critica cuando su variable S coincide con la del proceso que le precede. b=15. //PROTOCOLO ENTRADA SECC. i++) pthread_join(H[i].

a) Ambos hilos ejecutan el código de la columna derecha Esta solución cumple la exclusión mutua y la espera limitada. } } hilo_i(void){ while ( 1 ) { /* Sección restante */ flag[i] = 1. } } a) Ambos hilos ejecutan el código de la columna izquierda No es una buena solución pues no siempre garantiza la exclusión mutua. La espera limitada se cumple puesto que el uso de la variable "turno " evita que. la propiedad de progreso no se cumple. No se cumple tampoco la espera limitada. pondrán los dos flags a cierto. 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. si sólo hay un hilo que quiere entrar puede hacerlo sin problemas. tendremos ambos flags a 1 y la variable "turno" al valor 0. 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. /* Sección crítica */ flag[i] = 0. turno = (i+1)%2. int turno = 0. if (flag[(i + 1)%2]) while(turno==(i + 1)%2) . Sí que cumple progreso. cuando los dos hilos pretenden entrar. itálica y subrayado. uno pueda adelantar al otro más de una vez Por el contrario.SOLUCIÓN: Se han marcado las líneas de las secciones críticas en negrita. /* Compartida */ hilo_i(void) { while ( 1 ) { /* Sección restante */ while(flag[(i+1) % 2] ) . 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. 5-12 . Por ejemplo. con identifícadores (valores de la variable i) 0 y 1. En el hilo f2 se utiliza el puntero "c" para acceder a la variable global "a". flag[i] = 1. 5-14 Ej. 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. Cuando el hilo 0 sale de la sección crítica pone flag[0] a O. no se está respetando la propiedad de progreso pues el único candidato a la entrada en la sección crítica no conseguirá entrar. 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. 5-13 Ej. Además. permitiendo que el otro hilo pueda entrar si logra obtener la CPU. pero esto no abre el paso del hilo 1. asumiendo que las variables flag[0] flag[1] están compartidas. Razone adecuadamente si el siguiente código es una buena solución al problema de la sección crítica para dos hilos. si el hilo 0 estaba en la sección crítica y entonces el hilo 1 llega a su protocolo de entrada. Además. Todas las instrucciones que posteriormente utilicen "c" para leer o modificar el contenido de "a " deben considerarse secciones críticas. Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F) Ej. La exclusión mutua se cumple porque si ambos intentan entrar. 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. 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. /* Sección crítica */ flag[i] = 0. Por ello.

malgastando inútilmente el tiempo de CPU que les otorgue el planificador. 5-15 V F F F V Mediante la función pthread_self() se puede obtener el identificador de un hilo. En los programas escritos para ejecutarse a nivel de usuario. todo el proceso ha quedado suspendido y ninguno de los otros hilos podrá continuar. la espera activa no podrá eliminarse. El código visto en la cuestión 2 utiliza espera activa. El segundo argumento de esta función sirve para obtener dicho valor. 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. A consecuencia de esto.Ej. Falso. Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F) Ej. no es viable a nivel de usuario. Aunque podría ser una solución válida al problema de la sección crítica. Falso. Por ello. puesto que un uso inadecuado de las operaciones necesarias para esta gestión de las interrupciones podría dejar "coleado " al equipo Si el soporte a hilos se encuentra en el núcleo. 5-16 V V F F F Mediante la función pthreadjoin() se puede obtener el valor retornado por la función principal de un hilo. el planificador de éste puede gestionar de manera independiente a cada uno de ellos. al suspenderse uno de los hilos. 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. Si alguno de ellos estuviera preparado podría entonces pasar a ejecutarse. 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. Si alguno de los hilos de nivel de usuario utiliza alguna llamada que suspende a dicha unidad de ejecución. los demás podrán seguir ejecutándose. Para ello necesitamos algún mecanismo facilitado por el sistema operativo. no quedan ya otras unidades disponibles para ejecutar al resto de los hilos de nivel de usuario. Si los hilos están soportados por el núcleo. si se suspende alguno. Serán los semáforos. Cierto Esta función es la que debe utilizarse para obtener el identificador. . el resto mantendrán el estado que previamente tuvieran. Podríamos escribir programas multihilo que sincronizaran sus accesos sobre las variables compartidas que pudiera haber. los hilos que no puedan acceder a la sección crítica estarán ejecutando un bucle vacío. Falso. Cierto. no podemos hablar de que haya o no espera activa pues este problema sólo puede aparecer en los protocolos de entrada Si los hilos están soportados por el sistema operativo. que estudiaremos en el tema 8 Todos los programas que utilizan múltiples hilos de ejecución presentan condiciones de carrera No tienen por qué. pues en los protocolos de entrada. en un sistema sin soporte a hilos. siempre cuesta más un cambio de contexto entre procesos que entre hilos. Esto se debe a que la información del contexto ahora está distribuida entre el PCB y el bloque de control de hilo. resulta recomendable utilizar la inhabilitación/habilitación de interrupciones para solucionar el problema de la sección crítica. Las soluciones vistas en las cuestiones 3 y 4 utilizan espera activa Cierto. 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. En ese caso se evitan las condiciones de carrera 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.

tanto sin son creados a nivel de usuario como de núcleo. Tarea_2(). 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. Siempre que un hilo ejecuta la llamada pthread_exit(). 5-18 Procesos Pesados int main (void){ if (fork() == 0) Tarea_1(). el hilo finaliza voluntariamente su ejecución y se libera toda la memoria correspondiente al proceso pesado que soporta al hilo. mientras que los hilos soportados a nivel de núcleo pueden comparten datos y código. Tarea_1. Ej. Los hilos de ejecución creados a nivel de usuario pueden compartir datos. Una llamada al sistema bloqueante (read(). write()) por parte de un hilo. 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. con independencia de si han sido creados en modo núcleo o usuario. } } Procesos Ligeros int main(void){ pthread_1 hilo1. 5-17 V F F F V F Se desea desarrollar una aplicación que ponga en funcionamiento dos tareas que se deben ejecutar en orden. else { wait(NULL). El cambio de contexto entre hilos de ejecución soportados a nivel de usuario es más rápido que el de hilos soportados a nivel de núcleo.NULL) pthread_join(&hilo1) Tarea_2(). implica que todos los hilos pertenecientes al mismo proceso se bloqueen. es el planificador del sistema. pero no código. El responsable de la planificación de hilos. juego de registros y su propia pila . 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). pthread_create(&hilo1. exit(0) } .Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F). NULL.

Nota: Las variables y funciones que no están definidas dentro de las funciones agrega y resta son definidas como globales. al ser accedida únicamente mediante operaciones atómicas (test_and_set y asignación. pthread_exit(0).ct<REPE. Úsese como base la solución basada en test_and_set con espera activa. utilizando las funciones test_and_set y yield(). 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. } void *resta (void *argumento) { int ct. for (ct=0. y da al planificador la oportunidad de seleccionar otro proceso para su ejecución. V=tmp. llave=0.ct++) { while(test_and_set(&llave)==1). } printf("->RESTA (V=%ld)\n".ct++) { while(test_and_set(&llave)==1). son indivisibles) no plantea ningún problema de acceso concurrente. por tanto.V). tmp=V. V). Por otra parte. tmp=V.1 Dificultad de los ejercicios Nivel de dificultad Principiante Medio Avanzado Ejercicios 6.tmp. implantados con la variable “llave” mencionada en el párrafo anterior. Ej.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. Ejercicios sobre semáforos y monitores 6. la variable “V” está protegida por un protocolo de entrada y otro de salida. llave=0. Void *agrega (void *argumento) { int ct. el proceso que la invoca (que permanece en estado PREPARADO) libera la CPU. for (ct=0.tmp. tmp--. tmp++. V=tmp. que pueden ser completadas en una sola instrucción máquina y.6. } Recursos compartidos: variables “llave” y “V”. } printf("->AGREGA (V=%ld)\n". 6-1 . La variable “llave”. pthread_exit(0). De esta forma.ct<REPE. 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.

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. 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. /* Sección crítica */ llave = 0. /* Sección crítica */ llave = 0. //sc llave=false. /* Sección restante */ } } /* Solución b */ void *hilo(void *p) { while(1) { while (test_and_set(&llave)) usleep(1000). 6-2 a) reducir el tiempo desperdiciado en la espera activa. /* Solución a */ void *hilo(void *p) { while(1) { while (test_and_set(&llave)). //sr Suponiendo que la variable global llave tiene valor inicial 0 y que la función test_and_set está definida correctamente. 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. /* 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.Ej. Nota: la función usleep() suspende al hilo que la invoca una cantidad de microsegundos. //sr while(test_and_set(&llave)) yield(). abandona la CPU voluntariamente nada más comprobar que está ocupada. Podemos considerar que este tiempo ha sido malgastado en una “espera activa”. No consume todo su cuanto de tiempo comprobando la variable “llave” y por lo tanto es más eficiente. b) int llave=0. . En la solución “b” el hilo que espera a que la sección crítica quede libre.

} .... inicializado a cinco void *hilo_i(void *p) { .. 6-5 a) establecer un orden de precedencia en la ejecución de zonas de código de distintos hilos. . por ejemplo: (1) Establecer un cierto orden (precedencia) en la ejecución de zonas de código de diferentes procesos/hilos Supongamos dos hilos. Enumera al menos otras dos aplicaciones de los semáforos y ponga un ejemplo simple de cada una de estas aplicaciones: Pueden servir para sincronizar procesos en general. V(sinc).. Definimos un semáforo compartido “sinc”.En relación a la utilización de semáforos según la definición de Dijkstra.. F2. 5 hilos pasen por un determinado lugar del código. .. 6-4 Ej. donde se llama a la función “F” Definimos un semáforo compartido “max_5”. . F1. P(sinc)... } (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.. “hilo1” e “hilo2”. 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. a) inicializado a 0 b) inicializado a 1 c) inicializado a un valor positivo mayor que 1 Ej. } void *hilo2(void *p) { . como mucho. V(max_5).Queremos que “hilo1” ejecute la función “F1” antes que “hilo2” ejecute la función “F2”.. dar un ejemplo de qué problema podría resolver en función de su valor de inicialización. inicializado a cero void *hilo1(void *p) { . P(max_5).. F.

Si eso ocurriera el sistema no sabría como responder a tal operación V. . 6-6 No se puede inicializar a dicho valor porque estaría indicando que el semáforo tiene un hilo suspendido en su cola. Sección critica de Proc2(). 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 Proc2() void Proc1() {…… {…… P(S). pues buscaría en la cola de hilos suspendidos en el semáforo y no encontraría nada. 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. F F V F Un proceso puede suspenderse al realizar una operación V sobre un semáforo. Un proceso siempre se suspende al realizar una operación señal (signal) sobre una variable condición. Un proceso siempre despierta a otro proceso al realizar una operación V sobre un semáforo. V(S). Un proceso siempre despierta a otro proceso al realizar una operación señal (signal) sobre una variable condición. P(S). Ej. 6-8 F F 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. Indique para cada una de las siguientes afirmaciones si es verdadera (V) o falsa (F). cuando eso no es cierto.Explique por qué razón no se puede inicializar un semáforo a un valor negativo. Sección critica de Proc1(). Un proceso siempre se suspende al realizar una operación espera (wait) sobre una variable condición. 6-7 Ej. V(S). Un proceso siempre se suspende al realizar una operación P sobre un semáforo. Ej. …… …… } } 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.

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. sc(). V(S2). ha de asegurarse que las funciones “sc()” se ejecuten en exclusión mutua. secuencia2(). Dado el siguiente código. P(S2). P(S2). H2. P(S3). secuencia2(). P(S1). V(S4). V(S3). 6-9 HILO 2 P(S1). Además. V(S1). S3(0). 6-10 HILO 3 P(S1). V(S1). P(S1). Secuencia1(). V(S1). secuencia3(). V(S1). HILO 3 P(S1). P(S3). P(S2). sc(). HILO 1 P(S1). S2(0). secuencia3(). Indique qué valor inicial deberán tener los semáforos que haya utilizado. Valores iniciales de los semáforos: S1(1). Ej.V(S2). que los hilos presentados están soportados por el núcleo del sistema operativo. secuencia2(). ha de asegurarse que las funciones “sc()” se ejecuten en exclusión mutua. P(S3). y suponiendo que todos los semáforos tenían valor inicial cero. V(S3). Indique qué valor inicial deberán tener los semáforos que haya utilizado.P(S3). sc(). .Dado el siguiente código. S3(0) HILO 1 HILO 2 P(S1). V(S1). P(S2). sc(). sc(). V(S1). Además. H3 V(S2). sc(). se encontraban en la cola de preparados en el orden H1. V(S3). V(S3). V(S2). Si hay varias funciones con el mismo nombre. P(S2). V(S2). V(S1). V(S1). H2 P(S2). V(S1). sc().P(S1). secuencia3(). secuencia1(). V(S1). 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. 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. H3 (es decir. sc(). sc(). Si hay varias funciones con el mismo nombre. Ej. H1 será el primero en obtener la CPU y H3 el último) y que se utiliza un algoritmo de planificación FCFS: H1 P(S1). P(S1). 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. Dado el siguiente código. V(S1). sc(). S2(0). Valores iniciales de los semáforos: S1(1). secuencia4().P(S3). secuencia4(). V(S2). P(S1).

sólo termina H2 en el paso 8. Como dicho semáforo tiene valor 1. lo deja a cero y termina. Ej. Se desea sincronizar la ejecución de dos hilos H1 y H2. en este caso en P(S1). P(S1). H2 queda suspendido en el P(S3). Ej. se encontraban en la cola de preparados en el orden H1. despertando a H1 que quedará el 2º en la cola de preparados. H1 realiza un P(S3). H2 realiza un V(S3) y también deja ese semáforo a 1.V(S1). sólo termina H3 en el paso 10. P(S3). H3 ejecuta P(S4) y queda suspendido. indique en qué operación se ha quedado suspendido. 6-12 El orden de ejecución de estas operaciones será: H1 ejecuta P(S3) y queda suspendido. reactivando a H1 y dejando S3 a cero. P(S1). V(S4). H3 realiza un V(S1). pues S4=-1. P(S3). lo deja a valor -1 y queda suspendido. H3 V(S2). V(S1). P(S1). pues S2=-1. H3 ejecuta P(S1). dejando tal semáforo a 1. H2 ejecuta P(S3) y queda suspendido. pues S3=-1. P(S4). En caso de que algún hilo no termine. indique en qué operación se ha quedado suspendido. Indique el orden de terminación de dichos hilos. V(S3). H2 realiza un P(S2) y también queda suspendido. pues S3=-1. H3 realiza un V(S2). H3 ejecuta V(S1). P(S4). Indique el orden de terminación de dichos hilos. que los hilos presentados están soportados por el núcleo del sistema operativo. H2 ejecuta V(S3). despertando a H2 que quedará el 1º en la cola de preparados. definidos como sigue: . reactivando a H2 y dejando S2 a cero. V(S3). H3 realiza un P(S1) y queda suspendido. pues S2=-1. H1 ejecuta P(S1) y queda suspendido. H2 ejecuta V(S4). 6-11 Se siguen estos pasos en la ejecución del código: H1 realiza un P(S1) y se queda suspendido. V(S3). H3 ejecuta V(S2). H3 (es decir. H2. V(S3). dejándolo a cero. H1 será el primero en obtener la CPU y H3 el último) y que se utiliza un algoritmo de planificación FCFS: H1 P(S3). y asumiendo que todos los semáforos tenían valor inicial cero. reactivando a H3 y dejando S4 a cero. H2 realiza un V(S4). Dado el siguiente código. H1 ha quedado suspendido en P(S3) y H3 también está suspendido. H3 sale del P(S4) y termina. Por tanto. pues S1=-1. H2 ejecuta P(S2) y queda suspendido. Por tanto. pues S1=-1. En caso de que algún hilo no termine. H2 P(S2). mientras H1 queda suspendido en el P(S1). dejándolo a 1. V(S1). pues S1=-1. H2 realiza el P(S3).

El primer hilo en completar su fragmento de código (A o B) debe espere a que el otro hilo complete el suyo. V(S2). Ej. } ------------------------while(true) { P(S2). y = x * 4. ⇒ x = 30. 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). while(true) { A V(S2). y = y + 1. x = y * 2. V(S1). y = y + 1. y = 8 + x. Proceso C P(S1). y = 8 + x. y = y + 1. S2=0 y S3=1. Existen dos posibles soluciones que vienen dadas por las siguientes secuencias de ejecución: 1) x = x + 1. V(S3). ⇒ x = 20. B V(S1). P(S1). y=4. Proceso B P(S1). x = x + 1.h> int main() Ej. es decir. x = x + 1. Proceso A P(S2). y = x * 4. y = 11 2) x = y + 2. Diseñe una solución utilizando semáforos que cumpla dichos requisitos. A V(S2). } ------------------------while(true) { B V(S1). no se puede asumir nada sobre la duración de los fragmentos A y B. V(S3). V(S3). 6-13 Variante 1 Semaforo S1=0. Semaforo S2=0. 6 14 . P(S2). x = y * 2. Los valores iniciales son los siguientes: x=1. } Comente qué valores posibles tendrían las variables x e y al finalizar la ejecución de los siguientes tres procesos concurrentes. P(S3). x = y + 2. Semaforo S2=1.H1 while (true) {A} H2 while (true) {B} Donde A y B representan fragmentos de código. P(S3). y = 8 + x. S1=1. y = 16 ¿Cuales son los posibles valores que tomará x como resultado de la ejecución concurrente de los siguientes hilos? #include <semaphore. Para realizar la sincronización. while(true) { P(S1). ni sobre la velocidad relativa de H1 y H2. x = y * 2. } Variante 2 Semaforo S1=1. P(S3).

} void *func_hilo2(void *b) { sem_wait(&s2). 3) 4) 5) 6) 7) 8) 9) 25) 26) 27) 28) .func_hilo1.NULL). Si se ejecuta func_hilo1 (sin interrupción) y. int x. sem_wait(&s1).0. no llega a implicar la suspensión de los procesos (ya que no realiza una E/S que necesite espera): #include <pthread. sem_wait(&s3). por tanto. pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER. pthread_mutex_unlock(&mut). if (a==0) pthread_cond_wait(&cond. En el segundo resultado ocurre un interbloqueo. /*Inicializa a 1*/ sem_init(&s2. sem_wait(&s2). *hilo2(void *a2) { pthread_mutex_lock(&mut). sem_post(&s1). } { pthread_t h1. pthread_exit(0).\n" ).\n” ). x=20 y x=1. printf( "Hilos creados.\n" ). x=x+1. donde se supone que la función printf() escribe directamente en memoria de vídeo y.func_hilo2.NULL.NULL. pthread_mutex_lock(&mut).s3.0. pthread_cond_signal(&seg).\n" ). 6-15 int main(void) { pthread_t h1. /*Inicializa a 1*/ sem_init(&s3. x = 1. hilo1. sem_post(&s2). pthread_create(&h2.NULL. hilo2. printf( “Hilo 1 espera. pthread_exit(0).1). printf( "Termina hilo 1. sem_init(&s1. a continuación. pthread_cond_signal(&cond). Ej. Dado el siguiente programa escrito en C con primitivas de sincronización POSIX.NULL). if (a!=0) pthread_cond_wait(&seg. pthread_join(h1. el resultado es 20. se ejecuta func_hilo2. void 14) 15) 16) 17) 18) 19) 20) 21) } void 10) 11) 12) 13) *hilo1(void *a1) { pthread_mutex_lock(&mut). pthread_cond_t seg = PTHREAD_COND_INITIALIZER. pthread_join(h2. a=15.1). x=10*x.\n” ).\n” ).&mut).h> #include <stdio.NULL). El semáforo s3. printf( “Hilo 1 avisa. void *func_hilo1(void *a) { sem_wait(&s1). /*Inicializa a 0*/ pthread_create(&h1.0. 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.h> #include <stdio. 23) 24) } printf( "Termina hilo 2. pthread_join(h1. return 0.NULL).NULL.#include <pthread. sem_post(&s2).0). printf( "Esperando hilos.h> sem_t s1. sem_post(&s1). inicializado a 0.\n" ).&mut). pthread_join(h2.NULL).NULL). } De la ejecución del código anterior se obtiene dos posibles valores de x. printf( “Hilo 2 espera. pthread_create(&h1. pthread_cond_t cond = PTHREAD_COND_INITIALIZER.h2.h2 .h> int a=0.s2.h> #include <stdlib. obliga a que la sentencia x= 10 * x se ejecute después de la sentencia x = x+1. sem_post(&s3).NULL).NULL).\n" ). printf( "Hilo 1 terminado. pthread_mutex_unlock(&mut). 1) 2) pthread_create(&h2. printf( "Hilo 2 terminado.\n" ).

Utilizando semáforos lo tendría mucho más complicado. cuántos hilos terminarán la ejecución del código mostrado y en qué orden. Además. convirtiéndose en el propietario de “mut”. Este va superando sin suspenderse todas las instrucciones que le quedaban. Hilo 1 avisa. } Indique qué se mostrará en pantalla al ejecutar el programa anterior. hilo 1 permite que hilo 2 vuelva a estar preparado. 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. Hilo 1 terminado. pues el hilo 1 todavía no había terminado. en el paso 14 podrá cerrarlo y en el paso 16 no se suspenderá pues no se cumple la condición. Con ello vuelve a dejar el mútex abierto. 6-16 Ej. Si algún hilo no consigue terminar indique por qué motivo no logra hacerlo. En el paso 6. Termina hilo 2. que en el paso 22 deja definitivamente a “mut” abierto y en el 24 termina. el aviso no tiene ningún efecto. Así. dejando el paso libre al hilo principal. 6-17 Se muestra en el propio listado del enunciado el orden de ejecución de las diferentes instrucciones. Asuma un algoritmo de planificación FCFS. Termina hilo 1. La salida por pantalla quedaría: Hilos creados. . ya no tendrá que preocuparse por el problema de la sección crítica. El algoritmo de planificación FCFS no sustituye al hilo actualmente en ejecución hasta que éste se suspenda. En el paso 19. atiende a los hilos según su orden de llegada a la cola de preparados. al abrir el mútex. el hilo 1. Esperando hilos. Hilo 2 terminado. hilo 1 termina y deja preparado al hilo principal. Pero el primero dentro de la cola de preparados era hilo2. Ahora empieza el hilo 2. pues “a” era diferente a cero. En el paso 18 avisa a hilo 2. Como los monitores son una construcción lingüística que ya proporciona de forma automática la exclusión mutua. la variable a toma el valor 15 y en el paso 7 se abre de nuevo el mútex. Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F). pues no había ningún hilo suspendido en tal condición. Hilo 1 espera. que puede cerrar el mútex en el paso 10 y se suspenderá en el paso 13. pues los dos hilos que debía esperar ya han terminado. En el paso 21. Hilo 2 espera. el programador obtendría con ellos un modelo de programación mucho más sencillo. En el paso 5. Ej. Así.22) pthread_mutex_unlock(&mut). el hilo principal queda suspendido. Explique qué ventajas aporta el uso de monitores sobre el uso de semáforos. En el paso 9. Si sólo utiliza variables compartidas por los hilos que estén protegidas por los monitores. en los pasos 1 y 2 se han creado los hilos H2 y H1 y se han dejado en ese orden en dicha cola.

Por ejemplo.Ej. First Out). si actualmente un mútex tiene un hilo propietario o está abierto. . En el ejemplo citado. En algunas versiones de Linux no se devuelve tal error. Una operación pthread_cond_signal() puede suspender al hilo que la ejecuta. Si el mútex estaba abierto. Para ello. Falso. No existe ninguna forma de averiguar. Cierto. deberían devolver un error al realizar la operación pthread_mutex_wait() (véase la documentación del estándar POSIX). 6-19 F F F V V Una operación pthread_mutex_lock() siempre suspende al hilo que la ejecuta. Razone adecuadamente si el siguiente código es una buena solución al problema de la sección crítica. deberíamos combinar los mútex con las variables de tipo condición. Ej. Cierto. donde S es un semáforo compartido por todos los procesos o hilos con acceso a dicha sección crítica. si había hilos suspendidos en la condición utilizada y éstos son todos más prioritarios que el que la ha ejecutado. 6-18 F F F F V Una operación V(S) puede llegar a suspender al proceso que la ejecute. dependiendo del valor que tenga el contador del semáforo. La operación pthread_cond_signal() nunca suspende. La operación pthread_cond_broadcast() no debería emplearse. pues depende del valor que tuviera el contador del semáforo. Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F). Los monitores son un ejemplo de construcción lingüística utilizable para sincronizar hilos o procesos. Sí que puede hacerse mediante la función pthread_mutex_trylock(). pero el programa acaba abortando al intentar avisar al hilo suspendido. De otra forma. no se realiza una suspensión. no siempre se despertará algún hilo o proceso. Las variables condición deben utilizarse SOLAMENTE dentro de regiones de código protegidas mediante un mútex. sino una expulsión. sin suspenderse. no le suspende. con valor inicial 1. 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. La expulsión conlleva pasar al hilo al estado PREPARADO. En los semáforos se debe tomar la precaución de haberlos inicializado con el valor 1. 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(). pero no al SUSPENDIDO. Una operación de este tipo jamás suspenderá a un proceso o a un hilo. Aunque sí que sirven para garantizar exclusión mutua. Además. se sabe que la política utilizada por el sistema operativo para reactivar a los procesos suspendidos es LIFO (Last In. Las variables condición permiten suspender a los hilos fuera de las regiones de código protegidas mediante mútex. 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. no sirven para la segunda tarea de utilización de los semáforos: garantizar un orden de ejecución entre dos o más funciones de diferentes hilos o procesos. Utilizando sólo mútex POSIX se pueden realizar las mismas labores de sincronización que con los semáforos propuestos por Dijkstra. pues ambas herramientas suspenden al hilo que intenta entrar en la sección cuando ésta ya está ocupada. Al utilizar la operación V(S) sobre un semáforo.

pues no satisface todas las condiciones exigibles a una solución correcta (exclusión mutua. barrera=0. Con ello se obtiene un efecto similar al de la reactivación en cascada que se daba en algunos modelos de monitor. 6-20 No es una buena solución. P(barrera).. Úsese la función "int test_and_set (int * objetivo). if (c == n) V(barrera). . #include <stdlib. usando espera activa. De hecho. secció_prévia (. bastaría con añadir esta línea tras la P(barrera) original del enunciado.h> #include <stdio. si no paran de llegar otros procesos a sus respectivos protocolos de entrada. comptador sem mutex=1. c=0. // Nombre de fils.. ésta solución es incorrecta.h> #include "testandset. de forma que todos pueden continuar con la sección posterior únicamente cuando los n han completado la sección previa. pues un proceso puede haber llegado a su protocolo de entrada y. c=c+1. El siguiente pseudocódigo utiliza semáforos (según la nomenclatura original de Dijkstra). 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.. 6: V(barrera) Rellene el código siguiente.". Sección restante } Ej.) P(mutex). */ int test_and_set (int * objetivo). Sección crítica V(S). El primero que llegó encontrando la sección crítica ocupada no podría entrar jamás.while(1) { P(S). secció_posterior (.h" /* test_and_set: devuelve 1 (cierto) si llave esta siendo utilizada. para proporcionar un protocolo de entrada y salida a la sección crítica del problema de los filósofos. no satisface la espera limitada. V(mutex).) Ej. que lo dejaría con el valor –(n-2) sin conseguir el objetivo propuesto en el enunciado. para sincronizar n hilos en una barrera. 6-21 El valor del semáforo barrera será -(n-1) cuando todavía falte llegar el hilo n. Diseña de nuevo el código entre la sección previa y la posterior. Sin embargo. progreso y espera limitada). Para resolverlo. Esto se dará si el número de procesos o hilos en el protocolo de entrada es siempre igual o superior a 2. int n.. devuelve 0 (falso) si llave esta libre.

Filosofo. pthread_attr_init( &atrib ). tenedores[der]=0. return 0. //protocolo de salida tenedores[izq]=0. char **argv) { pthread_attr_t atrib. 6-22 //protocolo de entrada bool pasar. while(true) { //PONER AQUI EL protocolo de entrada ***** //comer(). do { while (test_and_set(&tenedores[der])==1) { } if (test_and_set(&tenedores[izq])==1) { tenedores[der]=0. w_lec--. pasar=true. void *Filosofo(void *arg) { int nfilo=(int)arg. &atrib. for (int i=0. int der=nfilo.. pthread_cond_wait(&cond.&mutex_monitor).i++) tenedores[i]=0. pthread_cond_wait(&cond.#define NUMERO_FILOSOFOS 5 int tenedores[NUMERO_FILOSOFOS]. El código que se proporciona a continuación es parte de una solución POSIX al problema de los lectores y escritores. pasar=false. pthread_join(&hilos[0]). while ( (nesc>0) || (w_esc>0) ) { w_lec++. w_esc--. while((nesc>0)||(nlec>0)) { w_esc++. ..&mutex_monitor).i<NUMERO_FILOSOFOS. //PONER AQUI EL protocolo de salida ***** } } int main(int argc. /****protocolo de entrada para Lectores ***/ void pre_leer() { pthread_mutex_lock(&mutex_monitor). } /***protocolo de entrada para Escritores ***/ void pre_escribir() { pthread_mutex_lock(&mutex_monitor). for(int i=0. } .i++) pthread_create(&hilos[i].i<NUMERO_FILOSOFOS. int izq=(nfilo+1) % NUMERO_FILOSOFOS. (void*)i).. pthread_t hilos[NUMERO_FILOSOFOS]. } while (!pasar). }else pasar=true. } Ej.

“nesc” valdrá 1 y en ese caso. El tipo de proceso con mayor prioridad en esta solución serán los escritores. múltiples lectores deben poder acceder al mismo tiempo. con sólo tres operaciones accesibles: suspender. pthread_mutex_unlock(&mutex_monitor). pues en la condición de pre_escribir() también se comprueba tal cosa. nesc--. Por otra parte. y entre lectores y escritores. “Liberar” reactiva a uno de los hilos si había menos de cinco hilos suspendidos. En caso de serlo. En esta solución. Por todo ello. Su comportamiento es el siguiente. si comprobamos las condiciones de pre_leer() y pre_escribir() veremos que ningún otro escritor. pthread_cond_broadcast(&cond). 6-23 Para que la solución pueda considerarse correcta debe darse exclusión mutua entre escritores. Implemente el código utilizando mútex y variables condición del estándar POSIX: . liberar e inicializar. no debe tener ningún efecto. si no hay hilos suspendidos.nlec++. nlec--. por una parte. pthread_mutex_unlock(&mutex_monitor). pthread_mutex_unlock(&mutex_monitor). si hay algún lector accediendo. Escriba el código necesario para implantar una nueva primitiva de sincronización a la que vamos a llamar “barrera”. } /*****Protocolo de salida para Lectores ***/ void post_leer() { pthread_mutex_lock(&mutex_monitor). Además. } } nesc++. Esto también se cumple. esta solución ES CORRECTA. if (nlec==0) pthread_cond_broadcast(&cond). /****Protocolo de salida para Escritores ***/ void post_escribir() { pthread_mutex_lock(&mutex_monitor). pthread_mutex_unlock(&mutex_monitor). para que entre un escritor no importa si hay lectores esperando o no. o a todos ellos si había cinco o más. si hay un escritor accediendo. ni lector podrán acceder. } ¿Es correcto? En caso de no serlo indicar por qué. Por su parte. “Suspender” suspende siempre al hilo que la invoque. por otra. pues ningún lector puede entrar mientras haya algún escritor esperando. pues para entrar un lector no se comprueba en ningún caso si hay o no otros lectores accediendo. “Inicializar” se encarga de inicializar esta herramienta. no podrá haber ningún escritor. ¿qué tipo de proceso posee mayor prioridad? Ej. fijando el número de hilos suspendidos a cero.

NULL ). pthread_mutex_unlock( b->mut ). } En el código para los lectores y escritores anterior. } void liberar( barrera *b ) { pthread_mutex_lock( b->mut ). pthread_mutex_unlock( b->mut ). pthread_cond_init( b->cond.”? Esa variable condición sirve para suspender a los hilos que. #include <pthread. deberá despertarlos y preparar el monitor para la siguiente iteración. Como la suspensión en la condición está asociada a un bucle while. por el tipo de acceso que se esté llevando a cabo en ese momento. b->hilos++. Cada hilo producirá una imagen.h> void hilo(void *p) { . void inicializar( barrera *b ) { pthread_mutex_init( b->mut. } barrera. 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. b->hilos--. pthread_cond_t cond. pthread_cond_wait( b->cond. Al realizar un aviso múltiple sobre tal condición. if (b->hilos<5) pthread_cond_signal( b->cond ). else pthread_cond_broadcast( b->cond ). ¿para qué sirve la variable condición “cond”? ¿Qué ocurre cuando se realiza una operación “pthread_cond_broadcast(&cond). para ello invertirá un tiempo de cómputo diferente en cada caso. y en la expresión lógica evaluada en dicho bucle se vuelve a comprobar si dicho tipo de hilo tiene permitido el acceso. } void suspender( barrera *b ) { pthread_mutex_lock( b->mut ). El objetivo del monitor que se pide implementar es sincronizar la sección de visualización de los diferentes hilos. deberá suspenderse. Para ello el monitor ofrece la función “Punto_de_sincronización()”.Ej. la implementación presentada es correcta. 6-25 • • typedef struct { pthread_mutex_t mut. Se pide implementar la clase “Monitor”. no tengan permitido el acceso cuando ellos lo intentaban.h> #include <stdlib. En el siguiente programa se pretende producir y visualizar imágenes usando hilos independientes. Si ya han llegado todos los demás hilos al punto de sincronización. b->mut ). se despertarán consecutivamente todos los hilos suspendidos en tal condición. b->hilos=0. 6-24 Ej. NULL ). int hilos.

#include <stdio. int cuantos.Punto_de_sincronizacion(). i<N. pthread_cond_wait(&cond. // fin de la sección A (producción de la imagen) mon.NULL).hilo.…. Monitor mon.h> #define N 5 class Monitor { // implementar // en la solución }.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. } else { pthread_cond_broadcast(&cond).……. } pthread_mutex_unlock(&mutex).NULL).NULL. Produce una imagen del tipo id Producir_Imagen(id). del monitor antes y después de utilizar la pantalla respectivamente.NULL). void Monitor() { pthread_mutex_init(&mutex. if (cuantos<N-1) { cuantos++. pthread_t hilos[N]. a) Indique de forma justificada si el siguiente monitor cumple con dicha especificación. cuantos=0. teniendo en cuenta que ambos procesos invocarían las funciones.Pn trabajos de programación) y los Ji (J1.. } }. i<N. pthread_cond_t cond. class Monitor {private: . for (int i=0. //fin del monitor Se desea controlar el uso de una pantalla TFT de 24” utilizada para visualizar los resultados de dos tipos de procesos. (void *) i). 6-26 class Monitor { pthread_mutex_t mutex. for (int i=0. // fin de la sección B (visualización de la imagen) } main() { int i. } Ej. } int id = (int) p. while(true) { // Sección A. i++) pthread_create(&hilos[i]. los Pi (P1. cuantos=0. //Sección B. i++) pthread_join(hilos[i]. Visualiza una imagen del tipo id Visualizar_Imagen(id). } void Punto_de_sincronizacion() { pthread_mutex_lock(&mutex). Pre_utilizacion() y Post_utilizacion().&mutex)... pthread_cond_init(&cond.

pthread_mutex_unlock(&mutex). public: void Monitor() { pthread_mutex_init(&mutex. pthread_cond_init(&cond. pthread_mutex_unlock(&mutex). visualizando=FALSE.NULL). hay que modificar el interfaz del monitor para que incluya las funciones: Pre_utilizacion_P().NULL). pthread_cond_signal(&cond).&mutex). El código quedaría de la siguiente forma: class Monitor {private: pthread_mutex_t mutex. public: void Monitor() { pthread_mutex_init(&mutex. pthread_mutex_unlock(&mutex). visualizando=0. 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.pthread_mutex_t mutex. añadiendo tanto código nuevo como funciones dentro del monitor. NOTA1: Se trata de una solución en la que puede haber inanición para los procesos Ji sino dejan de llegar procesos Pi. pthread_mutex_unlock(&mutex). Pre_utilizacion_J(). //fin del monitor Ej. if (visualizando==TRUE) pthread_cond_wait(&cond. Visualizando=FALSE. 6-27 El monitor no cumple con el requisito de exclusión mutua. int visualizando. cuando no puedan acceder al monitor. } void Pre_utilizacion() { pthread_mutex_lock(&mutex). Post_utilizacion_P() y Post_utilizacion_J() . } }. } void Post_utilizacion() { pthread_mutex_lock(&mutex). } void Post_utilizacion() { pthread_mutex_lock(&mutex). pthread_cond_t cond. NOTA2: Al tener diferente prioridad los procesos. visualizando++.NULL). int visualizando. } }. } void Pre_utilizacion() { pthread_mutex_lock(&mutex). visualizando--. se necesita una variable condición para suspender a los procesos. visualizando=TRUE. //fin del monitor b) Modifique el código del monitor propuesto.

Diseñe el mecanismo de sincronización necesario utilizando un monitor con primitivas POSIX.espera_P--. } void Pre_P() { pthread_mutex_lock(&mutex). pthread_cond_broadcast(&cond) pthread_mutex_unlock(&mutex).&mutex). visualizando.class Monitor {private: pthread_mutex_t mutex. indicamos únicamente el número de items a insertar/extraer. while (visualizando==TRUE) {espera_P++.} //extrae n items (se retrasa si no hay elementos suficientes) Para simplificar. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER. visualizando==FALSE. 6-28 . espera_P=0. } void Post_J() { pthread_mutex_lock(&mutex).. public: void Monitor() { pthread_mutex_init(&mutex. int pthread_cond_broadcast(pthread_cond_t *cond).&mutex).pthread_cond_wait(&cond. pthread_cond_broadcast(&cond) pthread_mutex_unlock(&mutex). pthread_mutex_unlock(&mutex). } }. } void Post_P() { pthread_mutex_lock(&mutex). pthread_mutex_unlock(&mutex). 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). pthread_cond_t cond. int pthread_cond_signal(pthread_cond_t *cond). visualizando==FALSE. while ((visualizando==TRUE) || (espera_P>0) ) pthread_cond_wait(&cond. int espera_P. pthread_cond_t cond = PTHREAD_COND_INITIALIZER. Ej..} // deposita n items (se retrasa si no hay huecos suficientes) void extrae (int n) {. //fin del monitor Suponemos un tampón de talla MAX compartido por productores y consumidores. } void Pre_J() { pthread_mutex_lock(&mutex). int pthread_mutex_lock(pthread_mutex_t *mutex).NULL).} visualizando=TRUE. Nota: Como ayuda al uso de la nomenclatura se aportan los prototipos de algunas llamadas. visualizando=TRUE. 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) {. visualizando=FALSE. int pthread_mutex_unlock(pthread_mutex_t *mutex). pthread_cond_init(&cond.NULL). pthread_mutex_t *mutex). int pthread_cond_wait(pthread_cond_t *cond.

EsperarC(). void deposita (int n) { pthread_mutex_lock(&mutex).. . } void extrae (int n) { pthread_mutex_lock(&mutex).} e+=n. x. . pthread_mutex_unlock(&mutex). while (n > e) {pthread_cond_wait(&vacio.EsperarS(). &mutex). x. /**código que extrae y devuelve resultado (SE SUPONE HECHO)**/ pthread_cond_broadcast(&lleno). /*********************************** Cita C/S con 1 Cliente y 1 Servidor ***********************************/ #include <pthread.h> #include <stdlib. 6-29 /**** Declaración de variables globales*******/ int MAX /***número máximo de items que caben en el tampón ****/ int e=0.&mutex).. while (e+n > MAX) {pthread_cond_wait(&lleno.. printf("Cliente: invoca servicio. } } 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().\n").Ej.h> #include <stdio.ActivarC(). • ActivarC(): activa el proceso C (suspendido en EsperarS()) Se pide implementar el monitor “ClienteServidor” utilizando mutex y variables condición de POSIX.h> void *Cliente(void *arg){ while(true) { retraso(200+rand()%1). x. Servicio().. x.. // num. } . • EsperarC(): suspende el proceso S hasta que el proceso C invoque EsperarS(). printf("Cliente: fin invocacion.\n"). } } void *Servidor(void *arg) { while(true) { . } En un sistema existen dos procesos: C (cliente) y S (servidor). pthread_mutex_unlock(&vacio). con el siguiente patrón de comportamiento: void *Cliente(void *arg) { while(true) { .EsperarS().. vacio.. actual elementos pthread_mutex_t mutex.} e-=n. pthread_cond_t lleno.. /** código de inserta valores (SE SUPONE HECHO)***/ pthread_cond_broadcast(&lleno).

Servicio(). pthread_mutex_unlock(&mutex). pthread_cond_init(&cita. 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.NULL. } } int main(int argc.NULL). printf("Servidor: fin servicio.tv_sec = ms/1000.void retraso(int ms) { struct timespec ts.NULL). printf("Servidor: esperar cliente. pthread_mutex_t mutex. } void EsperarS() { pthread_mutex_lock(&mutex). } void ActivarC() { pthread_mutex_lock(&mutex). ser. void Servicio() { retraso(10+rand()%1). C_esp=0. S_esp=0. } void EsperarC() { pthread_mutex_lock(&mutex). x. public: ClienteServidor() { C_esp=0. if(ms>0) { ts. nanosleep(&ts. C_esp=1. pthread_create(&cli.NULL.tv_nsec = (ms%1000)*1000000. NULL).NULL).EsperarC().\n"). pthread_cond_t cita.ActivarC(). } Ej. S_esp. . pthread_join(ser. pthread_cond_signal(&cita). if (!C_esp){ S_esp=1. pthread_join(cli. pthread_mutex_init(&mutex. pthread_create(&ser. class ClienteServidor { private: int C_esp. } }. if (S_esp) pthread_cond_signal(&cita). } pthread_mutex_unlock(&mutex).&mutex). S_esp=0. } } void *Servidor(void *arg) { while(true) { retraso(100+rand()%1).NULL). NULL).Servidor. 6-30 Nota: La solución presentada solo precisa una variable condición.Cliente. ts. x. pthread_cond_wait(&cita.&mutex). printf("Servidor: inicia servicio.\n"). char **argv) { pthread_t cli.NULL). } } Class ClienteServidor { // implementar en la solución } ClienteServidor x. utilizada para suspender tanto al Cliente como al Servidor para esperarse mutuamente). pthread_cond_wait(&cita. pthread_mutex_unlock(&mutex).\n").

P3 pide 1 instancia de R2. que así también podrá terminar. inicialmente sin ninguna instancia asignada.\n”). 12: pthread_mutex_unlock(&mutex_del_contador). . mutex_de_contador Traza de los hilos Estado del mutex. P1 pide 1 instancia de R1. 5: while (cuantos_dentro>=4) 6: { 7: printf(“Puedo soñar.F2:14. F5 en la cola F1:20 asignado a F5. P2 y P3. 4: pthread_mutex_lock(&mutex_del_contador).. 17: pthread_mutex_lock(&mutex_del_contador).F3:14. 1: while(1) 2: { 3: // Entrada en el comedor. // son muy comilones 15: printf(“Como. con lo que se puede conceder lo que pedía P1 y éste también podrá terminar. los mutex y las variables condición están correctamente inicializados. P2 pide 1 instancia de R1. Asumiendo que ningún proceso llega a liberar ninguna instancia.\n”). 21: retraso(40000+rand()%1000). P2 pide 1 instancia de R2.\n”). cola vacía F1:19 asignado a F1. P2 pide 1 instancia de R1. F1. 8: pthread_cond_wait(&cond_x. 6-31 encuentran ejecutando la línea 14: cond_x Hilos en cola de la var. Gracias a esto quedan libres dos instancias de R1 y R2. hilos en cola F5:1 libre. cola vacía Ej. dibuje el grafo de asignación de recursos resultante e indique si en el sistema hay o no un interbloqueo. 18: cuantos_dentro--. 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. // son muy dormilones. 6-32 No hay interbloqueo. cola vacía F1:17 asignado a F1. pues P3 puede terminar y al hacerlo libera una instancia de cada recurso.&mutex_del_contador). 9: printf(“Soy yo. 14: retraso(5000+rand()%1000). En el sistema se da la siguiente secuencia de solicitudes de recurso: P3 pide 1 instancia de R1. Nota: Suponga que los hilos. 20: pthread_mutex_unlock(&mutex_del_contador). Ej. En él se ejecutan tres procesos: P1. cola vacía F5:8 libre. 10: } 11: cuantos_dentro++. 19: pthread_cond_signal(&cond_x).F4 se (F1:14.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. P1 pide 2 instancias de R2. Situación: Existen 5 hilos en marcha.F4:14) y F5 acaba de lanzarse. cola vacía F5:4 asignado a F5. 16: // Saliendo del comedor. 13: // Dentro del comedor comiendo. condición --F5 suspendido en la cola F5 suspendido en la cola --- Un sistema está formado por dos recursos: R1 (con tres instancias) y R2 (con dos instancias). Con la instancia de R1 se pueden atender todas las peticiones pendientes de P2.

2 P1. S3=3. /*Variables globales*/ S1=1. En caso afirmativo.2 bloqueo P1. Diga si es posible que se produzca un ínterbloqueo. represente el grafo de asignación de recursos que correspondería a dicha situación y en caso negativo demuéstrelo. S2=2.1 P3. Ej. P2 y P3 que ejecutan el código que se presenta en la siguiente tabla.Dado un sistema con tres recursos R1. S2=2.2 bloqueo P3. T 0 1 2 3 4 5 6 P1 P(S1) P(S2) P(S3) A1 V(S3) V(S2) V(S1) P2 P(S2) P(S3) P(S1) B1 V(S1) V(S3) V(S2) P3 P(S3) P(S3) P(S2) C1 V(S2) V(S3) V(S3) . haciendo uso de las condiciones de Coffman. Suponga que los valores iniciales de los semáforos son: S1=1.1 P2. 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. S3=3.1 P1. R3 y donde se ejecutan tres procesos que realizan las siguientes operaciones: P1 1: SOLICITA(R1) 2: LIBERA(R1) 3: SOLICITA(R3) 4: LIBERA(R3) P2 1: SOLICITA(R2) 2: SOLICITA(R3) 3: LIBERA(R3) 4: LIBERA(R2) P3 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).3 bloqueo Sean tres procesos P1. R2.

Indique si es posible que se produzca un interbloqueo razonando la respuesta en términos de las condiciones de Coffman.Ej. porque no se puede dar una espera circular. Después de la petición de P1. P2. #include <pthread.h> #include <stdio. No existe ninguna evolución de este estado que termine en un interbloqueo. existe al menos una secuencia segura de terminación: P3. 6-35 No existe riesgo de interbloqueo. P1. Una vez ha terminado P1. el proceso P3.h> #define NUMERO_FILOSOFOS 5 pthread_mutex_t tenedores[NUMERO_FILOSOFOS]. . Suponiendo que para que P1 finalice necesita apropiarse del una instancia de R2 ¿hay riesgo de interbloqueo de los procesos? P2 P3 R1 R2 R3 P1 Ej. P2 puede obtener todos los recursos necesarios y terminar. P1 P3 S3 S2 S1 P2 Sea el estado de asignación de recursos a procesos que se muestra en la figura. 6-34 Si. que tiene asignados todos los recursos necesarios puede terminar. Considere la siguiente solución para el problema de los 5 filósofos. Por tanto. void *Filosofo(void *arg) { int nfilo=(int)arg. con lo cual P1 puede obtener todos los recursos necesarios y terminar también.h> #include <stdlib.

pthread_mutex_lock(&tenedores[t2]). } pthread_mutex_lock(&tenedores[t1]). usleep(300000). t2=nfilo. t2=(nfilo+1)%NUMERO_FILOSOFOS. t2. Peticion(R((i+1) mod 5))).. Peticion(Ri). // un tiempo comiendo. Peticion(Ri). Inicialmente la cantidad de recursos de cada tipo es la indicada en la siguiente tabla: Ej. P2. char **argv) { pthread_t th_filo[NUMERO_FILOSOFOS]. Esto evitará el interbloqueo. 6-36 Recurso Cantidad R0 R1 R2 R3 R4 RC 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 while TRUE do Peticion(RC). Con el código presentado. Libera(R((i+1) mod 5)).. 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. Libera(RC). el que no tenía el tenedor que él intentó coger en primer lugar).i<NUMERO_FILOSOFOS. cuando intentaran coger el segundo tenedor. int main(int argc. SeccionRestante(). excepto el filósofo 4. end while.while(true) { int t1. pues si todos los filósofos intentaran coger a la vez su primer tenedor. Peticion(R((i+1) mod 5))). pthread_mutex_unlock(&tenedores[t2]). R4 y RC. if (nfilo>((nfilo+1)%NUMERO_FILOSOFOS) ){ t1=(nfilo+1)%NUMERO_FILOSOFOS...(void*)i). el filósofo 4 será el único que haga cierta la condición (nfilo>((nfilo+1)%NUMERO_FILOSOFOS)). Por ello. Libera(RC).i<NUMERO_FILOSOFOS. Por ello. } } // un tiempo pensando. es decir. } pthread_mutex_unlock(&tenedores[t1]). uno de ellos lo podría hacer (el “otro” vecino del que antes se quedó sin nada. SeccionRestante(). Libera(Ri). end while. 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).i<NUMERO_FILOSOFOS.NULL). no podrá haber espera circular. //Se arrancan los filosofos for(int i=0. P1.Filosofo. Perfil de los procesos impares while TRUE do Peticion(RC). usleep(400000). Libera(Ri). todos lo conseguirían excepto uno (el 4 o el 0). P3 y P4 que utilizan los recursos R0.i++) pthread_join(&th_filo[i]). rompiendo así el ciclo dirigido. for(int i=0. // Se inicializan los tenedores for(int i=0. } else { t1=nfilo. R2. todos los filósofos cogerán primero el tenedor de su izquierda antes que el de su derecha (o al revés. . no podrá cerrarse jamás un ciclo dirigido.i++) pthread_mutex_init(&tenedores[i]. Como resultado. Gracias a esto.i++) pthread_create(&th_filo[i]. UsoDeLosRecursos(). Libera(R((i+1) mod 5)). UsoDeLosRecursos(). eso depende de cómo se distribuyan los filósofos y los tenedores en la mesa). R3. todos los filósofos están pidiendo los tenedores en orden creciente. En el ejemplo presentado. R1. En un sistema se encuentran en ejecución cinco procesos: P0. NULL.

Como en el protocolo de entrada se ejecuta la operación P(S). Considere que inicialmente S=1 y llave=false. while(1) { item = producir(). Por tanto otro proceso podría ejecutar el protocolo de entrada y pasar a la sección crítica. salida = (salida + 1) % N. llave = false. buffer[entrada] = item. S=S-1. contador = contador + 1. contador = contador . En caso afirmativo describa un escenario. entra. V(mutex). V(mutex). while(1) { P(mutex). while (S<0) {}. ya que el primero que consiga la llave. 3) Esta solución no garantiza la espera limitada. Ej. while (contador == 0) /*bucle vacio*/ . previamente concedido al proceso 0). Si no es correcta. indique cuál es el problema y modifique el código para que funcione. Ej. si hay un proceso ejecutando sección crítica ningún otro puede ejecutar la suya. entrada = (entrada + 1) % N. analizando con detalle cada una de las condiciones estudiadas. Compruebe si la siguiente solución al productor/consumidor es correcta o no. llave =false. consumir(item). 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. 6-37 No se producirá. item = buffer[salida].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. } V(S) { while (test_and_set( &llave )) { }. } . 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. void *func_prod(void *p) { int item. Suponiendo que n=5 ¿Es posible que en el sistema se produzca un interbloqueo? Razone la respuesta. } } } void *func_cons(void *p) { int item. P(mutex). } Exponga de manera justificada.1. S=S+1. si dicha implementación es válida para resolver el problema de la sección crítica. Analice la siguiente implementación para las operaciones P y V sobre un semáforo S: P(S) { while (test_and_set( &llave )) { }. pues puede haber obtenido el recurso 4 y después quedarse esperando el recurso 0. 2) El problema está. while (contador == N) /*bucle vacio*/ . 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. 1) Exclusión mutua. esto dejaría S= 0 y llave= false.

inicializado a N. 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”. P2 y P3 que realizan la siguiente traza de peticiones: 1. pthread_mutex_t m. P2: solicita R2 4. P1: solicita R1 2. Esta solución no funciona. 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 SOLUCION class Evento { private: pthread_cond_t c.Ej. m=PTHREAD_MUTEX_INITIALIZER. 3 y 2 instancias respectivamente. P1: solicita R2 8. inicializado a cero. R2 y R3 de los cuales se dispone de 2. pthread_mutex_unlock(&m).&m). P1: solicita R3 9. P1: solicita R1 3. public: Evento() { c=PTHREAD_COND_INITIALIZER. 6-40 No es correcta la solución. 6-39 Ej. P3: solicita R2 6. pthread_cond_broadcast(&c). } void sleep(int x) { pthread_mutex_lock(&m). P2: solicita R1 5. P3: solicita R2 7. pthread_mutex_unlock(&m). suspende a los consumidores cuando esté vacío “vacio”. suspende a los productores cuando el buffer está lleno Se define un evento como un tipo de datos. } } Sea un sistema con 3 recursos R1. utilizado para la sincronización de procesos. pthread_cond_wait(&c. en dicho sistema se ejecutan tres procesos P1. Partiendo de una situación en la que todos los recursos se encuentran disponibles. } void wakeup() { pthread_mutex_lock(&m). P1: solicita R3 .

El padre y el “hijo2” no cierran descriptores y siguen en ejecución. 3: sem_wait(&barrera). 0. while (1) { // Para cada hilo // Sección fuera de la barrera 1: c++. // Comtador de hilos sem_init(&mutex. El padre y el “hijo1” no cierran descriptores y no terminan. y hay un proceso que tiene todos los recursos (P3) necesarios para continuar y por tanto no retiene y espera. Ej. . int c= 0. es decir. 6-41 P1 P2 P3 R1 R2 R3 No hay interbloqueos porque no hay espera circular. // 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. 0. 4: sem_post(&barrera). . 6-42 hijo1 hijo2 H T Z E H B Z B Se pretende codificar una barrera que no sea sobrepasada hasta que no hayan llegado N hilos. barrera. 6: if (c == 0) sem_wait(&barrera). // Sección dentro de la barrera 5: c--. para ello represente el grafo de asignación de recursos que correspondería a dicha situación y en caso negativo demuéstrelo. Siguiendo la estructura del microshell desarrollado en prácticas. Terminado. luego P1 y después P2. Zombie o Huérfano) en que se encuentran dichos procesos hijos si: Situación El proceso “ush” ya ha terminado su ejecución.Indique de forma razonada si es posible o no que se produzca un interbloqueo. haciendo uso de las condiciones de Coffman.. sem_init(&barrera. 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. El padre no ha ejecutado ningún “wait” cuando los hijos ya han terminado su ejecución. mientras que los hijos aún continúan. 2: if (c == N) sem_post(&barrera). Secuencia posible: finaliza P3. sem_t mutex. 1). Ej. 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. La barrera está implementada usando semáforos POSIX. Bloqueado. 0).

Cada proceso realiza el siguiente programa: solicita 2 unidades de cinta. (b): La barrera es totalmente correcta a no ser que se use en el interior de un bucle infinito como es el caso. 6-43 Ej. m=PTHREAD_MUTEX_INITIALIZER. Observe que dicha barrera se puede utilizar repetidamente por un mismo hilo.&m). Se trata de impedir la espera circular. 6-45 (a): Deberían estar protegidas por el mutex los grupos de líneas 1-2 y 5-6. pthread_cond_signal(&b).&m). 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. pthread_mutex_unlock(&m). pthread_mutex_unlock(&m). } } . con p procesos compitiendo por ellas. int c0) { //maximo y valor inicial c=c0.n. Un ordenador tiene n unidades de cinta idénticas (n instancias de un mismo recurso). comente la corrección funcional del mismo. para ello lo único que hay que garantizar. Ej. pthread_cond_t a. } void P() { pthread_mutex_lock(&m). public: SemafLimitado(int n0. la solución propuesta es totalmente incorrecta. escribe datos en ellas y las libera. es que el número de procesos sea igual o inferior al número de cintas menos uno. En este supuesto. Se pide escribir un monitor que implemente las operaciones P y V sobre un semáforo limitado completando el siguiente código: SOLUCION class SemafLimitado { private: int c. una operación V supone esperar). } void V() { pthread_mutex_lock(&m). pthread_mutex_t m.b. pthread_cond_signal(&a). 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. Esto garantiza que siempre hay un proceso que pueda tener las dos cintas que necesita y no se produzca interbloqueos. 6-44 Ej. ¿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. En ningún caso hay que proteger las líneas 3 ni 4. c--. En estas líneas se puede producir una condición de carrera. n=n0. while (c>=n) pthread_cond_wait(&b. Con este código un hilo “rápido” podría finalizar su sección dentro de la barrera. a = b = PTHREAD_COND_INITIALIZER. ya que su uso se encuentra en el interior de un bucle infinito. while (c<=0) pthread_cond_wait(&a. c++.b) Suponiendo que el código está correctamente protegido frente a condiciones de carrera.

*/ /*-----------------------------------------------------*/ /* Código de los coches. */ Bloquear paso a los barcos. */ int coches = 0. */ sem_t mutex1.h> #define NUM_COCHES 5 #define NUM_BARCOS 5 void bajar_puente(void) {} void pasar(void) {} void levantar_puente(void) {} /*-----------------------------------------------------*/ /* Variables compartidas. /* /* /* /* /* /* /* */ Ej. Escribir los algoritmos para barcos y coches utilizando: a) semáforos b) monitores. /* Exclusión mutua entre barcos.*/ sem_t libre. */ Incrementar el nº de coches. */ .7.*/ sem_t mutex2. sobre coches. if (coches==1) sem_wait(&libre). coches++. sem_post(&turno). */ int barcos = 0. */ /*-----------------------------------------------------*/ sem_t turno. /* Exclusión mutua entre coches. /* Verificar si podemos pasar.C. sem_post(&mutex1). Problemas de sincronización 7. */ Fin de la S.2 Ejercicios Tenemos un puente levadizo sobre un río con las siguientes condiciones de utilización: • Los barcos tienen siempre prioridad de paso. si */ ya hubiese barcos.1 Dificultad de los ejercicios Nivel de dificultad Principiante Medio Avanzado Ejercicios 7. /* Necesario para dar prioridad */ /* a los barcos. sem_wait(&mutex1). o*/ a los siguientes coches. pero para levantar el puente han de esperar a que no haya ningún coche sobre él. /* Número de coches intentando */ /* pasar. a) Solución con semáforos: #include <pthread. */ /*-----------------------------------------------------*/ void *coche(void *arg) { sem_wait(&turno). • Los coches pueden utilizar el puente si no hay ningún barco pasando (en cuyo caso el puente estará levantado) o esperando. /* Número de barcos intentando */ /* pasar. /* Indica si el puente está */ /* libre.h> #include <semaphore. 7-1 Exclusión mutua en el acceso */ a la variable coches.

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

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

coches--. pthread_cond_init(&ok_coche. } barcos++. barcos_bloqueados--. levantar_puente(). En cualquier momento.. pues*/ /* así garantizamos que se re. */ pthread_cond_broadcast(&ok_coche). if (barcos==0) pthread_cond_broadcast(&ok_coche). } void salir_barco(void) { pthread_mutex_lock(&mutex_monitor).h> #include <semaphore. } void salir_coche(void) { pthread_mutex_lock(&mutex_monitor). 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). y de sur a norte. pthread_mutex_unlock(&mutex_monitor). NULL).coches++. &mutex_monitor). 7-2 #include <pthread.libre. .. pthread_mutex_unlock(&mutex_monitor). /* Es preferible utilizar broad-*/ /* cast en lugar de signal. Ej. habría que inicia-*/ /* lizar las herramientas de */ /* sincronización. while (coches > 0) { barcos_bloqueados++. /* Necesarios para fijar el sen-*/ /* tido de paso. */ pthread_mutex_init(&mutex_monitor. pthread_mutex_unlock(&mutex_monitor). pthread_cond_init(&ok_barco. en-*/ /* tre otras. bajar_puente(). NULL).h> /*-----------------------------------------------------*/ /* Variables compartidas. pthread_cond_wait(&ok_barco. if (coches==0) pthread_cond_broadcast(&ok_barco). } Disponemos de dos caminos que separan dos sentidos de circulación: Norte y Sur. } void entrar_barco(void) { pthread_mutex_lock(&mutex_monitor). */ . Con esta*/ /* instrucción reactivamos al */ /* resto de coches. Implementar una solución mediante semáforos.*/ /* evalúe la condición. Ambos caminos convergen en uno solo cuando se trata de cruzar un río. } int main(void) { /* En el programa principal. NULL). barcos--. */ /*-----------------------------------------------------*/ sem_t turno. Existen coches que quieren pasar de norte a sur. pthread_mutex_unlock(&mutex_monitor). pthread_cond_broadcast(&ok_barco).

/* deja pasar a los del norte. */ */ */ */ */ */ */ /*-----------------------------------------------------*/ /* Funciones utilizadas en los hilos.C. */ norte++. para acceder a “norte”. /* no deja pasar a los del sur. permite que */ /* los del norte pasen. if (norte==1) /* El primer vehículo del norte */ sem_wait(&libre).C. 1). int norte=0. 0. */ sur++. /* Exclusión mutua a la hora de */ /* pedir el paso. */ sem_post(&turno) .C.C. 1). Mientras el barbero atiende a un cliente.C. 0. */ /* Si es el último. if (sur==1) /* El primer vehículo del sur no*/ sem_wait(&libre). */ /* sem_init(&libre. /* S. de “sur”. */ /* Fin de la S. */ /* Si es el último. pueden sentarse (si quedan sillas libres en la antesala) o irse a pasear (si no quedan sillas disponibles). */ sem_post(&mutex1). if (norte==0) sem_post(&libre). Nº de vehículos en dirección sur-->norte. int sur=0. sem_wait(&mutex1).C. } /*-----------------------------------------------------*/ /* En el programa principal (no mostrado) tendríamos */ /* estas instrucciones de inicialización: */ /* sem_init(&turno. /* S. /* /* /* /* /* /* /* Exclusión mutua en el acceso a la variable “norte”. */ sem_wait(&mutex2). */ */ /* Proceder a cruzar. este último espera detenido hasta que finalice el corte de pelo. sem_post(&mutex2). de “norte”. */ sem_post(&mutex2). sem_wait(&mutex2). */ /* sem_init(&mutex1. sem_post(&mutex1). Si llegan más clientes mientras el barbero atiende a alguien. para acceder a “sur”. /* Exclusión mutua a la hora de */ /* pedir el paso. norte--. de “sur”. /* Otro hilo podrá pedir paso. } /*-----------------------------------------------------*/ /* Hilo que representa a un vehículo en sentido S-->N */ /*-----------------------------------------------------*/ void *sur_norte(void *arg) { sem_wait(&turno). Si no hay clientes que atender. */ /* S. /* Otro hilo podrá pedir paso. sur--. */ /* Fin de la S. 1). un barbero y una cantidad N de sillas en la sala de espera para los clientes. el barbero se echa una siesta. if (sur==0) sem_post(&libre). Ídem para la variable “sur”. /* Fin de la S.C. Cuando en este caso llega un cliente. 1). */ sem_wait(&mutex1). . */ pasar_puente(). */ /*-----------------------------------------------------*/ void pasar_puente(void) {} /*-----------------------------------------------------*/ /* Hilo que representa a un vehículo en sentido N-->S */ /*-----------------------------------------------------*/ void *norte_sur(void *arg) { sem_wait(&turno). /* S.sem_t mutex1. */ /*-----------------------------------------------------*/ /* Proceder a cruzar. */ Una barbería dispone de un sillón para el corte de pelo. /* Fin de la S. */ pasar_puente(). 0.C. */ sem_post(&turno). 0. de “norte”. */ /* sem_init(&mutex2. para acceder a “sur”. tiene que despertar al barbero. para acceder a “norte”. sem_t mutex2. permite que */ /* los del sur pasen. Nº de vehículos en dirección norte-->sur.

*/ fin_corte().*/ . i<NUM_CLIENTES. /* Crear clientes y barbero. cliente. */ cortar_pelo(). */ /* Sillas de espera para los */ /* clientes (basta con una con. */ /*-----------------------------------------------------*/ void *barbero(void *arg) { while (1) { esperar_cliente(). NULL. i<NUM_CLIENTES. */ for (i=0. } /*-----------------------------------------------------*/ /* Código del hilo que representa al barbero. int n_clientes = 0. i++) pthread_join(id_clientes[i]. */ } } */ int main(void) { int i. for (i=0. barbero. /* No pertenece al monitor. */ /*-----------------------------------------------------*/ void *cliente(void *arg) { respuesta res. } /* Terminar. /*-----------------------------------------------------*/ /* Código del hilo que representa a un cliente. } while (res!=CORTADO). NULL). pthread_t id_clientes[NUM_CLIENTES]. NULL). CORTADO} respuesta. do { /* Operación del monitor.El problema consiste en diseñar un programa adecuado para que se logre la sincronización adecuada. NULL. /* Número de clientes en la bar-*/ /* bería. */ pthread_mutext_t m_monitor = PTHREAD_MUTEX_INITIALIZER. /* Operación del monitor. pthread_t id_barbero. */ Ej. Debe resolverse implementando en POSIX el equivalente a un monitor. */ /*-----------------------------------------------------*/ /* Para exclusión mutua en las */ /* operaciones del monitor. */ pthread_create(&id_barbero. /* Esperar a los clientes. 7-3 El código que aparece a continuación formaría parte del programa mostrado en el enunciado. sólo*/ /* simula el corte. /*-----------------------------------------------------*/ /* Variables globales. /* Operación del monitor. if (res==LLENO) dar_una_vuelta().h> typedef enum {LLENO. respetando la interfaz que se presenta en el ejemplo de uso siguiente: #include <pthread. entrar_barberia(&res). return 0. i++) pthread_create(&id_clientes[i]. NULL).

*/ pthread_cond_wait( &sillon. if (n_clientes > N) *resp = LLENO.*/ pthread_cond_t sillon = PTHREAD_COND_INITIALIZER. llamar al primero. . &m_monitor ). */ pthread_cond_t dormir = PTHREAD_COND_INITIALIZER. /* Sillón para el corte de pelo. /* Si no somos el primero. } pthread_mutex_unlock( &m_monitor ). if (n_clientes==1) /* Despertar al barbero. } void esperar_cliente(void) { pthread_mutex_lock( &m_monitor ).*/ /* tentar. } void fin_corte(void) { pthread_mutex_lock( &m_monitor ). else /* Sino. /* No cabe. pthread_mutex_unlock( &m_monitor). 2 y 3. SC2 y SC3 tal como se muestra en el código de las dos tablas. respectivamente. */ pthread_cond_wait( &dormir. /* Litera del barbero. &m_monitor ). */ else pthread_cond_wait( &silla. &m_monitor ). } */ Sea un proceso que crea tres tipos de hilos que ejecutan las funciones Proceso1. n_clientes--. if (n_clientes == 0) /* Echar una siesta si no hay */ /* clientes. pthread_mutex_unlock( &m_monitor )./* dición). pthread_cond_signal( &dormir ). habrá*/ /* que esperar. /*-----------------------------------------------------*/ /* Operaciones del monitor. */ pthread_cond_signal( &sillon ). */ else { n_clientes++. /* Decirle al cliente que baje */ /* del sillón y salga de la bar-*/ /* bería (cuando quiera). */ /*-----------------------------------------------------*/ void entrar_barberia(respuesta *resp) { pthread_mutex_lock( &m_monitor ). /* Esperar a que acabe el corte. que lo vuelva a in. Cada uno de ellos ejecuta una sección crítica SC1. *resp = CORTADO. 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). */ pthread_cond_signal( &silla ). */ pthread_cond_t silla = PTHREAD_COND_INITIALIZER.

} void EntraB() { pthread_mutex_lock(&mutex). } } pthread_mutex_t mutex. pthread_cond_signal(&mutexA).&mutex).Proceso1.NULL). pthread_mutex_unlock(&mutex). } for(i=1. } } a) Indique qué tipo de sincronización proporciona el monitor anterior. nB=nB+1. EntraB(). &mutex). } int main() { int i. EntraA(). . &mutex). mutexC. pthread_join(&P2. i++) {pthread_create(&P1. nC=nC-1. SaleB(). mutexB.NULL). } void EntraC() { pthread_mutex_lock(&mutex).h> #include <stdlib. pthread_t P1.i<NUM_HILOS.NULL). pthread_cond_signal(&mutexB). pthread_join(&P3. pthread_cond_t mutexA. EntraB().wait.Proceso3. pthread_cond_signal(&mutexC).NULL).NULL).NULL). pthread_cond_signal(&mutexA). void *Proceso2() { while(1) { SeccionRestante2.nB:=0. while (nB>0) pthread_cond_wait(&mutexA. nC=nC+1.i<NUM_HILOS.NULL). SaleA(). pthread_create(&P3. while ((nC>0) || (nB>0)) pthread_cond_wait(&mutexC.NULL. pthread_mutex_unlock(&mutex).h> #include <stdio. conteste en la siguiente tabla en la cual debe colocar una X en el elemento (i. int nA. pthread_mutex_unlock(&mutex). SaleC().NULL).P3. nB. Para ello. pthread_cond_signal(&mutexC). i++) {pthread_join(&P1. pthread_cond_signal(&mutexB). SaleC(). } void SaleB() { pthread_mutex_lock(&mutex).NULL.P2. } } void *Proceso3() { while (1) {SeccionRestante3. nB=nB-1. for(i=1. pthread_cond_init(&mutexC. EntraC(). pthread_cond_signal(&mutexC). SC2. pthread_cond_signal(&mutexB). pthread_cond_init(&mutexB. pthread_cond_init(&mutexA. nA=nA+1.h> #define NUM_HILOS 20 void void void void void void EntraA().#include <semaphore. nC. while((nA>0)||(nC>0)) pthread_cond_wait(&mutexB.NULL). SaleA(). } void SaleA() { pthread_mutex_lock(&mutex).NULL. pthread_cond_signal(&mutexA). SC3. pthread_mutex_unlock(&mutex). } void SaleC() { pthread_mutex_lock(&mutex). mutexB. pthread_mutex_init(&mutez.Proceso2. pthread_create(&P2.NULL).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. nA=nA-1. } } void EntraA() { pthread_mutex_lock(&mutex). void *Proceso1() { while(1) { SeccionRestante1.h> #include <pthread.nC:=0. pthread_mutex_unlock(&mutex). SC1. SaleB(). pthread_mutex_unlock(&mutex). EntraC(). nA:=0.

EntraB. nC.h> #define NUM_HILOS 20 void EntraA(). EntraA(). } /* fin entrar_coche*/ void salir_coche(void) { void entrar_barco(void) { sem_wait(&mutex2). int nA. if (c==1) sem_wait(&libre).h> #include <stdio. void *Proceso1() { while(1) { SeccionRestante1.h> #include <stdlib. pthread_mutex_t mutex.b) Modifique los procedimientos Proceso1. EntraC(). c = c +1. SaleB.h> #include <pthread. void EntraC(). void entrar_coche(void) { sem_wait(&turno). } } SC2 --X --- SC3 X ----- El código que se proporciona a continuación constituye una solución al problema de un puente levadizo. entrar_puente(). void SaleC(). void SaleA(). SC1. SaleA. sem_wait(&libre). sem_post(&turno). SaleC ) para conseguir el tipo de sincronización que se muestra en la siguiente tabla. mutexB. SC2. pthread_cond_t mutexA. mutexC. SC3. SaleC(). void SaleB(). b = b+1. nB. bajar_puente(). if (b==1) { sem_wait(&turno). EntraC. } /* fin entrar_barco*/ void salir_barco(void) . EntraB(). entrar_puente(). } sem_post(&mutex2). SaleB(). 7-4 a) SC1 SC1 X SC2 --SC3 X b) #include <semaphore. sem_post(&mutex1). sem_wait(&mutex1). } } void *Proceso3() { while (1) {SeccionRestante3. levantar_puente(). void EntraB(). SaleA(). SC1 SC2 SC3 SC1 X X --- SC2 X ----- SC3 ----X Ej. Proceso2 y Proceso3 (dejando intacta la implementación del las funciones EntraA. } } void *Proceso2() { while(1) { SeccionRestante2.

} 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. Inicialmente. sem_wait(&mutex2). 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). • B2 invoca entrar_barco. b = b-1. hay barcos esperando y un nuevo coche invoca entrar_coche?. hay coches en el puente. • C0 invoca entrar_coche. • B0 invoca entrar_barco. Para bajarlo no debe haber ningún barco cruzando. • C3 invoca entrar_coche. sem_post(&mutex1).salir_puente. } /*fin salir_coche*/ { salir_puente. c) ¿Qué ocurre si. • C1 invoca entrar_coche. Para levantarlo no debe haber ningún coche cruzando. . if (b==0){ sem_post(&turno). sem_wait(&mutex1). 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). Conteste a las siguientes preguntas suponiendo que los semáforos tienen asociada una cola FIFO. 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. sem_post(&libre). d) ¿Qué ocurre si hay barcos cruzando bajo el puente. c = c-1. todos los semáforos valen 1 y los contadores valen 0. • Los coches pueden cruzar el puente cuando esté bajado. • B1 invoca entrar_barco. • C2 invoca entrar_coche. if (c==0) sem_post(&libre).

B1. Es decir: variable contador mutex1 1 mutex2 -1 turno -1 libre -1 En el puente se encuentra el proceso C0 Procesos suspendidos B1 C1 B0 b) Al salir C0 da paso en primer lugar a B0 al realizar P(libre). 7-6 . . /**** variables globales ****/ pthread_mutex_t mutex_monitor = PTHREAD_MUTEX_INITIALIZER. e) Situación imposible. B1 pasa al liberar B0 mutex2. C3 llega y se suspende en turno. Se pueden añadir más variables si fueran necesarias. while(ocupado || w_aterriza>0) Ej. si un avión llega desde el aire y en ese momento hay otro avión utilizando la pista (despegando o aterrizando). que utilizan tanto los aviones que despegan como los que aterrizan. . C1 se bloquea en turno que ha cogido B0. entonces se ha de esperar. 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. B2 llega y pasa. que aún no ha sido liberado por los barcos. C2. B2 Procesos suspendidos C1. si llega un nuevo coche y tiene el turno.Cuando un avión termina de utilizar la pista. si un avión llega a la pista con intención de despegar y en ese momento hay otro avión despegando o aterrizando. B0 se detiene en libre pero coge el turno. Se pretende hacer un programa concurrente para simular un aeropuerto de pequeño tamaño en el que se dispone de una única pista. int ocupado=FALSE. pthread_cond_t cond_despegar = PTHREAD_COND_INITIALIZER. C2 llega y se suspende en turno. d) El nuevo barco cruza bajo el puente. B1 no puede entrar porque B0 no ha liberado mutex2. /*** métodos del monitor ****/ void inicia_despegue(){ pthread_mutex_lock(&mutex_monitor). contador Variable mutex1 1 mutex2 1 turno -3 libre 1 En el puente se encuentran B0.Sólo un avión puede estar utilizando la pista en cada momento.Ej.Igualmente. Al no liberar el semáforo mutex1 hasta después de salir del puente. 7-5 a) C0 pasa. C3 c) El coche se suspende en el semáforo turno. también habrá de esperar. pthread_cond_t cond_aterrizar = PTHREAD_COND_INITIALIZER. f) En entrar_coche se suprime la llamada sem_post(&mutex1) y en salir_coche se suprime la llamada sem_post(&mutex1). se suspenderá en mutex1. El comportamiento ha de ser el siguiente: . teniendo prioridad los aviones que desean aterrizar (la inanición de éstos podría llevar a que agotaran su combustible). int w_aterriza=0. si había algún otro avión esperando podrá pasar a utilizarla.

cinco. /***COMPLETAR****/ /***ENTRADA CHICOS****/ //*****CHICAS**************// void chicas() { sem_wait(&mutex_as). ocupado=FALSE. ocupado=FALSE. sem_wait(&libre). pthread_mutex_unlock(&mutex_monitor). while(ocupado) { w_aterriza++.mutex_as. • Puede haber más de un chico a la vez. if (w_aterriza>0) pthread_cond_broadcast(&cond_aterrizar).&mutex_monitor). Complete el código que se propone a continuación para que se cumpla dicho protocolo. pero con un máximo de cinco. ocupado=TRUE. .h> sem_t libre. utiliza_servicio(). Esto quiere decir que si un chico está esperando y llega una chica. /***COMPLETAR****/ /***ENTRADA CHICAS****/ num_chicas++. } ocupado = TRUE. else pthread_cond_broadcast(&cond_despegar).mutex_os. if (num_chicas==1)sem_wait(turno) sem_post(&mutex_as). 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. //*****CHICOS**************// void chicos() { sem_wait(&cinco). int num_chicos. utilice las variables que ya han sido declaradas e inicializadas en el mismo. } void inicia_aterrizaje(){ pthread_mutex_lock(&mutex_monitor). pthread_mutex_unlock(&mutex_monitor). Ej. } void acaba_aterrizaje(){ pthread_mutex_lock(&mutex_monitor). &mutex_monitor). w_aterriza--. pthread_mutex_unlock(&mutex_monitor). sem_wait(&mutex_os). else pthread_cond_broadcast(&cond_despegar). • Las chicas tienen preferencia sobre los chicos. turno. sem_post(&turno). 7-7 #include <semaphore. if (w_aterriza>0) pthread_cond_broadcast(&cond_aterrizar). 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. } void acaba_despegue(){ pthread_mutex_lock(&mutex_monitor). sem_wait(&turno). ésta debe pasar antes.pthread_cond_wait(&cond_despegar. pthread_cond_wait(&cond_aterrizar. num_chicas.

if (num_chicos==1)sem_wait(libre) sem_post(mutex_os).0.. num_chicas=0. • Los niños. sólo la pueden utilizar la piscina P si hay al menos un entrenador.1). } sem_post(&mutex_as). if (num_chicos==0)sem_post(libre) sem_wait(&mutex_as)..sem_post(&libre). pueden utilizar la piscina P simultáneamente. /***COMPLETAR****/ /***SALIDA CHICAS****/ num_chicas--.N ok o N.. • Los hilos N nunca pueden utilizar la piscina P sin hilos E. } /***********************// int main() { int hilos_chicas.0. if (num_chicas==0)sem_post(turno).. num_chicos=0. … … } 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).. hilos E. • Los entrenadores. sem_post(&cinco). sem_post(&mutex_os).5).1). num_chicos++. sem_init(&mutex_os.1). sem_init(&cinco.. permanecen en ella (ejecutan código arbitrario). permanecen en ella (ejecutan código arbitrario). según la siguiente normativa: • Los hilos E invocan la operación 'entraE' para acceder a la piscina. y luego invocan 'saleE' para abandonarla • Los hilos niños N invocan la operación 'entraN' para acceder a la piscina.. sem_init(&libre. utiliza_servicio(). /***COMPLETAR****/ /***SALIDA CHICOS****/ num_chicos--. sem_wait(&mutex_os). hilo E.0.1). utilizándola.0.. hilos_chicos. y luego invocan 'saleN' para abandonarla. sem_init(&turno.. hilos N.. o E. .. sem_init(&mutex_as.E ok o E.0.N ilegal Se pide diseñar una solución utilizando monitores para garantizar las condiciones anteriores..E N.

NULL). } void saleE() { pthread_mutex_lock(&mutex). if (nN==0) pthread_cond_broadcast(&cond_E). pthread_mutex_unlock(&mutex). public: void Monitor() { pthread_mutex_init(&mutex. pthread_cond_t cond_E. pthread_cond_init(&cond_N. nE=0. 7-8 class Monitor {private: pthread_mutex_t mutex. int nE. while(nN>0 && nE==1) pthread_cond_wait(&cond_E. Por requerimientos técnicos. mientras que no hay límite en la conexión de consumidores.nN=0.&mutex). } void entraE() { pthread_mutex_lock(&mutex).NULL). --nN. } void saleN() { pthread_mutex_lock(&mutex).NULL). while(nE==0) pthread_cond_wait(&cond_N. pthread_mutex_unlock(&mutex). nN. sólo se aceptan nuevas conexiones de consumidores mientras el número de consumidores no supere el triple de los generadores conectados en ese . sólo pueden conectarse un máximo de Ng generadores. --nE. //fin del monitor Sea una central de distribución eléctrica a la que se pueden conectar generadores y consumidores. pthread_mutex_unlock(&mutex). pthread_mutex_unlock(&mutex). cond_N. pthread_cond_broadcast(&cond_E).&mutex). aunque para evitar sobrecargas. ++nN. pthread_cond_init(&cond_E. pthread_cond_broadcast(&cond_N).Ej. } void entraN() { pthread_mutex_lock(&mutex). ++nE. } }.

n_cons++. int pthread_mutex_unlock(pthread_mutex_t *mutex). while(n_gene>=Ng) pthread_cond_wait(&cond_gene. pthread_mutex_unlock(&mutex).&mutex). pthread_cond_signal(&cond_gene). Ej. int pthread_cond_wait(pthread_cond_t *cond. pthread_cond_signal(&cond_cons). } void desconexión_consumidor() { pthread_mutex_lock(&mutex). n_cons--. int n_gene=0. } void desconexion_ generador() { pthread_mutex_lock(&mutex). Se pide programar un monitor que sincronice las peticiones de conexión de generadores y consumidores. pthread_mutex_unlock(&mutex). Nota: Como ayuda al uso de la nomenclatura se aportan los prototipos de algunas llamadas. } void conexión_consumidor() { pthread_mutex_lock(&mutex). while(n_cons>=3*n_gene) pthread_cond_wait(&cond_cons. int pthread_mutex_lock(pthread_mutex_t *mutex). pthread_mutex_unlock(&mutex).&mutex). int pthread_cond_broadcast(pthread_cond_t *cond). int pthread_cond_signal(pthread_cond_t *cond). n_gene++. pthread_cond_t cond = PTHREAD_COND_INITIALIZER.momento. n_gene--. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER. Int n_cons=0. pthread_cond_broadcast(&cond_cons). pthread_mutex_unlock(&mutex). pthread_cond_t cond_cons = PTHREAD_COND_INITIALIZER. 7-9 /**** variables globales ****/ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER. } . pthread_cond_t cond_gene = PTHREAD_COND_INITIALIZER. pthread_mutex_t *mutex). /*** funciones a implementar ****/ void conexión_generador() { pthread_mutex_lock(&mutex).

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()”.
#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;

Ej. 7-10

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 Principiante Medio Avanzado Ejercicios

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

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]); } }

Lo anterior es una utilización inadecuada de “trylock”. no son posibles los interbloqueos porque se incumple la condición de retener y esperar (asignación gradual de recursos). . typedef struct { int infd. Lo mejor es un ejemplo: $ ls | grep txt | sort > hola tubo1 tubo2 grep txt ls sort hola cmdfd[0] cmdfd[1] cmdfd[2] /dev/tty tubo1 tubo1 tubo2 tubo2 hola /dev/null si background El siguiente fragmento de código corresponde a proyecto “microshell” desarrollado en prácticas. “Completar 2” y “Completar 3”. 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. }CMDFD[PIPELINE] Ej. 8-2 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.Sí es posible que se produzca un bloqueo. Explica para qué sirve la estructura CMDFD utilizada en la práctica de microshell. que evidentemente va a suspender el proceso. Para resolverlo bastaría con eliminar el segundo pthread_mutex_lock(). Se dan por tanto todas las condiciones de Coffman y existe riego de interbloqueo. Si “trylock” da como resultado que el recurso está ocupado (EBUSY) entonces se intenta coger mediante un “lock” bloqueante. typedef struct { int infd. 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”. int outfd. situado tras el “trylock”. int outfd.

i.i<nordenes-1. 8-4 Completar 1 fd_pipes[i]. for(i=0. fdin.}CMDFD[PIPELINE].infd=pfdes[0].infd !=0 ) . Completar 2 fd_pipes[0]. i++) { …… if ((pidhijo=fork())==0) { if (cmdfd[i]. char ***args .i++) { pipe(pfdes). char **ordenes . Completar 3 a) fd_pipes[0]. } /* pipeline */ Ej.} // Completar 2 } else { // Completar 3 } // sigue el código que no se incluye en el ejercicio. pfdes[2].O_RDONLY)) == -1) {perror("No puedo abrir el fichero de entrada").outfd=pfdes[1]. int *nargs . iniciar_pipes().infd=fdin.infd=0.exit(1). CMDFD { int int int int * pipeline(CMD * ordenes) nordenes. 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 . fdout. return(&fd_pipes). fd_pipes[i+1]. nordenes=ordenes->num_ordenes. i <nordenes. // Completar 1 } if(ordenes->fich_entrada[0]!='###BOT_TEXT###') { if ( (fdin=open(ordenes->fich_entrada. CMDFD fd_pipes. int bgnd) { for (i=0.

La combinación close. 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.{ close(0). Esta búsqueda del hueco se realiza por orden creciente de descriptores comenzando por 0. } If (cmdfd[i]. pthread_mutex_unlock(&mutex_del_contador). Por lo tanto el close se aplica sobre el descriptor de fichero destino de la duplicación. b. while (cuantos_dentro>=4) pthread_cond_wait(&puerta_del_comedor.la función cerrar_fd cierra todos los descriptores de fichero abiertos (tubos y ficheros) excepto los descriptores 0. cuantos_dentro++.infd). 1 y 2. while (true) { ptread_mutex_lock(&mutex_del_contador). pthread_mutex_lock(&mutex_del_contador).el padre ejecuta sólo el último cerrar_fd(). 8-5 .outfd). dup(cmdfd[i]. args[i]). } } Ej. cuantos_dentro--. &mutex_del_contador). } ….. c. pthread_mutex_unlock(&mutex_del_contador).. execvp(ordenes[i]. } cerrar_fd().. Conocido el problema de los cinco filósofos y la solución del comedor: void * Filosofo (void * arg) { int nfilo=(int)arg. pthread_cond_signal(&puerta_del_comedor). dup(cmdfd[i]. ……. 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. } 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? a. ….outfd !=1 ) { close(1).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.dup es equigalente a dup2. } cerrar_fd().

txt Descriptor 7 Ej. justificando su respuesta. Además el uso de variables condición obliga al uso de mutex.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. Por ejemplo: ls -l | grep hola | sort > fichero1.1.Para registrar el número de filósofos a los que se les ha permitido pasar a comer al interior del comedor. 3 “fichentrada. 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. Construya una línea de órdenes.. Ejemplo: 0. 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.Para que no se produzca condición de carrera en el acceso a “cuantos_dentro”. . b. calcule estos descriptores cuando el usuario introduzca las siguientes órdenes por el teclado. al ejecutarla con el microshell desarrollado en prácticas..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. tal que. 8-7 Cualquier linea con tres órdenes sin redirección de entrada y redirección de salida al fichero fichero1.Para retener a los filósofos a los que no se les permite comer. c. En particular. 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. genere una estructura cmdfd con el siguiente contenido: infd Cmdfd[0] Cmdfd[1] Cmdfd[2] 0 3 5 outfd 4 6 7 Considere que se dispone del siguiente fichero regular: Nombre fichero fichero1. Tenga en cuenta la estructura típica de microshell desarrollado en el laboratorio y calcule.txt es correcta. 8-6 a.2 /dev/tty .txt”..

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.6) tubo /dev/tty En el problema de los 5 filósofos (filósofos numerados como 0.4). 8-8 En todos los casos tiene abiertos: 0 /dev/tty . (4. 4 file1 d) 3 file1. 4 while(1){ 5 //PROTOCOLO ENTRADA en el comedor 6 pthread_mutex_lock(&m).. donde se establece un protocolo de entrada al comedor de manera que sólo entran 4 filósofos simultáneamente. para los filósofos 1 y 6 su tenedor derecho es el 1 y su izquierdo el 2. 22//**FIN PROTOCOLO DE ENTRADA*******// 23 …pienso en si existo… 24 } 25 } . en total 10 filósofos. mientras que los 5 filósofos nuevos. 20 pthread_cond_signal(&vc). Indica cómo afecta esa decisión al problema de los interbloqueos. La mesa que tienen para comer los 10 filósofos. (5. Los 5 primeros filósofos. y porqué. es la tradicional con 5 platos y 5 tenedores. 21 pthread_mutex_unlock(&m). 10 pthread_mutex_unlock(&m).4) tubo c) 3 /dev/null . 1 void filo(void * arg) 2 { 3 nfilo=(int) arg. 4 file2 . sólo necesitan un tenedor cada uno. y luego el de índice mayor.4. y por lo tanto es imposible que se produzca un interbloqueo.2 a) 3 file1. 7 while(dentro==4) 8 pthread_cond_wait(&vc. 8-9 La ordenación parcial de recursos hace que no se produzca espera circular. Al problema tradicional de los 5 filósofos. de manera que: para los filósofos 0 y 5 su tenedor derecho es el 0 y su izquierdo el 1.1 /dev/tty . que es una condición de Coffman.. Ej. siguen necesitando 2 tenedores para comer. 9 dentro++. 18 pthread_mutex_lock(&m) 19 dentro--.Ej. suponemos que cada filósofo pide primero el tenedor de menor índice.&m). en concreto el tenedor derecho. numerados del 0 al 4. etc…. numerados del 5 al 9. tenedores numerados como 0.5) tubo b) (3. se le han incorporado 5 nuevos filósofos. 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). y cuyo código se presenta a continuación.

while ((dentro==4)&&(nfilo<5)) pthread_cond_wait(&vc. Justifique su implementación. pthread_mutex_unlock(&m). 8-10 A) Protocolo de entrada Pthread_mutex_lock(&m). ¿Qué ocurriría si se omite la línea 18? Justifique su respuesta. pthread_mutex_unlock(&m). if (nfilo<5) dentro++.&m). while ((dentro==4)) pthread_cond_wait(&vc. Considere el microshell desarrollado en el laboratorio. if (nfilo<5) dentro++. 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. o bien… lock(&m). 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. pthread_cond_signal(&vc). Ej.Se pide: Modifique los protocolos de entrada y salida al comedor que aparecen en el pseudocódigo anterior. B) Protocolo de salida Pthread_mutex_lock(&m) if (nfilo<5) dentro--. pthread_mutex_unlock(&m). controlando la entrada al comedor para evitar interbloqueo y con una utilización lo más eficiente posible de los tenedores. .&m).

cont < REPETICIONES. La función retraso(1) realiza un retraso de 1 milisegundo. en particular. 8-11 } Si el padre no cierra los descriptores. la orden cat <file1 terminará cuando termine de leer el fichero file1 y dejará su contenido sobre el tubo cerrando. los del tubo.. int temp. 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. V = temp.. temp=temp+1. 19 if (bg==FALSE) while(wait()!=-1).ct<nordenes.. cuando termina el proceso. 11 Res=Exec(.ct). void *resta (void *argumento) { long int cont. pthread_exit(0).”No puedo ejecutar la orden: %d\n”. int temp. 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 proceso asociado con la orden sort no terminará puesto que nunca recibirá una marca EOF del tubo. todos sus descriptores.) 2{ 3 for (ct=0. . En este caso particular. for (cont = 0. V). El segundo. 15 } 16 } 17 } 18 cerrar_fd(). y suma una unidad a la variable global V. while (test_and_set(&llave)==1) {}. cont = cont + 1) { temp=V.). 14 exit(-1).ct++) 4 { 5 if (!fork()) // hijo 6 7 { 8 Redirigir_entrada(ct). printf("-------> Fin AGREGA (V = %ld)\n". que ejecuta la función resta(). void *agrega (void *argumento) { long int cont. decrementa en una unidad la misma variable V. retraso(1).. 21} Ej. 12 if (res<1) { 13 fprintf(2.1 int Ejecucion(…. 20 return OK. 10 Cerrar_fd(). Ambas funciones realizan el mismo numero de iteraciones. } llave=0. El siguiente fragmento de código corresponde a un programa con tres hilos: el primero ejecuta el código de la función agrega().. 9 Redirigir_salida(ct).

8-13 Solución: Como agrega tiene el test_and_set fuera del bucle for. En la implementación que se ha estudiado en las prácticas de la asignatura. c) Se realiza un pthread_mutex_unlock y se libera el recurso tenedor. Para cada uno de los supuestos siguientes. cont < REPETICIONES. suponiendo que REPETICIONES=5 y que cada iteración del bucle for tarda 1 milisegundo en ejecutarse. int n. cada tenedor es un recurso compartido que únicamente puede ser accedido por un filósofo cada vez. V = temp. while(!los_tengo) . cada tenedor se ha representado mediante un mutex. b) Se realiza un pthread_mutex_lock sobre un recurso libre. cont = cont + 1) { while (test_and_set(&llave)==1) {}. temp=temp-1. Se pide dibujar el cronograma de ejecución de los dos hilos. Dado el siguiente fragmento de código correspondiente al problema de los cinco filósofos: void *Filosofo(void *arg) { int nfilo=(int)arg. resta puede acceder y ejecuta las 10 iteraciones seguidas a) Se realiza un pthread_mutex_lock sobre un recurso que ya está bloqueado por lo que. 8-12 Ej. ejecuta las 10 iteraciones seguidas. Cuando agrega termina. despertando si hubiera algún suspendido esperándolo. el cual pasará ahora a estar bloqueado. } (V = %ld)\n". bool los_tengo. V). while(true) { los_tengo=false. 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.for (cont = 0. el invocante se suspende hasta que es despertado cuando el recurso sea liberado. indique qué llamada realiza el hilo filósofo y cómo afecta dicha llamada al estado de ejecución de los filósofos. } printf("-------> Fin RESTA pthread_exit(0). llave=0. temp=V. siendo ahora propiedad del hilo invocante. En el problema de los filósofos. Se ejecuta primero agrega debido al retraso inicial de resta.

de manera que si el mutex está abierto. } } d) Se pide completar el código en los puntos 1.. En el caso en el que el mutex esté cerrado cuando se invoca la llamada. retraso(300). pthread_mutex_unlock(&tenedores[(nfilo +1)%NUMERO_FILOSOFOS]). Si consigue coger el tenedor izquierdo debe actualizar la variable “_los_tengo”. 8-14 Ej. // un tiempo comiendo. soltamos el derecho y nos esperamos Punto 2 los_tengo = true. retraso(100). para salir del bucle y poder indicar que está comiendo Punto 3 pthread_mutex_unlock(&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. 2 y 3. EF_DURMIENDO). Una vez ya ha comido debe soltar ambos tenedores y esperar un tiempo durmiendo. e) Explique el funcionamiento de la llamada al sistema pthread_mutex_trylock La llamada pthread_mutex_trylock intenta cerrar el mutex que se le pasa como parámetro. retraso(300). Como el tenedor izquierdo está ocupado. // Punto 3 // y se va a dormir. 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. lo cierra.. 8-15 Punto 1 pthread_mutex_unlock(&tenedores[nfilo]). EF_COMIENDO). los_tengo = false. lo que hace es devolver el código de error EBUSY . devolviendo como valor de retorno un 0. Ej.{ //tomo el tendor derecho pthread_mutex_lock(&tenedores[nfilo]). // p_w->getView()->setEstadoFilosofo(nfilo.. ésta no suspende al hilo invocente. p_w->getView()->setEstadoFilosofo(nfilo.

NULL). 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. char ***args . &act.El siguiente código muestra la ejecución del microshell visto en prácticas.. cerrar_fd(). 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. NULL).sa_handler = SIG_DFL. Para solucionarlo hay que crear un hijo auxiliar cuando la orden sea en bgnd. char **ordenes .args[i][0] ).. i++){ pid=fork(). int *nargs . fprintf(stderr. //codigo del hijo struct sigaction act. ¿Es posible que se creen zombies? ¿En qué situaciones? Justifique la respuesta y.sa_handler = SIG_IGN. act. NULL). /* proceso background intentando escribir */ . en caso afirmativo. redirigir_sal(i). act. } Ej. &act. int bgnd) { int pid. /* senyal interrupcion del teclado CTRL-C */ sigaction(SIGQUIT.. "%s no encontrado\n". 8-17 Si se crean zombies ya que en este código el ush no espera a los hijos si la orden es en background. Escriba el código del intérprete y de los hijos // codigo del interprete struct sigaction act. &act. int ejecutar (int nordenes . NULL). 8-16 Ej. for (i=0. NULL). Recordando lo realizado en la práctica de microshell sobre el tratamiento de señales. i. &act. NULL). indique cómo habría que modificar el código para que esto no ocurriera.args[i]). exit(1). que no espere a los hijos y haga un exit . sigaction(SIGINT.i<nordenes. &act. } } cerrar_fd(). sigaction(SIGQUIT. /* senyal terminacion del teclado */ sigaction(SIGTTOU. .. /* proceso background intentando leer */ sigaction(SIGTTIN. execvp(ordenes[i]. SIGQUIT. if (!bgnd) while (wait(&estado) != pid). &act. if (pid == 0){ redirigir_ent(i). sigaction(SIGINT.