Sie sind auf Seite 1von 106

Sist3m@s Op3r@#iv0s

Sistemas Operativos 2
Estudios de Informtica ETSIAp / FI

Recopilacin de ejercicios y problemas de la asignatura Sistemas Operativos 2 impartida en las titulaciones de Informtica de la Universidad Politcnica de Valencia. Versin: 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, redireccin y tubos .............................................................................................................. 10 Ejercicios sobre seales................................................................................................................................................ 23 Ejercicios sobre directorios y proteccin. .................................................................................................................... 34 Ejercicios sobre hilos y seccin crtica ......................................................................................................................... 41 Ejercicios sobre semforos y monitores ....................................................................................................................... 52 Problemas de sincronizacin ........................................................................................................................................ 79 Ejercicios sobre prcticas. ............................................................................................................................................ 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 podra fallar. Puede que haya llamadas que nunca fallen. En caso de ser as indica tambin por qu

Ej. 1-1

fork()

Puede fallar si el sistema no tiene suficientes recursos para realizar la creacin de un nuevo proceso. Ejemplos: falta de memoria, falta de entradas libres en la tabla de procesos o haber llegado al lmite 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 direccin 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 podra fallar. Puede que haya llamadas que nunca fallen. En caso de ser as indica tambin por qu

Ej. 1-2

execv()

Existen mltiples razones. Ejemplos: Que el fichero no exista, que no sea ejecutable, que alguna de las direcciones facilitadas como argumentos no sea vlida (est fuera de los rangos del espacio lgico a los que se ha asignado memoria), ... Tambin puede fallar por mltiples 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 slo lectura un fichero para el que no se tenga ese derecho en la palabra de proteccin), que se intente abrir en modo escritura en un dispositivo montado como slo lectura, etc. Esta llamada no puede fallar. Con ella se solicita la terminacin del proceso y no se necesita solicitar ningn recurso para poder llevar a cabo esta accin.

open()

exit ()

Indica cuntos procesos llegarn a generarse como resultado de la ejecucin de este programa y cuntos de ellos terminarn. Justifcalo adecuadamente.
#include <unistd.h> int main(void) { int i;

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

Slo se generan cinco procesos (ms 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 tenan 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 terminacin de todos ellos, la llamada wait() devolver un error por no quedar ms procesos hijos, saliendo entonces del bucle "while". Todos los procesos llamarn implcitamente a exit() cuando el control
retorne a la biblioteca de C, tras haber ejecutado completamente la funcin principal del programa.

Explica si como resultado de la ejecucin del programa anterior podr llegar a generarse, aunque sea durante un intervalo muy breve procesos hurfanos o procesos zombies.
No pueden generarse procesos hurfanos, pues el proceso padre no terminar antes que los hijos. Esto se logra con la utilizacin 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.

Cmo puede un proceso hijo saber si su padre ha finalizado su ejecucin? Comenta la respuesta. NOTA.- No se puede modificar el cdigo 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 cuntos procesos llegarn a generarse como resultado de la ejecucin de este programa y cuntos de ellos terminarn. Justifcalo adecuadamente.
#include <unistd.h> int main(void) { int i; for (i =0; i <5; i++) if (!fork()) wait(NULL); }

El nmero total de procesos generados es 32, pues en cada iteracin 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 terminarn normalmente. La llamada wait() slo es utilizada por los procesos "hijos " de cada fork() completado, por lo que falla de inmediato y no tiene ningn efecto.

Explica si como resultado de la ejecucin del programa anterior podr llegar a generarse, aunque sea durante un intervalo muy breve procesos hurfanos o procesos zombies.

Pueden llegar a generarse ambos tipos de procesos. Podr haber procesos hurfanos porque resulta posible que un proceso padre termine antes que alguno de sus hijos, con lo que stos quedaran hurfanos. Tambin es posible que haya procesos en estado zombie durante cierto intervalo de tiempo. Esto suceder si un proceso hijo termina antes que su padre. Como ninguno de los procesos padres llega a realizar un wait(), mientras no terminen estos padres, sus hijos que hayan terminado permanecern en estado zombie. Cuando su padre termine, estos procesos quedaran hurfanos y seran heredados por un proceso especial del sistema (normalmente "init"), que realizara el waitQ correspondiente y los eliminara.

Dado el siguiente cdigo en C y POSIX, indique que imprimirn por salida estndar cada uno de los procesos que se generan. Considere todos los posibles casos de ejecucin de fork().
#include <stdio.h> #include <unistd.h> main(){ int pid; int i, status; i=0; printf("Mensaje 1: valor %d\n,i); switch(pid=fork()){ case -1: i++; printf("Mensaje 2: valor %d\n",i); exit(-1); case 0: i++; printf("Mensaje 3: valor %d \n,i); exit(3); default: i++; printf("Mensaje 4: valor %d \n,i); while (wait(&status)>0); } printf("Mensaje 5: valor %d \n,i); }

Ej. 1-7 Ej. 1-8

El primer mensaje siempre se escribe y entonces la variable i sigue valiendo cero. A continuacin se llega al fork(), que devolver un cero para el proceso hijo y el PID del hijo para el proceso padre. No obstante, dicha llamada podra llegar a fallar si el proceso que intenta el fork() se est ejecutando en un sistema donde haya muy poca memoria libre, por ejemplo. Por tanto, si el fork() llega a fallar, por esa u otra causa, el resultado obtenido habra sido: Mensaje 1: valor 0 Mensaje 2: valor 1 Mientras que si el fork() no falla, se obtendra: Mensaje 1: valor 0 Mensaje 3: valor 1 Mensaje 4: valor 1 Mensaje 5: valor 1 donde el mensaje 3 habra sido impreso por el proceso hijo y los mensajes 1, 4 y 5 seran impresos por el proceso padre. El orden entre los mensajes 3 y 4 podra ser diferente. Slo se imprimir un mensaje 1.

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

Hay varios ejemplos. Todas aquellas llamadas que impliquen la obtencin de atributos del propio proceso jams fallaran pues la informacin solicitada est siempre disponible y no se necesita ninguna control de proteccin para llegar a ella: getpid(), getppid(), getuid(), geteuid(), getgid(), getgid(), etc. La llamada para solicitar la terminacin del propio proceso tambin funcionar, pues es una accin que siempre debe permitirse. Por tanto, exit() tambin funciona en todos los casos.

Indique cuntos procesos llegarn a generarse como resultado de la ejecucin de este programa y cuntos de ellos terminarn. Justifquelo adecuadamente.

Ej. 1-9 Ej. 1-10

#include <unistd.h> int main(void) { int i; for (i=0; i<3; i++) if (fork()) wait(NULL); } Se crearn 23 procesos y todos ellos terminan. Debido a la presencia de la llamada wait(), empleada nicamente por los procesos padres en cada iteracin, los respectivos procesos hijos deberan terminar antes que sus padres. As, el orden de creacin y terminacin sera el siguiente (los nombres de los procesos tratan de reflejar nicamente el orden de creacin, pueden utilizarse otros cualesquiera): 1) Inicialmente se crea el proceso P1 que empieza a ejecutar el programa. 2) En la primera iteracin del bucle genera al proceso P2 y espera a que termine. 3) P2 contina y genera cuando i=1 al proceso P3 y espera a que termine. 4) P3 contina y genera cuando i=2 al proceso P4 y espera a que termine. 5) P4 contina, pero ya no debe generar a ningn proceso ms, terminando enseguida. 6) Con ello, P3 contina, termina tambin su bucle y finaliza. 7) Con ello, P2 contina, realiza la iteracin con i=2, generando entonces al proceso P5 y queda esperando a que P5 acabe. 8) P5 puede continuar. En el bucle ya no tiene que hacer nada ms, por lo que termina de inmediato. 9) Con ello, P2 contina, termina su bucle y finaliza su ejecucin. 10) Ahora P1 ya puede continuar, entra en la iteracin i=1, generando a un proceso P6 y espera a que ste termine. 11) P6 puede continuar, entrando entonces en la iteracin i=2, generando por ello al proceso P7 y debiendo esperar su finalizacin. 12) P7 es el nico que puede continuar y como ha terminado ya el bucle, acaba su ejecucin. 13) Con ello, P6 sale del wait(), terminando su bucle y finalizando su ejecucin. 14) Gracias a esto, P2 sale tambin del wait(), entra en su iteracin i=2, genera entonces al proceso P8 y espera a que acabe. 15) P8 contina, finalizando su bucle y terminando enseguida. 16) Esto libera a P1, con lo que ya puede tambin finalizar la ejecucin del bucle y terminar.

Sabiendo que el descriptor 0 es el que corresponde a la entrada estndar y que la llamada close()permite cerrar el descriptor cuyo nmero reciba como argumento escribe el fragmento de cdigo necesario para que un programa tome su entrada estndar del fichero "ejemplo". Implemntalo sin utilizar dup()ni dup2(). Puede ocasionar algn problema esta implementacin de la redireccin?

Ej. 1-11 Ej. 1-12

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

Sabiendo que el descriptor 2 es el que corresponde a la salida de error y que la llamada close()permite cerrar el descriptor cuyo nmero reciba como argumento escribe el fragmento de cdigo necesario para que un programa redirija su salida de error al fichero "ejemplo". Implemntalo sin utilizar dup()ni dup2(). Puede ocasionar algn problema esta implementacin de la redireccin?
close (2); /* El descriptor cero queda libre. */ open (ejemplo, O_WRONLY | O_CREAT | O_TRUNC, 0666); /* Open utiliza el descriptor ms bajo */ /* y en este caso es el cero. */ Este cdigo plantea el problema de que hemos tenido que perder la salida de error actual antes de abrir el fichero que desebamos asociar a dicha entrada. Con ello, si la posterior llamada open() fallara (ejemplos: por no tener permisos de escritura sobre el directorio, o estar trabajando en un disco montado en modo slo lectura) habramos dejado al proceso sin salida de error. La llamada dup() y su variante dup2() solucionan estos problemas.

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

Ej. 1-13

En la llamada execlp()se busca el programa a ejecutar en todos los directorios de la variable de entorno PATH. Cierto. Tanto con esta llamada como con execvp() el ejecutable se busca en los directorios presentes en PA TH Utilizando la llamada waitpid(), el proceso puede averiguar si tiene procesos hijos que no han terminado sin necesidad de suspenderse. Cierto Para ello debe utilizar la opcin W NOHANG en el ltimo argumento Utilizando la llamada open(), un proceso puede crear un fichero y darle la palabra de proteccin inicial que considere conveniente. Cierto. Para crearlo debe utilizar la combinacin "O WRONLY | O CREAT" en el segundo argumento y el valor que quiera darle a la palabra de proteccin en el tercero Con esta lnea de rdenes: (ls l noestaba * | sort) > uno 2> uno se garantiza que no se perder ningn carcter de la salida estndar ni de error Lo podremos encontrar todo en el fichero "uno". Falso. Al haber especificado el mismo nombre de fichero en las dos redirecciones, se efectuarn dos aperturas. Cada apertura tendr su propio puntero de posicin. 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 machacara. Es imposible que dos procesos distintos compartan una misma descripcin de apertura Falso. Los procesos hijos heredan los descriptores de fichero que tuvieran sus padres. Con ello, padres e hijos comparten una misma descripcin de apertura.

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

Ej. 1-14

F V

En la llamada execve () se busca el programa a ejecutar en todos los directorios de la variable de entorno PATH. No. Las variantes que buscan el ejecutable en el PATH son las terminadas en "p": execvp() y execlp(). El resto necesitan la ruta completa del ejecutable. Utilizando la llamada wait(), el proceso puede averiguar si tiene procesos hijo que no han terminado sin necesidad de suspenderse. No. Para realizar esto debe utilizarse la llamada waitpid() con el flag W_NOHANG Utilizando la llamada open(), un proceso puede crear un fichero y darle un propietario diferente a su UID efectivo (es decir al UID del proceso) No. Con esta llamada s que pueden crearse ficheros, pero stos tienen como propietario al UID efectivo del proceso creador. Resulta imposible abrir dos veces un mismo fichero en un mismo proceso. S que es posible. En algunos de los ejemplos vistos en clase ocurra. Es posible que dos procesos distintos compartan una misma descripcin de apertura S que es posible. Al realizar un fork() el proceso hijo hereda una copia de los descriptores del padre y con ello obtiene acceso a las mismas descripciones de apertura.

Dado el siguiente cdigo, indique cuntos procesos sern generados por la ejecucin de este programa (incluyendo al original), as como cul de ellos ser el ltimo que terminar. Suponga que ninguna de las llamadas al sistema utilizadas provoca errores y que los procesos generados no reciben ninguna seal.

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

Ej. 1-15

Se crean 8 procesos ms el original, total 9 procesos. Cada hijo crea un nico proceso y tanto los hijos como el proceso que crean hacen break. El ltimo proceso en finalizar es el original.

Considere la ejecucin del siguiente cdigo:


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

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 hurfano Se genera un proceso padre zombie Se genera un proceso padre hurfano No se genera ningn nuevo proceso

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

Ej. 1-16

Ej. 1-17

F F V F F V

Utilizando la llamada open(archivo, O_RDWR) el sistema nos retorna dos descriptores uno para lectura y otro para escritura para el fichero archivo. Un proceso puede crear un fichero con un propietario diferente a su UID efectivo, utilizando la llamada open(). La llamada open() no debe ser utilizada para abrir un directorio. Resulta imposible que un proceso ejecute dos veces la llamada open() en su cdigo sobre un mismo fichero. Es imposible que dos procesos distintos compartan una misma descripcin de apertura

Es posible que al ejecutar la llamada open(nuevo,O_WRONLY|O_CREAT|O_TRUNC,0644) se cree un fichero con la palabra de proteccin "-rw-r--r--

Se pide escribir el cdigo para la creacin de los procesos segn los esquemas descritos en las figuras (a) y (b). El cdigo se realizar utilizando las correspondientes llamadas POSIX y deber incluir un comentario que indique dnde habra que introducir la llamada exec a ejecutar por cada uno de los hijos. Suponiendo que cada proceso hijo realiza la llamada exec correspondiente, comente en cada caso la posibilidad de evitar la aparicin de procesos zombie.
a)
padre

b)

padre

hijo1 hijo1 hijo2 hijo3 hijo2

hijo3

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

Ej. 1-18

Ej. 1-19

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

2. Ejercicios sobre ficheros, redireccin y tubos


2.1 Dificultad de los ejercicios
Nivel de dificultad Principiante Medio Avanzado Ejercicios

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

Ej. 2-1

Los pasos a realizar para crear un tubo de comunicacin entre dos procesos padre e hijo de manera que el padre escribe y el hijo lee son: 1) Crear el tubo realizando una llamada pipe() en el proceso padre. int fd[2]; pipe(fd);

2) Crear un proceso hijo, mediante la llamada fork() que heredara la tabla de descriptores de ficheros. hijo_pid=fork(); 3) Asignarle a la entrada standard del proceso hijo al descriptor de lectura del tubo y cerrar los descriptores del tubo.

if (hijo_pid==0) { dup2(fd[0],STDIN_FILENO); close(fd[0]); close(fd[1]); } 4) Asignarle a la salida standard del proceso padre al descriptor de escritura del tubo y cerrar los descriptores del tubo.

if (hijo_pid>0) { dup2(fd[1],STDOUT_FILENO); close(fd[0]); close(fd[1]); }

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

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

Ej. 2-2

int tubo [2] pipe(tubo) if(fork()) { dup2(tubo[l], 2); } else { dup2(tubo[0], 0);

/* Creamos el tubo antes de crear el proceso hijo, para que lo herede. */ /* Cdigo del padre. Suponemos que no hay fallos. */ /* Cambiamos la salida de error. */ /* Cdigo del hijo. */ /* Cambiamos la entrada estndar. */

/* Lo siguiente ya es cdigo comn. */ close(tubo[0]); /* Cerramos todos los descriptores del tubo, para cumplir lo pedido en el punto 3. */ close(tubo[1]);

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

Ej. 2-3

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

Dado el siguiente cdigo, indica como queda la tabla de descriptores del proceso padre en la lnea sealada como B) y en el hijo en la lnea sealada como A). int fd[2], fd1, fde; fde = open(error.txt, NEWFILE, MODE644); dup2(fde, STDERR_FILENO); close (fde); pipe(fd); if (fork() == 0) { /*CODIGO DEL HIJO */ dup2 (fd[0], STDIN_FILENO); close (fd[0]); close (fd[1]); fd1 = open(salida.txt, NEWFILE, MODE644));

dup2(fd1, STDOUT_FILENO); close (fd1); // ---> A) ESTADO TABLA HIJO ... } else /* CODIGO DEL PADRE*/ dup2 (fd[1], STDOUT_FILENO); close (fd[0]); close (fd[1]); // ---> B) ESTADO TABLA PADRE ... } Suponer que el estado inicial de la tabla de descriptores es el siguiente:
[0] [1] [2] [3] [4] PADRE /dev/tty0 /dev/tty0 /dev/tty0 -

Ej. 2-4

[0] [1] [2] [3] [4]

PADRE /dev/tty0 Tubo_w Error.txr

[0] [1] [2] [3] [4]

HIJO Tubo_r Salida.txt Error.txt

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


int main(){ int i; char leer[100]; int tubo[2];

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

Ej. 2-5

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

Indique qu valor tendr el puntero de posicin asociado a los diferentes descriptores de fichero utilizados en el siguiente fragmento de programa cuando se haya llegado a su ltima instruccin. Asuma que no se produce ningn error en ninguna de las llamadas y que ninguna de las funciones utilizadas llega al final de fichero.
int main(void) { int fd1, fd2, fd3, fd4; char buffer[512]; fd1 = open( ejemplo1, O_RDWR ); read(fd1, buffer, 100); fd2 = dup(fd1); write(fd1, buffer, 100); fd3 = open( ejemplo2, O_WRONLY ); write(fd3, buffer, 40); fd4 = dup(fd2); read(fd4, buffer, 323); }

Ej. 2-6

fd1: 523

fd2:

523

fd3: 40

fd4: 523

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

Indique qu valor tendr el puntero de posicin asociado a los diferentes descriptores de fichero utilizados en el siguiente fragmento de programa cuando se haya llegado a su ltima instruccin. Asuma que no se produce ningn error en ninguna de las llamadas y que ninguna de las funciones utilizadas llega al final de fichero.
int main(void) { int fd1, fd2; char buffer[512]; fd1 = open( ejemplo1, O_RDWR ); read(fd1, buffer, 100); fd2 = open( ejemplo2, O_WRONLY ); fork(); /* A partir de esta lnea hay dos procesos. */ write(fd2, buffer, 40); read(fd1, buffer, 323); ... }

Ej. 2-7

fdl: 746

fdl: 80

"fd1" ya vala 100 antes del fork(). Tras el fork(), tanto el padre como el hijo tienen acceso a la misma descripcin de apertura sobre "ejemplo1" con dicha variable. Ambos realizan una lectura de 323 bytes, con lo que el valor final del puntero ser 100+323+323=746. "ejemplo2" tambin haba sido abierto antes del fork(), por lo que la escritura que aparece despus de l es ejecutada tanto por el padre como por el hijo. Con ello, el puntero de posicin vale finalmente 40+40=80.

El siguiente programa genera distintos procesos que acceden a ficheros. Suponemos que no existe problema alguno para abrir dichos ficheros, y que su talla es infinita. Para cada proceso, indica cual ser el valor del puntero de posicin asociado a cada uno de los descriptores inmediatamente antes de el proceso ejecute exit().
void main(void) { int fd1, fd2, fd3; char buffer[100]; fd1 = open( exemple, O_RDONLY ); read(fd1, buffer, 50); fd2 = open( exemple2, O_WRONLY ); write(fd2, buffer, 50); fd3 = dup(fd1); read(fd3, buffer, 60); fork(); write(fd2, buffer, 45); read(fd1, buffer, 50); write(fd1, buffer, 20); exit(1); }

Ej. 2-8

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

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

Ej. 2-9

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

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


Si un proceso cierra su entrada, salida y salida de error estndares ya no puede te acceso a la terminal. No tiene por qu haberlo perdido para siempre. Puede abrir el fichero especial correspondiente ("/dev/tty" o el que sea) ms tarde, si es que tiene permisos. Otra opcin habra sido realizar un dup() o dup2() para conservar el acceso en otros descriptores, antes de efectuar el cierre. Las llamadas necesarias para leer o escribir en la Terminal no son las mismas que la que deben emplearse para leer o escribir ficheros. Deben ser las mismas, en caso contrario no tendra ningn sentido todo lo que se ha explicado sobre las redirecciones de la entrada, salida v salida de error estndares. No hay forma de que un proceso pueda tener la misma descripcin de apertura asociada a la entrada y salida de error estndar. S que puede conseguirse, utilizando "dup2(2,0)" por ejemplo. No tendra excesivo sentido hacerlo pero s que es posible. En ciertos casos, escribir en un tubo puede causar que el sistema operativo enve una seal a ese proceso escritor. S, se llegara a enviar SIGPIPE si no hubiera ningn descriptor que permitiera la lectura de la informacin existente en el tubo Un proceso siempre se suspende al utilizar la llamada read () sobre un fichero puesto que dicha peticin siempre se traduce en una operacin de E/S sobre el disco. No tiene por qu suspenderse. La cach de bloques que mantiene el sistema operativo puede evitar la suspensin en muchos casos

Ej. 2-10

Dado el siguiente cdigo en el cual se generan al menos tres procesos P1, P2 y P3:
... pipe(fd); pipe(fd2); if(fork() != 0){ /***Proceso P1 ***/ dup2(fd[1],STDOUT_FILENO); close(fd[0]); close(fd[1]); dup2(fd2[0],STDIN_FILENO); close(fd2[0]); close(fd2[1]); }else{ /***Proceso P2 ***/ dup2(fd[0],STDIN_FILENO); close(fd[0]); close(fd[1]); pipe(fd3); if(fork() != 0){ close(fd2[0]); close(fd2[1]); dup2(fd3[1],STDOUT_FILENO); close(fd3[0]); close(fd3[1]); }else{ /***Proceso P3 ***/ dup2(fd3[0],STDIN_FILENO); close(fd3[0]); close(fd3[1]); dup2(fd2[1],STDOUT_FILENO); close(fd2[0]); close(fd2[1]); } } ...

Indique para los procesos P1, P2 y P3, el contenido, despus de la ejecucin de este cdigo, de sus respectivas tablas de descriptores de archivo, la relacin existe entre ellos y cul es el esquema de comunicacin resultante.

Ej. 2-11

En el cdigo presentado se estn creando tres tubos, a los que llamaremos T1 (asociado al vector de descriptores fd), T2 (asociado al vector fd2) y T3 (con el vector fd3). Tambin se muestra claramente que se crear un total de 3 procesos. Las tablas de descriptores seran: 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 tena asociado el proceso padre de P1. La relacin existente entre los tres procesos es: P1 es el padre de P2 y P2 es el padre de P3. El esquema de comunicacin resultante es una especie de anillo entre P1, P2 y P3, como puede deducirse de la tabla de descriptores anterior. As, T1 sirve para pasar la salida estndar de P1 a la entrada estndar de P2, T3 pasa la salida estndar de P2 a la entrada estndar de P3 y T2 pasa la salida estndar de P3 a la entrada estndar de P1.

Diga qu imprimirn por la salida estndar cada una de las rdenes que se detallan a continuacin, suponiendo que previamente se ha ejecutado con xito la orden $ echo hola > hola: 1. $./a.out cat 2. $./a.out hola siendo a.out el ejecutable correspondiente al siguiente cdigo en C:
main(int argc, char **argv) { int fd[2], f1, i; char str[80]="adios"; pipe(fd); if (fork()) { dup2(fd[1], STDOUT_FILENO); close(fd[0]); close(fd[1]); f1 = open("hola", O_RDONLY); dup2(f1, STDIN_FILENO); if (execvp(argv[1], &argv[1]) == -1) printf("El proceso 1 ha cometido un error\n"); } else { dup2(fd[0], STDIN_FILENO); close(fd[0]); close(fd[1]); for(i=0; (str[i] =getchar()) != EOF && i<80; i++); str[i-1]='\0'; printf("Soy el proceso p2 y digo: %s\n", str); } }

NOTA: la funcin getchar() devuelve el siguiente carcter que lee de la entrada estndar.

Ej. 2-12 Ej. 2-13

1: El cdigo presentado trata de abrir en modo lectura el fichero hola y dejarlo como entrada estndar para el
proceso padre. ste ejecutar la orden facilitada como primer argumento al programa. Adems, se crea un proceso hijo. Mediante un tubo se consigue pasar la salida del proceso padre a la entrada del proceso hijo. Este proceso hijo, almacena dicha entrada en la cadena str (que puede guardar un mximo de 80 caracteres) y posteriormente la imprime en el mensaje Soy el proceso p2 y.... En este primer ejemplo, el programa a ejecutar en el proceso padre ser un cat por lo que el hijo, al final, obtendr en su cadena str el contenido del fichero hola, que es justamente esa misma palabra. Por tanto, la salida obtenida ser: Soy el proceso p2 y digo: hola

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

Imagine que en un determinado sistema operativo se facilita una llamada (por ejemplo, int crear_proceso(char *nombre_programa, char *argumentos[]); ) que de manera atmica genera un proceso hijo y, si no hay errores para localizar y ejecutar el fichero, consigue que este nuevo proceso ejecute el programa suministrado en el primer parmetro con los argumentos facilitados en el segundo. En esta llamada, el proceso hijo hereda todos los descriptores de fichero que pudiera tener el proceso padre. Indique qu ventajas o inconvenientes plantea esta aproximacin frente a la empleada en POSIX, a la hora de implantar las redirecciones y tuberas.
En POSIX, las redirecciones y tuberas se suelen implantar una vez se ha creado el proceso hijo mediante fork(), heredando todos los descriptores del padre y antes de efectuar el exec() para cambiar el programa que ejecutar el nuevo proceso hijo. Con ello no hay que modificar para nada la tabla de descriptores que posea el padre y tampoco hay que hacer nada especial en el programa que finalmente ejecutar el hijo. Con la llamada que se comenta, el padre estara obligado a: 1) Duplicar sus descriptores estndar en otras entradas para poderlos recuperar posteriormente. 2) Modificar sus descriptores estndar para que tengan los valores que espera el hijo para implantar sus redirecciones o tuberas. 3) Llamar a crear_proceso() para generar el proceso hijo con las redirecciones apropiadas. 4) Recuperar los descriptores estndar del padre almacenados en el paso 1. No es excesivamente complicado hacer esto, pero resulta bastante ms cmodo hacerlo con el esquema propio de POSIX. Por tanto, nicamente implicara el inconveniente de necesitar ms cdigo en el programa del proceso padre.

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

Proceso hijo

tubo

Proceso padre

/tmp/file.txt

Donde el proceso padre redirige su salida al fichero /tmp/file.txt. Para ello se propone el siguiente cdigo: ...
pipe(fd); if(fork() == 0){ dup2(fd[1],STDOUT_FILENO); close(fd[0]); close(fd[1]); /* resto de codigo */ }else{ dup2(fd[0],STDIN_FILENO); close(fd[0]); close(fd[1]); fd_open=open("/tmp/file.txt",O_WRONLY|O_TRUNC|O_CREAT,0600); /* resto de codigo */ } ...

Suponiendo que el resto de cdigo no altera la redireccin de la E/S de los procesos, indique si el cdigo es correcto o no. En caso de no serlo escriba las modificaciones que son necesarias realizar en el cdigo para que lo sea.

Ej. 2-14

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

Dada la siguiente lnea de ordenes del shell: $ cat fich1 | sort | wc l > result a) Indique el nmero de procesos y ficheros que intervienen en la misma y el contenido de las tablas de descriptores de cada proceso.

Ej. 2-15

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

b) Escriba el cdigo, en POSIX y C, necesario para que se lleve a cabo la comunicacin entre los procesos que intervienen en dicha lnea de rdenes. Utilice las siguientes llamadas: execlp(/bin/cat, cat, fich1,NULL); execlp(/bin/sort, sort,NULL); execlp(/usr/bin/wc, wc, l,NULL);

Ej. 2-16

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

Completa el siguiente programa para que ejecute el mandato ls la, redirigiendo la salida estndar al fichero listado.txt. Para ello utiliza las variables Fichero y Comando. #include #include #include #include #include <sys/types.h> <sys/stat.h> <stdio.h> <unistd.h> <fcntl.h>

#define NEWFILE (O_WRONLY | O_CREAT | O_EXCL) #define MODE644 (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) char *Fichero = "listado.txt"; char *Comando[2] = { "ls", "-la" }; main(int argc, char *agrv[]) { int fd; /********** COMPLETAR ************/ }

Ej. 2-17

#include #include #include #include #include

<sys/types.h> <sys/stat.h> <stdio.h> <unistd.h> <fcntl.h>

#define NEWFILE (O_WRONLY | O_CREAT | O_EXCL) #define MODE644 (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) char *fitxer = "llistat.txt"; char *comandament[3] = { "ls", "-la", NULL }; main(int argc, char *argv[]) { int fd; if ((fd = open(fitxer, NEWFILE, MODE644)) == -1) { fprintf(stderr, "No es pot crear: %s\n", fitxer); exit(1); } // Creaci de nou fitxer if (dup2(fd, STDOUT_FILENO) == -1) { fprintf(stderr, "No es pot redirigir a: %s\n", fitxer); exit(1); } close (fd); //Eixida estndard redirigida a fitxer if (execvp(comandament[0], &comandament[0]) < 0) { fprintf(stderr, "No es pot executar: %s\n", comandament[0]); exit(1); } // Comandament llenat }

Considere el cdigo de la figura, cuyo correspondiente fichero ejecutable se denomina a.out


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

La ejecucin de dicho cdigo pretende ser equivalente a la ejecucin en el shell de: $ echo hola | sort | wc Sin embargo, al ejecutar a.out se observa que este proceso no termina (el shell no devuelve el prompt) y que su ejecucin no proporciona ningn resultado por la salida estndar. Se observa, adems, que despus de lanzada la ejecucin de a.out permanecen sin acabar los siguientes procesos en el sistema:
PID TTY TIME CMD

17466 pts/4 00:00:00 a.out 17468 pts/4 00:00:00 sort 17469 pts/4 00:00:00 wc a) Indique de forma concisa el motivo por el que a.out no termina y diga las modificaciones necesarias que se han de introducir en el cdigo para que el resultado de la ejecucin sea el esperado.

Ej. 2-18 Ej. 2-19

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

b) En la situacin expuesta en el enunciado y con el cdigo descrito: b1) Razone si al ejecutar la orden $kill -9 17468 se producira la seal SIGPIPE. b2) Si en vez de la orden anterior se ejecuta la orden $kill -9 17466 , indique si quedaran en el sistema procesos zombies de forma permanente. Razone la respuesta.

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

b2) No quedan zombies. Quedan procesos hurfanos de forma permanente. El padre de estos procesos ha muerto, pero los procesos no han invocado exit, pues se encuentran suspendido esperando leer del tubo.
Con el cdigo correcto, no quedaran zombies permanentes, pues si el padre muriese antes de ejecutar wait, INIT adoptara e invocara wait.

Justifique si finalizan o no y en que estado se encontraran cada uno de los procesos (padre e hijo) que se crearan al ejecutar los cdigos representados en la tabla (CODIGO_A y CODIGO_B). Diga que mensajes se imprimiran en la pantalla.
//*****CODIGO_A**************// int main() {int i; char leer[100]; int tubo[2]; pipe (tubo); if (fork()==0) {for (i=0; i<10; i++) { write(tubo[1],"HIJO",5); } close(tubo[1]); close(tubo[0]); } else { close(tubo[1]); while( read(tubo[0],leer,5)!= 0) printf("%s\n",leer); while (wait(NULL)>0); } //*****CODIGO_B**************// int main() {int i; char leer[100]; int tubo[2]; pipe (tubo); if (fork()==0) {for (i=0; i<10; i++) { write(tubo[1],"HIJO",5); } } else { while(read(tubo[0],leer,5)!= 0) printf("%s\n",leer); close(tubo[1]); close(tubo[0]); while (wait(NULL)>0); } }

Ej. 2-20 Ej. 2-21

A) //***********CODIGO_A**********// En este cdigo el hijo escribe en el tubo 10 veces la palabra HIJO y se quedara en estado zombie hasta que su padre haga wait. El padre imprime por pantalla 10 veces la palabra HIJO, hace wait y termina. B) //***********CODIGO_B**********// En este cdigo el hijo escribe en el tubo 10 veces la palabra HIJO y se quedara en estado zombie hasta que su padre haga wait. En este cdigo el padre lee todo lo que el hijo ha escrito en el tubo y sigue intentando leer. Como no se ha cerrado el descriptor de escritura del tubo el padre no recibe la seal de EOF y por tanto se encuentra suspendido en la llamada read que es bloqueante.

Se pide escribir un programa que realice las llamadas a sistema necesarias para ejecutar la siguiente lnea 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 travs de la variable de entorno PATH.
int main(){ int tubo[2]; pipe(tubo); If (fork()){ dup2(tubo[1],STDOUT_FILENO); close(tubo[0]); close(tubo[1]); execlp("ls","ls",NULL); } else { dup2(tubo[0],STDIN_FILENO); close(tubo[0]); close(tubo[1]); execlp("wc","wc",NULL); } return 0; }

3. Ejercicios sobre seales


3.1 Dificultad de los ejercicios
Nivel de dificultad Principiante Medio Avanzado Ejercicios

3.2 Ejercicios
Indique qu funciones POSIX permiten enviar seales a un proceso, a cuntos procesos pueden enviar tales funciones una seal a la vez (explicar mediante qu parmetros se puede especificar esto y de qu forma) y cundo tiene lugar el envo

Ej. 3-1 Ej. 3-2

Las dos nicas funciones que permiten enviar seales a los procesos son kill() y alarm(). Con el primer parmetro de kill() se puede especificar si la seal debe ser enviada a solo un proceso (valor positivo), a todos los procesos del grupo del emisor (valor cero), a todos los procesos (valor -1) o a todos los procesos de un grupo determinado (otros valores negativos). Utilizando esta funcin, la seal se enva de inmediato. Con alarm() la seal a enviar ser la S1GALRM y el envo se producir cuando hayan transcurrido los segundos especificados en el nico parmetro que necesita esta funcin. Dicha seal slo se enva al propio proceso que ha utilizado la llamada.

Indique qu funciones POSIX modifican la mscara de seales de un proceso, de qu manera (permanente o temporal) y cundo tiene lugar la modificacin.

sigprocmask() es la funcin a utilizar para modificar permanentemente la mscara de bloqueo de seales que tiene un proceso. La modificacin tiene lugar desde el preciso instante en que se invoque y se mantendr hasta que vuelva a usarse esta misma funcin (salvo los intervalos en los que est activa la mscara de sigsuspend() o de sigaction(). sigaction() permite especificar el tratamiento de seales que se llevar a cabo para una determinada seal. Si el tratamiento a utilizar es la instalacin de una funcin manejadora, tambin existe la posibilidad de utilizar el campo "sa_mask" de la estructura "sigaction" para indicar las seales que se aadirn a la mscara "oficial" durante la ejecucin de la funcin manejadora. Por tanto, esta nueva mscara tiene un duracin breve (no es permanente). sigsuspend() permite especificar la mscara a utilizar mientras el proceso que solicite esta funcin permanezca suspendido. Tan pronto como llegue alguna de las seales no bloqueadas en esa mscara temporal, el proceso abandona el estado de suspensin (bien cambiando a preparado para ejecutar el manejador o bien muriendo) y si contina activo recupera la mscara anterior.

Conteste de forma concreta y concisa a las siguientes preguntas sobre seales: a) Qu es una seal?

b) Qu mecanismos pueden desencadenar la produccin de una seal? c) Cmo puede impedir temporalmente un proceso que el Sistema Operativo le notifique la ocurrencia de una seal? d) Qu es un manejador? Cmo se instala un manejador? Qu tipo de comportamiento se pueden asociar a una seal?

Ej. 3-3 Ej. 3-4

a) Una seal es la notificacin por software de que ha ocurrido un evento. b) La generacin de seales puede ser: c) Desde el Terminal. Ejemplo CTRL-C Por error en ejecucin. Ejemplo: Divisin por cero SIGFPE. Por software. Ejemplo: alarm(10), kill(numero proceso, numero de seal) Para impedir temporalmente el depsito de una seal esta debe enmascararse. Para ello se ha de trabajar con la mscara definindola (sigemptyset(), sigfillset(), sigaddset()) e instalando la mscara (sigprocmask()). d) Un manejador es el cdigo que se ejecuta como respuesta a una seal, para ello hay que instalarlo previamente mediante la llamada sigaction(). La llamada sigaction() tiene un parmetro que es el manejador a instalar para una determinada seal que tambin pasamos como parmetro. Este manejador pude ser: el de defecto(SIG_DFL) que normalmente finaliza el proceso, el de ignorar la seal SIG_ING, o el cdigo de una funcin.

Escriba el fragmento de cdigo necesario para que un proceso pueda modificar su mscara actual de bloqueo de seales realizando lo siguientes cambios: desbloquear la seal SIGHUP y bloquear la seal SIGILL.
/* Primera alternativa: */ sigset_t mascara /* Primero debemos crear una mscara donde slo aparezca SIGHUP. */ sigemptyset(&mascara ); sigaddset(&mascara, SIGHUP); /* Ahora eliminamos las seales de esa mscara de la que tenga el proceso. */ Sigprocmask(SIGUNBLOCK, &mascara, NULL); /* Repetimos el trabajo para SIGILL, pero ahora utilizando la accin SIGC */ sigemptyset( &mascara ); sigaddset(&mascara, SIGILL) sigprocmask(SIGBLOCK &mascara NULL)

/* Segunda alternativa: Partir de la mscara actual. */ sigset_t mascara; ... /* La accin no importa, queremos obtener la mscara actual. */ sigprocmask(SIG_BLOCK, NULL, &mascara); /* Ahora, eliminamos SIGINT. */ sigdelset(&mascara, SIGINT); /* Y aadimos las que hay que bloquear. */ sigaddset(&mascara, SIGHUP); sigaddset(&mascara, SIGILL); /* Finalmente, reinstalamos la mscara que acabamos de construir.*/ sigprocmask(SIG_SETMASK, &mascara, NULL); ... /* Otra solucin consistira en haber construido una mscara con solo SIGINT activo, utilizando sigprocmask() con la accin SIG_UNBLOCK. Tras esto, hay que construir otra mscara con SIGHUP y SIGILL activos, utilizando entonces sigprocmask() con la accin SIG_BLOCK. */

Escriba el fragmento de cdigo necesario para que un proceso pueda instalar a la funcin manejador (que se supone definida e implementada en otra parte del programa) como manejadora de la seal SIGHUP y tambin que pase a ignorar cualquier ocurrencia de la seal SIGINT.

Ej. 3-5

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

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

Ej. 3-6

F F

Realizar una lectura en un tubo puede originar que el sistema operativo enve la seal SIGPIPE al proceso lector. No. Esa seal slo puede ser generada para un proceso que intente escribir en un tubo que no tenga ningn lector. Un proceso puede tener bloqueada la entrega de todas las seales existentes. Las seales SIGKILL y SIGCONT no pueden bloquearse. Cuando un proceso enva una seal SIGKILL a otro, el sistema operativo nos garantiza SIEMPRE que el destinatario de dicha seal morir. Puede que el proceso especificado como destino de esa seal no exista o pertenezca a otro usuario. En ambos casos, fallara. Mediante la llamada al sistema sigsuspend() se puede solicitar que algunas seales sean ignoradas mientras dure la espera. No. Lo que puede hacerse es aadir ciertas seales a la mscara de bloqueo. No es lo mismo ignorar una seal que bloquearla Existen seales que no pueden ser ignoradas, bloqueadas, ni tener una funcin manejadora asociada. SIGKILL y SIGCONT no pueden ser ignoradas, bloqueadas, ni tener una funcin manejadora. Slo pueden tener su tratamiento por omisin (matar al proceso en el caso de SIGKILL y hacer que contine en el caso de SIGCONT)

Explica las diferencias que pueden haber entre las siguientes formas de terminar un proceso POSIX. Si algunas formas son equivalentes razona por qu producen el mismo resultado. Nota: Considera la equivalencia entre soluciones en trminos de los valores que recibe la llamada wait que ejecuta el proceso padre cuando termina el proceso en cuestin.
a) int main(void){ codi(); } b) int main(void){ codi(); exit(0); } c) int main(void){ codi(); return 0; } d) int main(void){ codi(); kill(9,getpid()); }

Ej. 3-7

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

Dado el programa seal.c cuyo cdigo es el siguiente y que mantienen el tratamiento por omisin o defecto para todas las seales:
seal.c #include <stdlib.h> #include <stdio.h> #include <unistd.h> int main (int argc, char *argv[]) { alarm(5); for ( ; ; ); }

Indique de manera justificada cual ser su tiempo aproximado de ejecucin.

Ej. 3-8 Ej. 3-9

La llamada al sistema alarm() programa el envo de una seal SIGALRM cuando hayan transcurrido los segundos especificados en su nico parmetro. Como este proceso no realiza ningn tratamiento para esa seal y por omisin su efecto es terminar el proceso, el proceso mostrado terminara al cabo de aproximadamente cinco segundos de haberse iniciado. Obsrvese tambin que tras la llamada a alarm(), el programa presenta un bucle infinito, por lo que si no se hubiera enviado esa u otra seal, el proceso no terminara.

Contesta las siguientes cuestiones relativas a las seales: a) Explica qu diferencia existe entre enmascarar una seal y tratarla mediante una funcin b) Podemos mantener una misma seal enmascarada y fijar una funcin para su tratamiento? De ser as, Cul es el comportamiento de esta combinacin? Si no resulta posible, explica por qu. a) Enmascarar una seal significa evitar la notificacin de su ocurrencia al sistema operativo, impidiendo as cualquier tratamiento por parte de ste. Tratar una seal significa asociar a la seal una funcin, la cual se ejecutar en cuanto se produzca una ocurrencia no enmascarada, o cuando se desenmascare una ocurrencia anterior. b) S que se puede. El comportamiento es que el manejador se ejecutar cuando se desenmascare (la seal se memoriza) si es que esto llega a ocurrir en algn momento.

Dado el siguiente programa POSIX, indique cul es la salida que producir por pantalla al ser ejecutado. Justificar la respuesta indicando los pasos que ha seguido la ejecucin del proceso.
#include <sys/types.h> #include <unistd.h> #include <signal.h> void handle_signal(int signo) { if (signo==SIGINT) printf("Se ha recibido una seal SIGINT\n"); else printf("Se ha recibido otra seal\n"); } main() { pid_t val; int status; struct sigaction act; act.sa_handler = handle_signal; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGUSR1, &act, NULL); if (val = fork()) { while(wait(&status)!=val) printf("Espera no finalizada\n"); } else { kill(getppid(),SIGUSR1); sleep(1); exit(0); } }

Ej. 3-10

El programa empieza cambiando el tratamiento de la seal SIGUSR1, instalando la funcin handle_signal() como manejadora de sta. Posteriormente se genera un proceso hijo, el cual enviar dicha seal a su proceso padre, esperar aproximadamente un segundo y posteriormente terminar. El proceso padre entra en un bucle de llamadas a la funcin wait() que concluir cuando el proceso hijo haya terminado. En dicho bucle, se escribir el mensaje Espera no finalizada si wait() ha fallado por algn motivo. De esta manera, cuando el proceso hijo enva la seal a su padre, ste es probable que se encuentre ya suspendido esperando la finalizacin del hijo. De ser as, la llamada wait() ser interrumpida y pasar a ejecutarse el manejador. El manejador escribir el mensaje Se ha recibido otra seal. Como consecuencia de la interrupcin de la espera, wait() termina con error, por lo que el proceso padre tambin escribe Espera no finalizada. En la siguiente iteracin de ese bucle de espera, el proceso padre volver a quedar suspendido, hasta que el hijo concluya tambin su pausa de 1 segundo y termine. Esta segunda vez, el proceso padre ya no escribir nada, pues el valor devuelto por wait() habr coincidido con el PID de su proceso hijo. Otra opcin es que, debido al algoritmo de planificacin que utilice el ncleo del sistema operativo, al realizar el fork(), el proceso hijo expulse al padre (porque terminara su quantum o por ser el hijo ms prioritario y emplear prioridades expulsivas, por ejemplo), enve la seal SIGUSR1 y el padre todava no se haya suspendido en el wait(). Si ocurriera as, el padre slo escribira Se ha recibido otra seal. Posteriormente llegara al wait(), se suspendera y slo saldra de este estado cuando el hijo terminara. Por tanto, los mensajes presentados sern: Se ha recibido otra seal. Espera no finalizada. O bien slo el primero de estos dos mensajes.

Dado el siguiente cdigo:


char buffer[80] = ""; char buffer2[80] = NADA; struct sigaction act; void manejador(int signo) { int fd; fd=open("f", O_WRONLY | O_CREAT, 0666); dup2(fd,1); strcpy(buffer, "Error"); fprintf(stderr,"Resultado: %s\n", buffer); } main() { int i=0; int n=0; act.sa_handler = manejador; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGALRM, &act, NULL); alarm(15); for (i=0; (n=read(0,&buffer2[i],1)) >0 && buffer2[i] != '\n' && i<80 ; i++); if (n>0) buffer2[i+1]='\0'; printf("La cadena leida es: %s\n", buffer2); }

Indicar cul ser el resultado de ejecutar el programa correspondiente si ste no recibe nada en su entrada estndar durante 15 segundos. Describa la secuencia de ejecucin del programa, detallando el efecto de las llamadas a sistema, si el proceso acaba, el motivo de la posible terminacin (terminacin normal / anormal) y todas las salidas de texto que proporciona.

Ej. 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. se produce SIGALRM Se ejecuta el manejador de SIGALRM que redirige la salida estndar al fichero f El manejador escribe Resultado: Error en la salida estndar de error. La llamada read retorna con un error (EINTR) Se escribe en el fichero f: "La cadena leda es: Error

Dado el siguiente ejemplo en POSIX


#include <unistd.h> #include <signal.h> int a = 16, x = 3 ; void manejador(int senyal ) { a = a - 2; } int main( void ) { struct sigaction act; act.sa_handler = manejador; sigemptyset(&act.sa_mask ); act.sa_flags = 0; sigaction(SIGQUIT, &act, NULL ); while ( a > 0 ) a = a + x - 3; return 0; }

Cuntas seales SIGQUIT habra que enviarle al proceso resultante, tras haber ejecutado el sigaction() para asegurar que terminase? Puede haber condiciones de carrera?

Ej. 3-12

Como mnimo 8, pues con cada seal, el valor de la variable global a se reduce dos unidades y el proceso empezaba con "a" igual a 16. Sin embargo, con 8 no tenemos una garanta total de que la variable "a" haya llegado al valor cero, permitiendo la salida del bucle "while" contenido en la funcin principal. Esto se debe a que pueden darse condiciones de carrera, pues tanto esa funcin principal como la funcin manejadora de la seal asignan valores a la variable "a". Si la llegada de la seal se produjera en un momento inoportuno podra ocurrir que el decremento dado en esa ejecucin de la funcin manejadora se perdiera al retornar a la funcin principal. Para ello bastara con que la interrupcin en la secuencia de la funcin principal se hubiera dado tras pasar el valor de "a" a un registro del procesador, pero antes de dejarlo en la posicin de memoria asociada a dicha variable. Al recuperar el control tras la ejecucin de la funcin manejadora, se perdera el cambio realizado en esa funcin manejadora

Sea el siguiente cdigo:


struct sigaction act; int fd[2]; void manejador(int signo){ close(fd[1]); close(fd[0]); } main(int argc, char *argv[]) { char c; act.sa_handler = manejador; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGINT, &act, NULL); /*sigue el main */ pipe(fd); if (fork()) { /* PADRE */ fprintf(stderr, "Padre: esperando\n"); wait(NULL); fprintf(stderr, "Padre: terminando\n"); } else { /* HIJO */ dup2(fd[1],1); fprintf(stderr, "Hijo: leyendo\n"); read(fd[0],&c,1); fprintf(stderr, "Hijo: escribiendo\n"); while(1) write(1,&c,1); } exit(1); }

Dada la lista de afirmaciones que ms bajo se relaciona, realice una lista ORDENADA con todas las afirmaciones que son ciertas para los procesos PADRE e HIJO. El orden debe corresponderse con el orden de ejecucin. Realice dicha lista en el siguiente supuesto: a) Se ejecuta el programa y, despus de imprimirse Padre: esperando, Hijo: leyendo se teclea <CTRL.-C>
AFIRMACIONES RELATIVAS AL PADRE P1.- El proceso PADRE termina normalmente al ejecutar exit P2.- El proceso PADRE queda suspendido en estado WAITING al ejecutar wait P3.- El proceso PADRE muere por una seal (accin por omisin) P4.- El proceso PADRE pasa del estado WAITING al estado ACTIVO P5.- El proceso PADRE ejecuta wait sin suspenderse P6.- El proceso PADRE maneja la seal SIGPIPE P7.- El proceso PADRE maneja la seal SIGINT P8.- El proceso PADRE ignora la seal SIGINT AFIRMACIONES RELATIVAS AL HIJO H1.- El proceso HIJO termina normalmente al ejecutar exit H2.- El proceso HIJO queda HURFANO ejecutando un bucle infinito (INIT lo adopta) H3.- El proceso HIJO queda suspendido en estado READING al ejecutar read H4.- El proceso HIJO queda suspendido en estado WRITING al ejecutar write

H5.- El proceso HIJO pasa del estado suspendido (READING) al estado ACTIVO H6.- El proceso HIJO provoca la seal SIGPIPE al ejecutar read H7.- El proceso HIJO provoca la seal SIGPIPE al ejecutar write H8.- El proceso HIJO muere por una seal (accin por omisin) H9.- El proceso HIJO maneja la seal SIGPIPE H10.- El proceso HIJO maneja la seal SIGINT H11.- El proceso HIJO ignora la seal SIGINT

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

Ej. 3-13

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

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

Sea el cdigo fuente siguiente:


#include <unistd.h> #include <stdio.h> #include <signal.h> char handmsg1[] = "He capturado una seal\n"; char handmsg2[] = " han transcurrido 15 seg.\n"; struct sigaction act; int signum, signal_received = 0; void handler (int signo) { if (signo!=SIGALRM) write(2,handmsg1, strlen(handmsg1)); else{ signal_received=1; write(2, handmsg2, strlen(handmsg2)); } } main(int argc, char *argv[]) { sigset_t sigset; int i; if (argc != 2) {fprintf (stderr, "Usage: %s signo\n", argv[0]); exit(0);} fprintf(stderr, "Comienzo a trabajar\n"); signum=atoi(argv[1]); act.sa_handler = handler; sigfillset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGALRM, &act, NULL); sigaction(signum, &act, NULL); sigfillset(&sigset); sigdelset(&sigset, signum); sigdelset(&sigset, SIGALRM); fprintf (stderr, "Espera 15 seg. a la seal: %s\n", argv[1]); alarm(15); while(signal_received == 0) sigsuspend(&sigset); fprintf (stderr, "Programa terminado\n"); }

Dicho cdigo se corresponde con un ejecutable denominado senyal. a) Indique qu se imprimira por pantalla si se ejecuta el programa con: $senyal 2 e inmediatamente antes de que dicho proceso ejecute alarm(15) el usuario teclea CTRL-C, CTRL-C, CTRL-C (se teclea tres veces lo mismo) NOTA: tenga el cuenta que CTRL-C es la seal 2.

b) Indique a lo largo del cdigo que seales se enmascara o/y desenmascaran y durante que seccin del mismo es valido ese estado.

Ej. 3-14

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

Dado los ficheros senyal1 y senyal2 que contienen los ejecutables de los cdigos fuentes siguientes:
senyal1.c
#include <stdlib.h> #include <stdio.h> #include <unistd.h> int main (int argc, char *argv[]) { int val_pid; alarm(15); val_pid =fork(); for ( ; ; ); } }

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

Indique de manera justificada cul ser el tiempo aproximado de ejecucin: A) De cada uno de los procesos que se generan cuando es ejecutado senyal1. B) De cada uno de los procesos que se generan cuando es ejecutado senyal2.

Ej. 3-15
}

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

Se pide completar el siguiente cdigo de un programa lector de correo. Este programa comprueba si existe correo nuevo a intervalos de tiempo regulares. El programa principal tiene un bucle en el que se lee por consola este periodo de tiempo en segundos (variable periodo). En el caso en que se introduzca un valor cero para el periodo, el programa termina. Utilizad para ello la seal de alarma (SIG_ALRM). El manejador que se ejecutar al ocurrir la seal, deber invocar a la funcin ComprobarCorreo(). Considere que la funcin ComprobarCorreo() ya est implementada y que retorna el nmero de mensajes que hay en el buzn de correo.
#include <unistd.h> #include <stdio.h> #include <signal.h>

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

Ej. 3-16

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

4. Ejercicios sobre directorios y proteccin.


4.1 Dificultad de los ejercicios
Nivel de dificultad Principiante Medio Avanzado Ejercicios

4.2 Ejercicios
Explique qu relacin puede tener la palabra de proteccin de un fichero con los errores que podra dar la llamada al sistema open(). Para ello, suponga que existe un fichero llamado prueba con la palabra de proteccin rw-r--r--. D un solo ejemplo de apertura de dicho fichero (es decir, la lnea completa de cdigo donde se producira tal apertura) y explique en qu casos fallara y en qu otros no.

Ej. 4-1 Ej. 4-2

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

Utilizando la orden "ls -l" se ha podido averiguar que un determinado directorio tiene 5 enlaces fsicos. En qu parte de la salida de dicha orden aparece tal informacin (es decir, en qu columna)? Qu entradas de directorio podran corresponder a dichos enlaces fsicos? Hay alguna forma de saber si alguna de las entradas listadas tiene algn enlace simblico en otro directorio?

Aparece en la segunda columna, tal como se poda ver en el enunciadote la siguiente cuestin. Las entradas de directorio que corresponderan a estos cinco enlaces son: El nombre de dicho directorio en su directorio padre. a entrada ". " en el propio directorio. a entrada ".. " en cada uno de los tres subdirectorios contenidos en l. No hay ninguna forma de saber si alguno de los ficheros listados tiene un enlace simblico ubicado en algn otro directorio y que haga referencia a l.

Dada la salida proporcionada por la orden ls li, donde la primera columna hace referencia al nodo-i de cada fichero y la tercera columna al nmero de enlaces fsicos de dicho nodo-i. Indique de forma justificada qu tipo de enlace son fich1, fich2, fich3 y fich4, sabiendo que han sido creados en ese orden.
916038 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. 4-3

fich1: enlace fsico (i-nodo 916038) fich2: enlace simblico a fich1 (nuevo i-nodo 916040) fich3: enlace fsico (i-nodo 916038) fich4: enlace fsico 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 . .. nuevo fich1 fich3

Donde el programa "nuevo" copia el fichero recibido como primer argumento, dndole a la copia el nombre recibido como segundo argumento. Explique si las siguientes rdenes podrn completarse con xito o no asumiendo que todas ellas se ejecutan en el directorio listado anteriormente a) "nuevo fich1 f ich2", con los atributos eUID=jose y eGID=so2, inicialmente. b) "nuevo fich1 f ich3", con los atributos eUID=juan y eGID=grupo3 inicialmente

Ej. 4-4

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

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

Ej. 4-5

c) Como "jse" no es igual a root, ni igual a pedro", ni "grupo3" es igual a so2, tendremos que utilizar la tercera tripleta de "nuevo". All comprobamos que s que podemos ejecutar el programa. Al hacerlo, adoptaremos "pedro" como eUID. Intentaremos leer "fich1" y podremos hacerlo, pues este fichero tiene el derecho de lectura en todas sus tripletas. Posteriormente intentaremos crear el fichero "fich2", para ello hay que examinar la palabra de proteccin del directorio "." y comprobar que el proceso tenga permiso de escritura. Este proceso est en la categora de "otros" y en ella no aparece el permiso que se necesita. Por tanto, esta orden fallar d) Ocurre algo similar a la orden anterior, excepto en lo referente al acceso a "fich3". Ahora bastara con tener permiso de escritura sobre dicho fichero, pues ya exista y no necesitamos crearlo, sino modificarlo. Seguimos estando en la categora de otros, por lo que el derecho de escritura no est presente y esta orden tambin 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 . 21 14:39 .. 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, dndole a la copia el nombre recibido como segundo argumento. Explique detalladamente si las siguientes rdenes podrn completarse con xito o no, asumiendo que todas ellas se ejecutan en el directorio listado anteriormente. a) copia1 fich1 fich2, con los atributos eUID=jose y eGID=so2, inicialmente. b) copia2 fich1 fich3, con los atributos eUID=pedro y eGID=fco, inicialmente.

Ej. 4-6

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

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

-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 aade al final del fichero que se le pasa como argumento una lnea con el nombre del usuario efectivo que ha ejecutado viatge. Indica cual ser el contenido de cada uno de los ficheros Londres, Roma y Viena despus de ejecutar la siguiente secuencia de rdenes: Usuario Orden ------ ----------------Albert viatge Viena Blanca viatge Londres Albert viatge Londres Albert chmod u+w Viena Albert viatge Viena Carles viatge Viena Carles viatge Roma Albert viatge Roma Albert chmod u+s viatge Blanca viatge Viena Blanca viatge Londres Carles viatge Roma

Ej. 4-7

Londres: blanca
Roma: Viena: carles albert albert albert albert

Razone si un proceso puede modificar sus UID y GID efectivos durante su ejecucin. Si es imposible, indique por qu. Si fuera posible, describa cmo podra conseguir hacerlo.

Ej. 4-8

S que puede hacerse. Existen dos formas. La primera slo podrn utilizarla aquellos procesos cuyo UID efectivo es 0 (root). Para ello, basta con utilizar las llamadas seteuid() y setegid(). Estas llamadas fallaran si el proceso no se ejecuta previamente con la identidad indicada (eUID=0) y se intentara instalar como UID efectivo un valor distinto al UID real del proceso. La segunda opcin es ejecutar un programa que tenga en su palabra de proteccin el bit SETUID o el bit SETGID activos. Para ello, el proceso ha de tener derechos de ejecucin sobre el programa correspondiente y su UID efectivo o GID efectivo pasaran a ser el del propietario o grupo del fichero, segn el bit que estuviera activo.

Suponiendo que el fichero dat.dat no existe en el directorio actual, indique con qu permisos se crea dicho fichero al ejecutar el siguiente cdigo:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> main() {

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

Ej. 4-9 Ej. 4-10 Ej. 4-11 Ej. 4-12

Se crear slo con permiso S_IWUSR, es decir, permiso de escritura para el propietario. Esto se debe a que los otros dos permisos solicitados (escritura para grupo y resto de usuarios) tambin estn presentes en la mscara de creacin de nuevos ficheros. Por ello, dichos permisos no se conceden al crear un nuevo fichero, pues cualquier derecho presente en dicha mscara se elimina a la hora de que el proceso genere nuevos ficheros.

Explique qu representan los nmeros mayor y menor en un fichero especial Cmo podemos averiguar sus valores para un fichero determinado?
El nmero mayor representa el tipo de dispositivo y de alguna forma permite que el sistema operativo identifique qu manejador tendr que tratar las operaciones dirigidas a tal dispositivo. El nmero menor representa el nmero de unidad dentro del tipo especificado por el nmero mayor. Podemos ver sus valores realizando un "ls -l" sobre el directorio "/dev". En la salida proporcionada por dicha orden, la 5a columna nos dara el nmero mayor, mientras que la 6a proporcionara el nmero menor

Explique al menos una forma de obtener el tamao de un fichero. Para ello, indique qu llamada al sistema tendr que utilizarse y cmo (con qu combinacin de argumentos o en qu campo) podr obtenerse ese tamao Una primera opcin es utilizar la siguiente linea:
tamao = lseek(fd,0,SEEK_END);

Con ella obtendramos el valor del puntero de posicin resultante de mover ste en la posicin final del fichero. Por ello, estaramos obteniendo el tamao del fichero. Ntese que el primer argumento de esta llamada es un descriptor de fichero obtenido previamente al abrir el fichero en cuestin. Una segunda opcin es utilizar la llamada stat(), pasndole el nombre del fichero como primer parmetro y la direccin de una estructura "stat" como segundo. Posteriormente, el campo "st_size" de la estructura nos dara el tamao (en bytes) del fichero.

Explique en qu consiste la operacin de montaje de un dispositivo en un sistema UNIX. Describa sus efectos en el grafo de directorios utilizado por el sistema. Escriba un ejemplo de lnea de rdenes en la que se realice una operacin de montaje. La operacin de montaje implica asociar el contenido (el grafo de directorios) de un determinado dispositivo de almacenamiento a un directorio utilizado como punto de montaje. Como resultado, la raz del dispositivo montado quedar asociada al directorio empleado como punto de montaje y, a travs de l, se podr acceder al contenido de dicho dispositivo. Con ello conseguimos que el grafo de directorios en un sistema UNIX sea nico, independientemente del nmero de dispositivos existentes en dicho equipo. Aparte, mientras dure el montaje todo el contenido del directorio empleado como punto de montaje queda temporalmente inaccesible, pues ha sido "sustituido" por lo que hubiese en el dispositivo montado. Ejemplo: mount /dev/fd0 /mnt (Montara el disqueteen el directorion /mnt)

Indique qu efecto tendr realizar una operacin lseek () sobre un tubo. Razone adecuadamente por qu sucede eso.

Ej. 4-13

Si queremos emplear lseek() para desplazar el puntero de posicin, esta llamada fallar pues los tubos slo admiten el acceso secuencial.

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

Ej. 4-14

F F

V V

No existe ninguna funcin POSIX que permita obtener el valor del puntero de posicin asociado a un determinado descriptor de fichero. S que existe una funcin para averiguar esto. Es lseek(). La funcin POSIX a emplear para modificar un directorio se llama writedir(). No existe una funcin que directamente nos permita hacer tal cosa. Las modificaciones de un directorio son resultados colaterales de otras operaciones: crear un fichero, borrar el nombre de un fichero, crear un subdirectorio, borrar un subdirectorio, cambiar el nombre de una entrada etc Es posible que al ejecutar la llamada open(nuevo,O_WRONLY|O_CREAT|O_TRUNC,0666) se cree un ficero con la palabra de proteccin "rw-r----- S que resulta posible. Aunque con la llamada especificada se estara solicitando la creacin con una palabra de proteccin "rw-rw-rw-", sera posible obtener la que indicaba el enunciado si tuviramos la mscara de creacin de ficheros adecuada. Por ejemplo, la 026 La funcin open () no debe ser utilizada para abrir un directorio. Cierto Para abrir un directorio necesitamos la funcin opendir() Un fichero de tipo directorio tiene como mnimo dos enlaces fsicos que hacen referencia a l. Cierto. Al crear el directorio, ste ya tiene dos enlaces que no podremos eliminar hasta haberlo borrado: su nombre en el directorio padre y la entrada "." en l mismo

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

Ej. 4-15

V 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. Verdadero. Por ejemplo, si queremos saber si el proceso puede escribir sobre un fichero, podemos abrirlo en modo "slo escritura". Si la llamada no falla, eso querr decir que el acceso est permitido. Si fallara indicara que no tenemos ese permiso. La funcin POSIX a emplear para abrir un directorio se llama opendir() Verdadero. No podemos utilizar la llamada open(), pues fallara. Es posible que al ejecutar la llamada open(nuevo,O_WRONLY|O_CREAT|O_TRUNC,0666) se cree un fichero con la palabra de proteccin "rw-r-x---". Falso. En el tercer argumento de open() no hemos especificado el permiso de ejecucin para el grupo, que si aparece en la palabra de proteccin que daba el enunciado. Es imposible que el sistema "aada" algn derecho a lo que pida el proceso creador. El valor de la mscara de creacin de ficheros justificara el hecho de que no aparezcan los derechos de escritura ni en el grupo ni en el campo del resto de usuarios, as como la ausencia del derecho de lectura en esa ltima tripleta. La funcin close () no debe ser utilizada para cerrar un directorio. Verdadero, debe usarse closedir() Un fichero de tipo especial (de bloques o de caracteres) tiene como mnimo dos enlaces fsicos que hacen referencia a l. Falso. El nmero mnimo de enlaces para los ficheros especiales es uno.

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), marta (perteneciente al grupo grupo3) y juan (perteneciente al grupo grupo2). Indique, de entre los tres usuarios citados, quienes podrn utilizar el programa miprog, y con l, procesar la informacin existente en los ficheros datos y punt2 (para ello, nicamente se necesita leer el contenido de ambos ficheros).

Ej. 4-16

Grupo 1, no puede ejecutar miprog Grupo 2 y 3 pueden ejecutar miprog y leer en ejecucin los ficheros datos y punt2.

5. Ejercicios sobre hilos y seccin crtica


5.1 Dificultad de los ejercicios
Nivel de dificultad Principiante Medio Avanzado Ejercicios

5.2 Ejercicios
Conteste de forma concreta y concisa a las siguientes preguntas: a) Qu es un programa concurrente? b) Qu es una condicin de carrera? c) Qu es una seccin critica? d) Qu condiciones deben cumplir los protocolos de las secciones crticas?

Ej. 5-1

a) Es un nico programa constituido por varias actividades concurrentes que pueden ejecutarse de forma independiente. Dichas actividades trabajan en comn para resolver un problema y se coordinan y comunican entre s. b) Una condicin de carrera ocurre cuando un cdigo que ejecutado secuencialmente es correcto, deja de serlo al ejecutarlo concurrentemente. Una condicin de carrera se produce cuando la ejecucin de un conjunto de operaciones concurrentes sobre una variable compartida deja a la variable en un estado inconsistente con las especificaciones de correccin. Adems el resultado de la variable depende de la velocidad relativa en que se ejecuten las operaciones. c) Son las zonas de cdigo en las que se acceden para lectura o escritura a las variables compartidas. Y por tanto zonas de cdigo que hay que sincronizar su ejecucin d) El protocolo tiene que cumplir tres condiciones: Exclusin mutua, progreso y espera limitada.

Sabiendo que la llamada necesaria para crear un hilo es:


int pthread_create(pthreadt *al, pthreadattrt *a2, void *(*a3)(void *), void *a4);

Escribir el cdigo necesario para crear un hilo cuya funcin principal se llamar "sumador" y cuya misin ser aplicar una determinada funcin matemtica a los cinco nmeros enteros que se le suministrarn como argumento. Declare las estructuras de datos necesarias para ello y muestre cmo quedara la lnea empleada para la creacin.

Ej. 5-2 Ej. 5-3

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

Cite al menos tres atributos que podramos encontrar en el "bloque de control de hilo". Slo podremos encontrar atributos relacionados con la identidad del hilo, con la gestin de sus recursos (apenas tiene recursos, pues comparte los asignados al proceso) y con su planificacin Ejemplos de estos atributos pueden ser: identificador del hilo, identificador del proceso en el que se encuentra el hilo, estado de planificacin, mapa de memoria referente a su pila, prioridad de planificacin (en caso de utilizar un algoritmo basado en prioridades)

Indique las cadenas que imprime el programa en la Terminal tras su ejecucin. Justifique su respuesta. Nota: retraso(n) realiza un retraso de n milisegundos
void * funcion_hilo1(void * arg) { retraso(4000+rand()%1000); printf(Hola Don Pepito\n); return null; } int main (void) { pthread_t th1,th2; pthread_attr_t atrib; pthread_attr_init( &atrib ); printf(Eran dos tipos requeeeteeefinos...\n); pthread_create( &th1, &atrib, function_hilo1, null); pthread_create( &th2, &atrib, function_hilo2, null); exit(0); } void * funcion_hilo2(void * arg) { retraso(4000+rand()%1000); printf(Hola Don Jose\n); return null; }

Ej. 5-4

Imprimir nicamente: Eran dos tipos requeeeteeefinos... El motivo es que tras la creacin de los dos hilos, el hilo principal (main) termina de inmediato (exit) y provoca la terminacin de todos los hilos. Como los hilos tienen un retraso importante antes de realizar sus respectivas sentencias impresin, no tendrn tiempo a imprimir nada (a menos que el algoritmo de planificacin retrase anormalmente la ejecucin 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 tambin son eliminados al realizar el exit().

Qu imprime por la salida estndar 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 operacin pthread_mutex_lock(&mutex) de la funcin 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 funcin copy_file. Esta funcin 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 funcin copy_file falla. Indique qu modificaciones habra que hacer para que la funcin main mostrara un mensaje de error cuando se produjese esta situacin. Este mensaje debe incluir el valor de la variable errno producido en la ejecucin del hilo. NOTA: Tngase 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, debera devolver el puntero a una estructura que contuviera tanto los bytes escritos como el valor de errno del hilo.

El siguiente pseudocdigo ilustra la manera en que se quiere proteger la Seccin Crtica de los procesos 1 y 2 que comparten algunas variables entre ellas la variable llave inicializada a 0.
Lnea Pseudocdigo

A B C D E

while (llave); printf(Modificamos llave a 1\n); llave=1; /* Seccin Crtica */ llave=0; /* Seccin restante */

Indique si es posible el siguiente orden de ejecucin 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 lnea de cdigo referenciada como A, A2 indica que el hilo2 ejecuta la lnea referenciada como A, etc.

Ej. 5-7

Si que es posible que se produzca la secuencia indicada.


Esta forma de proteger la seccin crtica no es adecuada ya que pueden producirse escenarios de cambio de contexto que violen la exclusin 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 Seccin Crtica de dos hilos de ejecucin: hilo_0 e hilo_1. Es correcta la solucin propuesta? Nota: Analice la implementacin en trminos de exclusin mutua, progreso y espera limitada.
#include <pthread.h> #include <stdlib.h> #include <stdio.h> void *hilo_1(void *p) { while(1) { while (turno != 1); /* Seccin crtica */ turno = 0; /* Seccin 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); /* Seccin crtica */ turno = 1; /* Seccin restante */ } }

Ej. 5-8

Garantiza la exclusin mutua pero no el progreso, pues los hilos pueden tener diferentes velocidades de ejecucin y solicitar la ejecucin de sus secciones crticas en rdenes no alternativos. En esos casos no se cumplira el requisito de progreso pues se estara asignando de manera esttica la entrada a la seccin crtica a un hilo que no lo ha solicitado y se impedira la entrada al que s lo pidi.

Razone adecuadamente si el siguiente cdigo es una buena solucin al problema de la seccin crtica para dos hilos, con identifcadores (valores de la variable i) 0 y 1, asumiendo que las variables flag[0] flag[1] estn compartidas e inicialmente valen cero.
int turno = 0; /* Compartida */ hilo_i(void) { while ( 1 ) { seccin_restante; flag[i] = 1; while(flag[(i+1) % 2] ) ; seccin_crtica; flag[i] = 0; } } hilo_i(void){ while ( 1 ) { seccin_restante; flag[i] = 1; turno = (i+1)%2; while(flag[(i+1)%2] && (turno==(i + 1) %2) ) ; seccin_crtica; flag[i] = 0; } }

a) Ambos hilos ejecutan el cdigo de la columna izquierda Una buena solucin al problema de la seccin critica debe cumplir exclusin mutua, progreso y espera limitada. Veamos que ocurre en este caso: a) Exclusin mutua: El hilo "i" slo podr estar en su seccin crtica si el flag del otro hilo estaba a valor falso. Es decir, slo si el otro no ha pedido entrar. Por tanto, si uno ejecuta su seccin crtica el otro no poda estar ejecutando su seccin cuando aqul entr. Si despus ha intentado entrar el que estaba fuera, tampoco ha podido lograrlo porque el flag del que ya est dentro lo evita. Por tanto, s que se cumple la exclusin mutua. b) Progreso: Para cumplir la condicin de progreso la decisin sobre qu hilo puede entrar debe considerar como candidatos slo a aqullos que hayan solicitado la entrada y tal decisin debe tomarse en un tiempo finito. Desafortunadamente, ste ltimo "detalle" puede no cumplirse en este ejemplo. Si los dos hilos han conseguido fijar sus flags a cierto (por ejemplo, debido a un cambio de contexto tras haber ejecutado "flag[i]=1;" en el primero que intentaba entrar), el protocolo de entrada utilizado es incapaz de elegir qu hilo debe entrar, bloquendose ambos mutuamente en dicho protocolo. Por tanto, como no se toma la decisin en un tiempo finito, la propiedad de progreso no siempre se cumple y sta no es una buena solucin al problema de la seccin crtica. c) Espera limitada: Para que esta condicin se cumpla, si un hilo llega al protocolo de entrada debe existir una cota sobre el nmero de veces que el otro hilo consiga entrar "adelantndole". Est claro que si un hilo llega al protocolo de entrada, logra poner su flag a cierto. Con ello garantiza que el otro ya no podr entrar. Por tanto, si que existe una cota sobre el nmero de veces que otros hilos pueden adelantar a un candidato, y dicha cota es cero. Por desgracia, aunque esto garantiza la propiedad de espera limitada, tambin puede conducir a violar la propiedad de progreso, como ya hemos visto arriba a) Ambos hilos ejecutan el cdigo de la columna derecha

Ej. 5-9

Esta solucin cumple las tres propiedades, como veremos seguidamente: a) Exclusin mutua: Si un hilo est ejecutando su seccin crtica, seguro que ha puesto su flag a cierto. Para que est ejecutndola, ha tenido que ocurrir algo ms: o bien el otro hilo no haba intentado entrar y tena su flag a falso, o bien haba intentado entrar y le haba cedido el turno. En cualquiera de las dos situaciones, el otro hilo no conseguir entrar. Por tanto, se garantiza la exclusin mutua, pues como mximo puede haber un hilo dentro de la seccin crtica. Progreso: Aqu el progreso tambin se cumple. Si slo desea entrar un hilo l ser el nico que habr fijado su flagy en ese caso, no tendr que quedarse iterando en el bucle while del protocolo de entrada. Si los dos hilos hubieran pedido la entrada, entonces la variable "turno " servir para deshacer el empate y dejar que slo entre uno de ellos. El otro conseguir entrar cuando el que estaba dentro ponga su flag a cero. Si ninguno de los dos est en el protocolo de entrada cuando sale el que ya estaba en la seccin crtica, sta queda disponible para el primero que llegue. Espera limitada: La gestin de la espera limitada es similar a la vista en la cuestin 3. Ahora, debido al uso de la variable turno sera 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 despus y consiguiera fijar tambin su flag a cierto. No obstante, el nmero de veces que podr adelantarlo ser como mximo uno. Por tanto, sigue cumplindose la espera limitada. No obstante aunque se satisfacen las tres propiedades, pueden darse ciertas situaciones con algunos algoritmos de planificacin que conduciran a una situacin de bloqueo. Por ejemplo, si utilizramos planificacin por prioridades expulsivas estticas, teniendo el hilo 0 menor prioridad que el hilo 1 y asumiendo que el hilo 0 fue creado antes que el hilo 1, podramos llegar a la siguiente situacin: 1) El hilo 0 ejecuta toda su seccin restante y tambin la instruccin flan[0]=1. 2) Se crea el hilo 1 y empieza a ejecutarse, como es el ms prioritario no tendr por que parar. Como flag[0] est a 1 y l mismo se ha encargado de poner turno=0, este hilo 1 no consigue superar su protocolo de entrada y queda perpetuamente en su bucle "while" de dicho protocolo. Esta situacin parece violar la propiedad de progreso, pues ninguno de los dos hilos supera su respectivo protocolo de entrada. Sin embargo, no es as. Los protocolos de entrada con su estado obtenido a partir del anterior paso 2), han decidido que quien debe entrar es el hilo 0 . Si no lo hace es porque el algoritmo de planificacin que hemos utilizado es INACEPTABLE (Ya se dijo en SOI que un algoritmo de ese estilo es bastante peligroso pues poda producir inanicin para los procesos menos prioritarios). En las clases de teora hemos visto que una buena solucin al problema de la seccin crtica deba cumplir exclusin mutua, progreso y espera limitada, pero siempre que se respetaran dos restricciones adicionales: que los procesos o hilos avanzaran todos ellos a velocidad no nula y que nuestra solucin no dependiera de la velocidad relativa de tales procesos o hilos. Aqu se est violando la primera de esas restricciones, pues el hilo menos prioritario jams podr avanzar. Es decir, un algoritmo como el explicado no lo podemos utilizar como base para fijarnos en el cumplimiento de LAS TRES propiedades. Podra servirnos para demostrar que la espera limitada no se cumple, pero jams para demostrar que el progreso no es viable.

Sea un proceso con un conjunto de hilos como se muestra en el cdigo siguiente de manera esquemtica. Las funciones seccion_critica_i() hacen referencia a un conjunto de instrucciones que deben ejecutarse en exclusin mutua por el hilo H[i]. a) Indique las caractersticas del protocolo de entrada. Analice los inconvenientes y/o ventajas que presenta este tipo de protocolo teniendo en cuenta tanto el tiempo de ejecucin de los procesos como los diferentes algoritmos de planificacin de procesos del Sistema Operativo. b) Indique de forma justificada la validez o no de este protocolo como solucin al problema de la seccin critica. NOTA: Suponga que tanto las operaciones de incremento y decremento como las de comparacin son atmicas.
#include <unistd.h> #include <stdio.h> #define N 5 int S[N];

Ej. 5-10

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

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

for (i=0; i<N; i++)

S[i]=0;

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

//PROTOCOLO SALIDA SECC. CRITICA if (id>0) { S[id]=S[id-1];} if (id==0) { S[0]=(S[0]+1)mod 2;} pthread_exit(0); }

Ej. 5-11

A) Se trata de un espera activa con todos los inconvenientes y ventajas de la misma. La espera activa presenta problemas de infrautilizacin del procesador, tanto en sistemas de planificacin por prioridades como en los de turno rotatorio.

B) Hay un derecho rotativo a entrar en la seccin crtica. El proceso que entra en la seccin crtica es el que tiene su variable S distinta del proceso que le precede, salvo para el hilo H[0] que entra en seccin critica cuando su variable S coincide con la del proceso que le precede. Inicialmente todas las S estn inicializadas a cero y el nico que puede entrar en la seccin critica es el H[0] que al salir pone S[0]=1 y de esta forma slo puede entrar en la seccin crticas H[1], y as sucesivamente. En general, las soluciones de turno rotatorio no cumplen con la condicin de progreso del problema de la seccin crtica.

Dado el siguiente cdigo de un programa POSIX


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

Si tanto f1 como f2 van a utilizarse dentro de la funcin main() como funciones principales de los hilos de ejecucin que se crean en este programa, marque dentro del cdigo presentado arriba qu regiones (es decir, grupos de una o ms lneas consecutivas) deberan considerarse secciones crticas dentro de cada una de esas dos funciones.

SOLUCIN: Se han marcado las lneas de las secciones crticas en negrita, itlica y subrayado. En el hilo f1() se realiza una asignacin del argumento recibido sobre la variable compartida "a" y posteriormente en su bucle se incrementa esa misma variable. En el hilo f2 se utiliza el puntero "c" para acceder a la variable global "a". Todas las instrucciones que posteriormente utilicen "c" para leer o modificar el contenido de "a " deben considerarse secciones crticas.

Razone adecuadamente si el siguiente cdigo es una buena solucin al problema de la seccin crtica para dos hilos, con identifcadores (valores de la variable i) 0 y 1, asumiendo que las variables flag[0] flag[1] estn compartidas.
int turno = 0; /* Compartida */ hilo_i(void) { while ( 1 ) { /* Seccin restante */ while(flag[(i+1) % 2] ) ; flag[i] = 1; /* Seccin crtica */ flag[i] = 0; } } hilo_i(void){ while ( 1 ) { /* Seccin restante */ flag[i] = 1; turno = (i+1)%2; if (flag[(i + 1)%2]) while(turno==(i + 1)%2) ; /* Seccin crtica */ flag[i] = 0; } }

a) Ambos hilos ejecutan el cdigo de la columna izquierda No es una buena solucin pues no siempre garantiza la exclusin mutua. Por ejemplo, si los dos hilos han superado sus respectivos bucles while() pero han sido expulsados del procesador antes de poner sus flags a 1 ambos podran llegar a entrar violando la exclusin mutua. S que cumple progreso, pues si un hilo estaba ejecutando su seccin y el otro quera entrar al abandonar la seccin se estar borrando el flag, permitiendo que el otro hilo pueda entrar si logra obtener la CPU. Adems, si slo hay un hilo que quiere entrar puede hacerlo sin problemas. No se cumple tampoco la espera limitada, pues el cdigo presentado no impone ninguna cota al nmero de veces que un hilo puede entrar mientras el otro est esperando en su protocolo de entrada Esto slo depender del planificador utilizado. a) Ambos hilos ejecutan el cdigo de la columna derecha Esta solucin cumple la exclusin mutua y la espera limitada. La exclusin mutua se cumple porque si ambos intentan entrar, pondrn los dos flags a cierto, pero slo uno de ellos conseguir entrar pues la variable "turno " slo permitir la entrada del primero que haba llegado al protocolo de entrada. Adems, la construccin de este protocolo de entrada tambin evita que un hilo entre en la seccin crtica si el otro ya estaba en ella. La espera limitada se cumple puesto que el uso de la variable "turno " evita que, cuando los dos hilos pretenden entrar, uno pueda adelantar al otro ms de una vez Por el contrario, la propiedad de progreso no se cumple, pues hay algunos casos en los que la seccin crtica queda libre y el hilo que pretende entrar no puede hacerlo debido a que la variable "turno " se lo impide. Por ejemplo, si el hilo 0 estaba en la seccin crtica y entonces el hilo 1 llega a su protocolo de entrada, tendremos ambos flags a 1 y la variable "turno" al valor 0. Cuando el hilo 0 sale de la seccin crtica pone flag[0] a O, pero esto no abre el paso del hilo 1. Por ello, no se est respetando la propiedad de progreso pues el nico candidato a la entrada en la seccin crtica no conseguir entrar.

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

Ej. 5-14

Ej. 5-13

Ej. 5-12

Ej. 5-15

V F

Mediante la funcin pthread_self() se puede obtener el identificador de un hilo. Cierto Esta funcin es la que debe utilizarse para obtener el identificador. El cdigo visto en la cuestin 2 utiliza espera activa. Falso. En el cdigo visto en la cuestin 2 no existan protocolos de entrada o protocolos de salida para acceder a las secciones crticas pues stas no estaban protegidas Por ello, no podemos hablar de que haya o no espera activa pues este problema slo puede aparecer en los protocolos de entrada Si los hilos estn soportados por el sistema operativo, siempre cuesta ms un cambio de contexto entre procesos que entre hilos. Falso. Cuando los hilos pertenezcan a procesos diferentes el cambio de contexto costar igual o ms que al dar un cambio de contexto entre procesos, en un sistema sin soporte a hilos. Esto se debe a que la informacin del contexto ahora est distribuida entre el PCB y el bloque de control de hilo. En los programas escritos para ejecutarse a nivel de usuario, resulta recomendable utilizar la inhabilitacin/habilitacin de interrupciones para solucionar el problema de la seccin crtica. Falso. Aunque podra ser una solucin vlida al problema de la seccin crtica, no es viable a nivel de usuario, puesto que un uso inadecuado de las operaciones necesarias para esta gestin de las interrupciones podra dejar "coleado " al equipo Si el soporte a hilos se encuentra en el ncleo, al suspenderse uno de los hilos, los dems podrn seguir ejecutndose. Cierto. Si los hilos estn soportados por el ncleo, el planificador de ste puede gestionar de manera independiente a cada uno de ellos. Por ello, si se suspende alguno, el resto mantendrn el estado que previamente tuvieran. Si alguno de ellos estuviera preparado podra entonces pasar a ejecutarse.

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

Ej. 5-16

V V

F F

Mediante la funcin pthreadjoin() se puede obtener el valor retornado por la funcin principal de un hilo. El segundo argumento de esta funcin sirve para obtener dicho valor. Las soluciones vistas en las cuestiones 3 y 4 utilizan espera activa Cierto, pues en los protocolos de entrada, los hilos que no puedan acceder a la seccin crtica estarn ejecutando un bucle vaco, malgastando intilmente el tiempo de CPU que les otorgue el planificador. Utilizando soluciones al problema de la seccin crtica con apoyo del hardware (por ejemplo con la instruccin testandset) se puede eliminar la espera activa Utilizando slo esos apoyos del hardware, la espera activa no podr eliminarse. Para eliminarla por completo necesitaramos que el proceso o hilo que est en el protocolo de entrada permaneciera ah en estado suspendido hasta que realmente pueda acceder a la seccin crtica. Para ello necesitamos algn mecanismo facilitado por el sistema operativo. Sern los semforos, que estudiaremos en el tema 8 Todos los programas que utilizan mltiples hilos de ejecucin presentan condiciones de carrera No tienen por qu. Podramos escribir programas multihilo que sincronizaran sus accesos sobre las variables compartidas que pudiera haber. 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 dems podrn seguir ejecutndose Si el soporte se da a nivel de usuario slo existe una unidad de ejecucin dentro del proceso gestionada por el sistema operativo. Si alguno de los hilos de nivel de usuario utiliza alguna llamada que suspende a dicha unidad de ejecucin, no quedan ya otras unidades disponibles para ejecutar al resto de los hilos de nivel de usuario. A consecuencia de esto, todo el proceso ha quedado suspendido y ninguno de los otros hilos podr continuar.

Indique si las siguientes afirmaciones son verdaderas (V) o falsas (F). El cambio de contexto entre hilos de ejecucin soportados a nivel de usuario es ms rpido que el de hilos soportados a nivel de ncleo. El responsable de la planificacin de hilos, tanto sin son creados a nivel de usuario como de ncleo, es el planificador del sistema. Siempre que un hilo ejecuta la llamada pthread_exit(), el hilo finaliza voluntariamente su ejecucin y se libera toda la memoria correspondiente al proceso pesado que soporta al hilo. Los hilos de ejecucin creados a nivel de usuario pueden compartir datos, pero no cdigo, mientras que los hilos soportados a nivel de ncleo pueden comparten datos y cdigo. Un hilo de ejecucin (o proceso ligero) se puede definir como una unidad bsica de utilizacin de CPU con su propio contador de programa, juego de registros y su propia pila . Una llamada al sistema bloqueante (read(), write()) por parte de un hilo, implica que todos los hilos pertenecientes al mismo proceso se bloqueen, con independencia de si han sido creados en modo ncleo o usuario.

Ej. 5-17

V F F F V F

Se desea desarrollar una aplicacin que ponga en funcionamiento dos tareas que se deben ejecutar en orden. Los cdigos 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 aplicacin anterior utilizando dos modelos distintos: un programa que crea procesos para ejecutar cada una de las tareas y un programa que realiza las tareas utilizando procesos ligeros (hilos posix). En cualquiera de los dos casos la aplicacin debe terminar cuando las tareas hayan acabado. La aplicacin se debe programar de forma que no se usen ms de dos procesos ligeros ni ms de dos procesos pesados segn el caso.

Ej. 5-18

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

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

6. Ejercicios sobre semforos y monitores


6.1 Dificultad de los ejercicios
Nivel de dificultad Principiante Medio Avanzado Ejercicios

6.2 Ejercicios
Observe el siguiente fragmento de cdigo correspondiente a dos hilos que pertenecen al mismo proceso y se ejecutan concurrentemente. Indique qu recursos compartidos aparecen en el cdigo y qu mecanismos se emplean para evitar las condiciones de carrera. Nota: Las variables y funciones que no estn definidas dentro de las funciones agrega y resta son definidas como globales.
Void *agrega (void *argumento) { int ct,tmp; for (ct=0;ct<REPE;ct++) { while(test_and_set(&llave)==1); tmp=V; tmp++; V=tmp; llave=0; } printf("->AGREGA (V=%ld)\n",V); pthread_exit(0); } void *resta (void *argumento) { int ct,tmp; for (ct=0;ct<REPE;ct++) { while(test_and_set(&llave)==1); tmp=V; tmp--; V=tmp; llave=0; } printf("->RESTA (V=%ld)\n", V); pthread_exit(0); }

Recursos compartidos: variables llave y V. La variable llave, al ser accedida nicamente mediante operaciones atmicas (test_and_set y asignacin, que pueden ser completadas en una sola instruccin mquina y, por tanto, son indivisibles) no plantea ningn problema de acceso concurrente. Por otra parte, la variable V est protegida por un protocolo de entrada y otro de salida, implantados con la variable llave mencionada en el prrafo anterior.

La llamada al sistema operativo yield() permite a un proceso ceder lo que le queda de su cuanto de tiempo a cualquier otro que est en la cola de PREPARADOS. De esta forma, el proceso que la invoca (que permanece en estado PREPARADO) libera la CPU, y da al planificador la oportunidad de seleccionar otro proceso para su ejecucin. a) Cul podra ser su aplicacin para mejorar el comportamiento de la solucin a la seccin crtica basada en la instruccin test_and_set? b) Implemntese los protocolos de entrada y salida a seccin crtica, utilizando las funciones test_and_set y yield(). sese como base la solucin basada en test_and_set con espera activa.

Ej. 6-1

Ej. 6-2

a) reducir el tiempo desperdiciado en la espera activa. b) int llave=0; //sr while(test_and_set(&llave)) yield(); //sc llave=false; //sr

Suponiendo que la variable global llave tiene valor inicial 0 y que la funcin test_and_set est definida correctamente, comenta las diferencias entre las siguientes dos soluciones al problema de la seccin crtica atendiendo al tiempo de ejecucin observado en cada una de ellas. Nota: la funcin usleep() suspende al hilo que la invoca una cantidad de microsegundos.
/* Solucin a */ void *hilo(void *p) { while(1) { while (test_and_set(&llave)); /* Seccin crtica */ llave = 0; /* Seccin restante */ } } /* Solucin b */ void *hilo(void *p) { while(1) { while (test_and_set(&llave)) usleep(1000); /* Seccin crtica */ llave = 0; /* Seccin restante */ } }

Ej. 6-3

En la solucin a el hilo que espera a que se libere la seccin crtica ocupada consume todo su cuanto de tiempo comprobando la variable llave que no cambiar de valor en ese tiempo ya que la CPU permanecer ocupada. Podemos considerar que este tiempo ha sido malgastado en una espera activa. En la solucin b el hilo que espera a que la seccin crtica quede libre, abandona la CPU voluntariamente nada ms comprobar que est ocupada. No consume todo su cuanto de tiempo comprobando la variable llave y por lo tanto es ms eficiente. La solucin a puede aplicarse en cualquier mbito de programacin ya que la nica instruccin necesaria es test_and_set que es una instruccin mquina. La solucin b puede aplicar slo cuando programamos sobre un sistema operativo ya que usleep se apoya en una llamada a sistema y en el mecanismo de suspensin de procesos del SO.

En relacin a la utilizacin de semforos segn la definicin de Dijkstra, dar un ejemplo de qu problema podra resolver en funcin de su valor de inicializacin. a) inicializado a 0 b) inicializado a 1 c) inicializado a un valor positivo mayor que 1

Ej. 6-4 Ej. 6-5

a) establecer un orden de precedencia en la ejecucin de zonas de cdigo de distintos hilos. b) Resolver la exclusin mutua c) Controlar el acceso a un recurso con instancias mltiples

Los semforos no slo sirven para resolver el problema de la seccin crtica. Enumera al menos otras dos aplicaciones de los semforos y ponga un ejemplo simple de cada una de estas aplicaciones:

Pueden servir para sincronizar procesos en general, por ejemplo: (1) Establecer un cierto orden (precedencia) en la ejecucin de zonas de cdigo de diferentes procesos/hilos Supongamos dos hilos, hilo1 e hilo2.Queremos que hilo1 ejecute la funcin F1 antes que hilo2 ejecute la funcin F2; Definimos un semforo compartido sinc, inicializado a cero
void *hilo1(void *p) { ... F1; V(sinc); ... } void *hilo2(void *p) { ... P(sinc); F2; ... }

(2) Establecer que un mximo nmero de procesos/hilos pueden pasar por un cierto punto del cdigo Tenemos un cdigo que ejecutan concurrentemente muchos hilos Queremos que, como mucho, 5 hilos pasen por un determinado lugar del cdigo, donde se llama a la funcin F Definimos un semforo compartido max_5, inicializado a cinco
void *hilo_i(void *p) { ... P(max_5); F; V(max_5); ... }

Explique por qu razn no se puede inicializar un semforo a un valor negativo.

Ej. 6-6

No se puede inicializar a dicho valor porque estara indicando que el semforo tiene un hilo suspendido en su cola, cuando eso no es cierto. El problema lo encontraramos despus si algn hilo o proceso intenta realizar una V antes de que ningn otro intente una P. Si eso ocurriera el sistema no sabra como responder a tal operacin V, pues buscara en la cola de hilos suspendidos en el semforo y no encontrara nada.

Indique para cada una de las siguientes afirmaciones si es verdadera (V) o falsa (F). F F V F Un proceso puede suspenderse al realizar una operacin V sobre un semforo. Un proceso siempre se suspende al realizar una operacin P sobre un semforo. Un proceso siempre se suspende al realizar una operacin espera (wait) sobre una variable condicin. Un proceso siempre se suspende al realizar una operacin seal (signal) sobre una variable condicin. Un proceso siempre despierta a otro proceso al realizar una operacin V sobre un semforo. Un proceso siempre despierta a otro proceso al realizar una operacin seal (signal) sobre una variable condicin.

Ej. 6-7 Ej. 6-8

F F

Justifique de forma razonada que las operaciones P(S) y V(S) se han de ejecutar de forma atmica para que los semforos funcionen correctamente.

Las operacione P y V de una semforo 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 crticas con un semforo S compartido e inicializado a 1 como sigue: void Proc2() void Proc1() { { P(S); P(S); Seccin critica de Proc2(); Seccin critica de Proc1(); V(S); V(S); } } Si llega Proc1 y ejecuta S=S-1 y hay un cambio de contexto y luego llega Proc2 y ejecuta S=S-1 ambos podrn entrar en sus secciones crticas y no se cumplir el requisito de exclusin mutua.

Dado el siguiente cdigo, utilice semforos (con la notacin propuesta inicialmente por Dijkstra) para garantizar que los diferentes hilos ejecuten las funciones cuyo nombre empieza por secuencia- segn el orden que sugieren los nmeros empleados en tales nombres. Si hay varias funciones con el mismo nombre, ninguna de ellas debe empezar antes de que termine la funcin con el nombre anterior y todas ellas deben haber terminado antes de que empiece la funcin con el nombre siguiente. Adems, ha de asegurarse que las funciones sc() se ejecuten en exclusin mutua. Indique qu valor inicial debern tener los semforos que haya utilizado.
Valores iniciales de los semforos: S1(1), S2(0), S3(0). HILO 1 P(S1); sc(); V(S1); P(S2); secuencia2(); V(S3); P(S1); sc(); V(S1);

Ej. 6-9

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

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

Dado el siguiente cdigo, utilice semforos (con la notacin propuesta inicialmente por Dijkstra) para garantizar que los diferentes hilos ejecuten las funciones cuyo nombre empieza por secuencia- segn el orden que sugieren los nmeros empleados en tales nombres. Si hay varias funciones con el mismo nombre, ninguna de ellas debe empezar antes de que termine la funcin con el nombre anterior y todas ellas deben haber terminado antes de que empiece la funcin con el nombre siguiente. Adems, ha de asegurarse que las funciones sc() se ejecuten en exclusin mutua. Indique qu valor inicial debern tener los semforos que haya utilizado.
Valores iniciales de los semforos: S1(1), S2(0), S3(0) HILO 1 HILO 2 P(S1); P(S1); sc(); sc(); V(S1); V(S1); Secuencia1(); P(S2); V(S2); P(S1); secuencia2(); sc(); V(S3); V(S3); V(S1); P(S3); P(S1); secuencia3(); sc(); V(S2); V(S1);

Ej. 6-10

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

Dado el siguiente cdigo, y suponiendo que todos los semforos tenan valor inicial cero, que los hilos presentados estn soportados por el ncleo del sistema operativo, se encontraban en la cola de preparados en el orden H1, H2, H3 (es decir, H1 ser el primero en obtener la CPU y H3 el ltimo) y que se utiliza un algoritmo de planificacin FCFS:

H1 P(S1); P(S3);

H2 P(S2); V(S4);

H3 V(S2); V(S1);

V(S1); V(S3);

V(S3); P(S3);

P(S1); P(S4);

Indique el orden de terminacin de dichos hilos. En caso de que algn hilo no termine, indique en qu operacin se ha quedado suspendido.

Ej. 6-11

Se siguen estos pasos en la ejecucin del cdigo: H1 realiza un P(S1) y se queda suspendido, pues S1=-1. H2 realiza un P(S2) y tambin queda suspendido, pues S2=-1. H3 realiza un V(S2), despertando a H2 que quedar el 1 en la cola de preparados. H3 realiza un V(S1), despertando a H1 que quedar el 2 en la cola de preparados. H3 realiza un P(S1) y queda suspendido, pues S1=-1. H2 realiza un V(S4), dejando tal semforo a 1. H2 realiza un V(S3) y tambin deja ese semforo a 1. H2 realiza el P(S3). Como dicho semforo tiene valor 1, lo deja a cero y termina. H1 realiza un P(S3), lo deja a valor -1 y queda suspendido. Por tanto, slo termina H2 en el paso 8. H1 ha quedado suspendido en P(S3) y H3 tambin est suspendido, en este caso en P(S1).

Dado el siguiente cdigo, y asumiendo que todos los semforos tenan valor inicial cero, que los hilos presentados estn soportados por el ncleo del sistema operativo, se encontraban en la cola de preparados en el orden H1, H2, H3 (es decir, H1 ser el primero en obtener la CPU y H3 el ltimo) y que se utiliza un algoritmo de planificacin FCFS: H1 P(S3); P(S1); V(S1); V(S3); H2 P(S2); V(S4); V(S3); P(S3); H3 V(S2); V(S1); P(S1); P(S4);

Indique el orden de terminacin de dichos hilos. En caso de que algn hilo no termine, indique en qu operacin se ha quedado suspendido.

Ej. 6-12

El orden de ejecucin de estas operaciones ser: H1 ejecuta P(S3) y queda suspendido, pues S3=-1. H2 ejecuta P(S2) y queda suspendido, pues S2=-1. H3 ejecuta V(S2), reactivando a H2 y dejando S2 a cero. H3 ejecuta V(S1), dejndolo a 1. H3 ejecuta P(S1), dejndolo a cero. H3 ejecuta P(S4) y queda suspendido, pues S4=-1. H2 ejecuta V(S4), reactivando a H3 y dejando S4 a cero. H2 ejecuta V(S3), reactivando a H1 y dejando S3 a cero. H2 ejecuta P(S3) y queda suspendido, pues S3=-1. H3 sale del P(S4) y termina. H1 ejecuta P(S1) y queda suspendido, pues S1=-1. Por tanto, slo termina H3 en el paso 10. H2 queda suspendido en el P(S3), mientras H1 queda suspendido en el P(S1).

Se desea sincronizar la ejecucin de dos hilos H1 y H2, definidos como sigue:

H1 while (true) {A}

H2 while (true) {B}

Donde A y B representan fragmentos de cdigo. Para realizar la sincronizacin, no se puede asumir nada sobre la duracin de los fragmentos A y B, ni sobre la velocidad relativa de H1 y H2. El primer hilo en completar su fragmento de cdigo (A o B) debe espere a que el otro hilo complete el suyo, es decir, ningn hilo empieza una nueva iteracin del bucle hasta que el otro ha completado la suya ( el ms rpido espera al ms lento). Disee una solucin utilizando semforos que cumpla dichos requisitos.

Ej. 6-13

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

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

Comente qu valores posibles tendran las variables x e y al finalizar la ejecucin de los siguientes tres procesos concurrentes. Los valores iniciales son los siguientes: x=1, y=4, S1=1, S2=0 y S3=1. Proceso A P(S2); P(S3); x = y * 2; y = y + 1; V(S3); Proceso B P(S1); P(S3); x = x + 1; y = 8 + x; V(S2); V(S3); Proceso C P(S1); P(S3); x = y + 2; y = x * 4; V(S3); V(S1);

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

Cuales son los posibles valores que tomar x como resultado de la ejecucin concurrente de los siguientes hilos?
#include <semaphore.h> int main()

Ej. 6 14

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

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

De la ejecucin del cdigo anterior se obtiene dos posibles valores de x, x=20 y x=1. En el segundo resultado ocurre un interbloqueo. El semforo s3, inicializado a 0, obliga a que la sentencia x= 10 * x se ejecute despus de la sentencia x = x+1. Si se ejecuta func_hilo1 (sin interrupcin) y, a continuacin, se ejecuta func_hilo2, el resultado es 20. Si el hilo h1 ejecuta sem_wait(&s1) de func_hilo1 y otro hilo h2 ejecuta sem_wait(&s2) de func_hilo2 antes que h1 ejecute sem_wait(&s2) de func_hilo1 entonces se tiene un interbloqueo y el resultado es x=1.

Dado el siguiente programa escrito en C con primitivas de sincronizacin POSIX, donde se supone que la funcin printf() escribe directamente en memoria de vdeo y, por tanto, no llega a implicar la suspensin de los procesos (ya que no realiza una E/S que necesite espera):
#include <pthread.h> #include <stdio.h> int a=0; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_cond_t seg = PTHREAD_COND_INITIALIZER; void 14) 15) 16) 17) 18) 19) 20) 21) } void 10) 11) 12) 13) *hilo1(void *a1) { pthread_mutex_lock(&mut); printf( Hilo 1 espera.\n ); if (a==0) pthread_cond_wait(&cond,&mut); printf( Hilo 1 avisa.\n ); pthread_cond_signal(&seg); pthread_mutex_unlock(&mut); printf( "Termina hilo 1.\n" ); pthread_exit(0); 23) 24) } printf( "Termina hilo 2.\n" ); pthread_exit(0);

Ej. 6-15

int main(void) { pthread_t h1,h2; 1) 2) pthread_create(&h2,NULL, hilo2,NULL); pthread_create(&h1,NULL, hilo1,NULL); printf( "Hilos creados.\n" ); pthread_mutex_lock(&mut); pthread_cond_signal(&cond); a=15; pthread_mutex_unlock(&mut); printf( "Esperando hilos.\n" ); pthread_join(h1,NULL); printf( "Hilo 1 terminado.\n" ); pthread_join(h2,NULL); printf( "Hilo 2 terminado.\n" ); return 0;

*hilo2(void *a2) { pthread_mutex_lock(&mut); printf( Hilo 2 espera.\n ); if (a!=0) pthread_cond_wait(&seg,&mut);

3) 4) 5) 6) 7) 8) 9) 25) 26) 27) 28)

22)

pthread_mutex_unlock(&mut);

Indique qu se mostrar en pantalla al ejecutar el programa anterior, cuntos hilos terminarn la ejecucin del cdigo mostrado y en qu orden. Si algn hilo no consigue terminar indique por qu motivo no logra hacerlo. Asuma un algoritmo de planificacin FCFS.

Ej. 6-16 Ej. 6-17

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

Explique qu ventajas aporta el uso de monitores sobre el uso de semforos.

Como los monitores son una construccin lingstica que ya proporciona de forma automtica la exclusin mutua, el programador obtendra con ellos un modelo de programacin mucho ms sencillo. Si slo utiliza variables compartidas por los hilos que estn protegidas por los monitores, ya no tendr que preocuparse por el problema de la seccin crtica. Utilizando semforos lo tendra mucho ms complicado.

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

Ej. 6-18

F F

F V

Una operacin V(S) puede llegar a suspender al proceso que la ejecute, dependiendo del valor que tenga el contador del semforo. Una operacin de este tipo jams suspender a un proceso o a un hilo. Las variables condicin permiten suspender a los hilos fuera de las regiones de cdigo protegidas mediante mtex. Las variables condicin deben utilizarse SOLAMENTE dentro de regiones de cdigo protegidas mediante un mtex. De otra forma, deberan devolver un error al realizar la operacin pthread_mutex_wait() (vase la documentacin del estndar POSIX). En algunas versiones de Linux no se devuelve tal error, pero el programa acaba abortando al intentar avisar al hilo suspendido. Utilizando slo mtex POSIX se pueden realizar las mismas labores de sincronizacin que con los semforos propuestos por Dijkstra. Aunque s que sirven para garantizar exclusin mutua, no sirven para la segunda tarea de utilizacin de los semforos: garantizar un orden de ejecucin entre dos o ms funciones de diferentes hilos o procesos. Para ello, deberamos combinar los mtex con las variables de tipo condicin. No existe ninguna forma de averiguar, sin suspenderse, si actualmente un mtex tiene un hilo propietario o est abierto. S que puede hacerse mediante la funcin pthread_mutex_trylock(). Los monitores son un ejemplo de construccin lingstica utilizable para sincronizar hilos o procesos.

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

Ej. 6-19

F F

V V

Una operacin pthread_mutex_lock() siempre suspende al hilo que la ejecuta. Si el mtex estaba abierto, no le suspende. Una operacin pthread_cond_signal() puede suspender al hilo que la ejecuta. Por ejemplo, si haba hilos suspendidos en la condicin utilizada y stos son todos ms prioritarios que el que la ha ejecutado. La operacin pthread_cond_signal() nunca suspende. En el ejemplo citado, no se realiza una suspensin, sino una expulsin. La expulsin conlleva pasar al hilo al estado PREPARADO, pero no al SUSPENDIDO. La operacin pthread_cond_broadcast() no debera emplearse, pues puede llegar a despertar mltiples hilos y ello conllevara violar la exclusin mutua de la regin en la que estaba incluida la pthread_cond_wait(). Falso. El problema citado se evita pasando como segundo argumento la direccin del mtex al realizar una espera y obligando a que el hilo despertado deba readquirir el mtex. Al utilizar la operacin V(S) sobre un semforo, no siempre se despertar algn hilo o proceso. Cierto, pues depende del valor que tuviera el contador del semforo. La espera activa puede eliminarse utilizando protocolos de entrada y salida de la seccin crtica basados en mtex o en semforos. Cierto, pues ambas herramientas suspenden al hilo que intenta entrar en la seccin cuando sta ya est ocupada. En los semforos se debe tomar la precaucin de haberlos inicializado con el valor 1.

Razone adecuadamente si el siguiente cdigo es una buena solucin al problema de la seccin crtica, donde S es un semforo compartido por todos los procesos o hilos con acceso a dicha seccin crtica, con valor inicial 1. Adems, se sabe que la poltica utilizada por el sistema operativo para reactivar a los procesos suspendidos es LIFO (Last In, First Out).

while(1) { P(S); Seccin crtica V(S); Seccin restante }

Ej. 6-20

No es una buena solucin, pues no satisface todas las condiciones exigibles a una solucin correcta (exclusin mutua, progreso y espera limitada). De hecho, no satisface la espera limitada, pues un proceso puede haber llegado a su protocolo de entrada y, si no paran de llegar otros procesos a sus respectivos protocolos de entrada, no existira ninguna cota sobre el nmero de veces que otros procesos en competencia con l le superan a la hora de acceder a la seccin crtica. Esto se dar si el nmero de procesos o hilos en el protocolo de entrada es siempre igual o superior a 2. El primero que lleg encontrando la seccin crtica ocupada no podra entrar jams.

El siguiente pseudocdigo utiliza semforos (segn la nomenclatura original de Dijkstra), para sincronizar n hilos en una barrera, de forma que todos pueden continuar con la seccin posterior nicamente cuando los n han completado la seccin previa. Sin embargo, sta solucin es incorrecta. Disea de nuevo el cdigo entre la seccin previa y la posterior.
int n, c=0; // Nombre de fils, comptador sem mutex=1, barrera=0; secci_prvia (...) P(mutex); c=c+1; V(mutex); if (c == n) V(barrera); P(barrera); secci_posterior (...)

Ej. 6-21

El valor del semforo barrera ser -(n-1) cuando todava falte llegar el hilo n, que lo dejara con el valor (n-2) sin conseguir el objetivo propuesto en el enunciado. Para resolverlo, bastara con aadir esta lnea tras la P(barrera) original del enunciado. Con ello se obtiene un efecto similar al de la reactivacin en cascada que se daba en algunos modelos de monitor. 6: V(barrera)

Rellene el cdigo siguiente, para proporcionar un protocolo de entrada y salida a la seccin crtica del problema de los filsofos, usando espera activa. sese la funcin "int test_and_set (int * objetivo);".
#include <stdlib.h> #include <stdio.h> #include "testandset.h" /* test_and_set: devuelve 1 (cierto) si llave esta siendo utilizada, devuelve 0 (falso) si llave esta libre. */ int test_and_set (int * objetivo);

#define NUMERO_FILOSOFOS 5 int tenedores[NUMERO_FILOSOFOS];

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

Ej. 6-22

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

El cdigo que se proporciona a continuacin es parte de una solucin POSIX al problema de los lectores y escritores.
/****protocolo de entrada para Lectores ***/ void pre_leer() { pthread_mutex_lock(&mutex_monitor); while ( (nesc>0) || (w_esc>0) ) { w_lec++; pthread_cond_wait(&cond,&mutex_monitor); w_lec--; } /***protocolo de entrada para Escritores ***/ void pre_escribir() { pthread_mutex_lock(&mutex_monitor); while((nesc>0)||(nlec>0)) { w_esc++; pthread_cond_wait(&cond,&mutex_monitor); w_esc--; }

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

nesc++; pthread_mutex_unlock(&mutex_monitor);

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

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

Ej. 6-23

Para que la solucin pueda considerarse correcta debe darse exclusin mutua entre escritores, por una parte, y entre lectores y escritores, por otra. En esta solucin, si hay un escritor accediendo, nesc valdr 1 y en ese caso, si comprobamos las condiciones de pre_leer() y pre_escribir() veremos que ningn otro escritor, ni lector podrn acceder. Por otra parte, si hay algn lector accediendo, no podr haber ningn escritor, pues en la condicin de pre_escribir() tambin se comprueba tal cosa. Adems, mltiples lectores deben poder acceder al mismo tiempo. Esto tambin se cumple, pues para entrar un lector no se comprueba en ningn caso si hay o no otros lectores accediendo. Por todo ello, esta solucin ES CORRECTA. El tipo de proceso con mayor prioridad en esta solucin sern los escritores, pues ningn lector puede entrar mientras haya algn escritor esperando. Por su parte, para que entre un escritor no importa si hay lectores esperando o no.

Escriba el cdigo necesario para implantar una nueva primitiva de sincronizacin a la que vamos a llamar barrera, con slo tres operaciones accesibles: suspender, liberar e inicializar. Su comportamiento es el siguiente. Suspender suspende siempre al hilo que la invoque. Liberar reactiva a uno de los hilos si haba menos de cinco hilos suspendidos, o a todos ellos si haba cinco o ms; si no hay hilos suspendidos, no debe tener ningn efecto. Inicializar se encarga de inicializar esta herramienta, fijando el nmero de hilos suspendidos a cero. Implemente el cdigo utilizando mtex y variables condicin del estndar POSIX:

Ej. 6-24 Ej. 6-25


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

En el cdigo para los lectores y escritores anterior, para qu sirve la variable condicin cond? Qu ocurre cuando se realiza una operacin pthread_cond_broadcast(&cond);?

Esa variable condicin sirve para suspender a los hilos que, por el tipo de acceso que se est llevando a cabo en ese momento, no tengan permitido el acceso cuando ellos lo intentaban. Al realizar un aviso mltiple sobre tal condicin, se despertarn consecutivamente todos los hilos suspendidos en tal condicin. Como la suspensin en la condicin est asociada a un bucle while, y en la expresin lgica evaluada en dicho bucle se vuelve a comprobar si dicho tipo de hilo tiene permitido el acceso, la implementacin presentada es correcta.

En el siguiente programa se pretende producir y visualizar imgenes usando hilos independientes. Cada hilo producir una imagen, para ello invertir un tiempo de cmputo diferente en cada caso. El objetivo del monitor que se pide implementar es sincronizar la seccin de visualizacin de los diferentes hilos. Para ello el monitor ofrece la funcin Punto_de_sincronizacin(). Cuando un hilo invoca dicha funcin su comportamiento est definido por las siguientes reglas: Si el hilo no es el ltimo que llega al punto de sincronizacin, deber suspenderse. Si ya han llegado todos los dems hilos al punto de sincronizacin, deber despertarlos y preparar el monitor para la siguiente iteracin.

Se pide implementar la clase Monitor.


#include <pthread.h> #include <stdlib.h> void hilo(void *p) {

#include <stdio.h>

#define N 5 class Monitor { // implementar // en la solucin }; Monitor mon;


}

int id = (int) p; while(true) { // Seccin A. Produce una imagen del tipo id Producir_Imagen(id); // fin de la seccin A (produccin de la imagen) mon.Punto_de_sincronizacion(); //Seccin B. Visualiza una imagen del tipo id Visualizar_Imagen(id); // fin de la seccin B (visualizacin de la imagen) }

main() { int i; pthread_t hilos[N]; for (int i=0; i<N; i++) pthread_create(&hilos[i],NULL,hilo, (void *) i); for (int i=0; i<N; i++) pthread_join(hilos[i],NULL); }

Ej. 6-26

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

}; //fin del monitor

Se desea controlar el uso de una pantalla TFT de 24 utilizada para visualizar los resultados de dos tipos de procesos, los Pi (P1,.,Pn trabajos de programacin) y los Ji (J1,..,Jm juegos didcticos). El nico requisito para utilizar la pantalla es que sea en exclusin mutua por parte de uno u otro proceso. a) Indique de forma justificada si el siguiente monitor cumple con dicha especificacin, teniendo en cuenta que ambos procesos invocaran las funciones, Pre_utilizacion() y Post_utilizacion(), del monitor antes y despus de utilizar la pantalla respectivamente.
class Monitor {private:

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

Ej. 6-27

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

b) Modifique el cdigo del monitor propuesto, aadiendo tanto cdigo nuevo como funciones dentro del monitor, de manera que: adems de cumplir el requisito de exclusin mutua en el uso de la pantalla tengan prioridad los procesos Pi para su utilizacin frente a los Ji. NOTA1: Se trata de una solucin en la que puede haber inanicin para los procesos Ji sino dejan de llegar procesos Pi. NOTA2: Al tener diferente prioridad los procesos, hay que modificar el interfaz del monitor para que incluya las funciones: Pre_utilizacion_P(), Pre_utilizacion_J(), Post_utilizacion_P() y Post_utilizacion_J()

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

Suponemos un tampn de talla MAX compartido por productores y consumidores. En cada operacin de insercin/extraccin se indica como argumento el nmero de tems a insertar/extraer:
void deposita (int n) {..} // deposita n items (se retrasa si no hay huecos suficientes) void extrae (int n) {..} //extrae n items (se retrasa si no hay elementos suficientes)

Para simplificar, indicamos nicamente el nmero de items a insertar/extraer, pero no es necesario mantener los valores en el tampn (ignoramos los detalles de implantacin del tampn y de paso de valores/obtencin de resultados). Disee el mecanismo de sincronizacin necesario utilizando un monitor con primitivas POSIX. Nota: Como ayuda al uso de la nomenclatura se aportan los prototipos de algunas llamadas.
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);

Ej. 6-28

Ej. 6-29

/**** Declaracin de variables globales*******/ int MAX /***nmero mximo de items que caben en el tampn ****/
int e=0; // num. actual elementos pthread_mutex_t mutex;

pthread_cond_t lleno, vacio;

void deposita (int n) { pthread_mutex_lock(&mutex); while (e+n > MAX) {pthread_cond_wait(&lleno, &mutex);} e+=n; /** cdigo de inserta valores (SE SUPONE HECHO)***/ pthread_cond_broadcast(&lleno); pthread_mutex_unlock(&vacio); } void extrae (int n) { pthread_mutex_lock(&mutex); while (n > e) {pthread_cond_wait(&vacio,&mutex);} e-=n; /**cdigo que extrae y devuelve resultado (SE SUPONE HECHO)**/ pthread_cond_broadcast(&lleno); pthread_mutex_unlock(&mutex); }

En un sistema existen dos procesos: C (cliente) y S (servidor), con el siguiente patrn de comportamiento:
void *Cliente(void *arg) { while(true) { ... x.EsperarS(); ... } } void *Servidor(void *arg) { while(true) { ... x.EsperarC(); Servicio(); x.ActivarC(); ... } }

Donde x es una instancia de un monitor denominado ClienteServidor cuyas operaciones son: EsperarS(): suspende el proceso C hasta que el proceso S invoque ActivarC(). EsperarC(): suspende el proceso S hasta que el proceso C invoque EsperarS(). ActivarC(): activa el proceso C (suspendido en EsperarS()) Se pide implementar el monitor ClienteServidor utilizando mutex y variables condicin de POSIX.
/*********************************** Cita C/S con 1 Cliente y 1 Servidor ***********************************/ #include <pthread.h> #include <stdlib.h> #include <stdio.h> void *Cliente(void *arg){ while(true) { retraso(200+rand()%1); printf("Cliente: invoca servicio.\n"); x.EsperarS(); printf("Cliente: fin invocacion.\n"); }

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

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

Ej. 6-30

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

El siguiente fragmento de cdigo es una versin simplificada de la solucin para el problema de los 5 filsofos basada en variables condicin.
1: while(1) 2: { 3: // Entrada en el comedor. 4: pthread_mutex_lock(&mutex_del_contador); 5: while (cuantos_dentro>=4) 6: { 7: printf(Puedo soar.\n); 8: pthread_cond_wait(&cond_x,&mutex_del_contador); 9: printf(Soy yo.\n); 10: } 11: cuantos_dentro++; 12: pthread_mutex_unlock(&mutex_del_contador); 13: // Dentro del comedor comiendo. 14: retraso(5000+rand()%1000); // son muy comilones 15: printf(Como.\n); 16: // Saliendo del comedor. 17: pthread_mutex_lock(&mutex_del_contador); 18: cuantos_dentro--; 19: pthread_cond_signal(&cond_x); 20: pthread_mutex_unlock(&mutex_del_contador); 21: retraso(40000+rand()%1000); // son muy dormilones. 22: }

Rellene la tabla indicando para cada evento(hilo:linea) de la primera columna: el estado del mutex y los hilos bloqueados en la cola de cada primitiva de sincronizacin. Nota: Suponga que los hilos, los mutex y las variables condicin estn correctamente inicializados. Situacin: Existen 5 hilos en marcha. F1..F4 se (F1:14,F2:14,F3:14,F4:14) y F5 acaba de lanzarse. mutex_de_contador Traza de los hilos Estado del mutex, hilos en cola F5:1 libre, cola vaca F5:4 asignado a F5, cola vaca F5:8 libre, cola vaca F1:17 asignado a F1, cola vaca F1:19 asignado a F1, F5 en la cola F1:20 asignado a F5, cola vaca

Ej. 6-31

encuentran

ejecutando

la

lnea

14:

cond_x Hilos en cola de la var. condicin --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). En l se ejecutan tres procesos: P1, P2 y P3, inicialmente sin ninguna instancia asignada. En el sistema se da la siguiente secuencia de solicitudes de recurso: P3 pide 1 instancia de R1, P2 pide 1 instancia de R1, P1 pide 1 instancia de R1, P3 pide 1 instancia de R2, P2 pide 1 instancia de R2, P2 pide 1 instancia de R1, P1 pide 2 instancias de R2. Asumiendo que ningn proceso llega a liberar ninguna instancia, dibuje el grafo de asignacin de recursos resultante e indique si en el sistema hay o no un interbloqueo.

Ej. 6-32

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

Dado un sistema con tres recursos R1, R2, 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 operacin).

Ej. 6-33

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

Sean tres procesos P1, P2 y P3 que ejecutan el cdigo que se presenta en la siguiente tabla. Suponga que los valores iniciales de los semforos son: S1=1, S2=2, S3=3. Diga si es posible que se produzca un nterbloqueo. En caso afirmativo, represente el grafo de asignacin de recursos que correspondera a dicha situacin y en caso negativo demustrelo, haciendo uso de las condiciones de Coffman.
/*Variables globales*/ S1=1; S2=2; S3=3; 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)

Ej. 6-34

Si.
P1 P3 S3 S2 S1

P2

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

R1

R2

R3 P1

Ej. 6-35

No existe riesgo de interbloqueo, porque no se puede dar una espera circular. No existe ninguna evolucin de este estado que termine en un interbloqueo. Despus de la peticin de P1, el proceso P3, que tiene asignados todos los recursos necesarios puede terminar, con lo cual P1 puede obtener todos los recursos necesarios y terminar tambin. Una vez ha terminado P1, P2 puede obtener todos los recursos necesarios y terminar. Por tanto, existe al menos una secuencia segura de terminacin: P3, P1, P2.

Considere la siguiente solucin para el problema de los 5 filsofos. Indique si es posible que se produzca un interbloqueo razonando la respuesta en trminos de las condiciones de Coffman.
#include <pthread.h> #include <stdlib.h> #include <stdio.h> #define NUMERO_FILOSOFOS 5 pthread_mutex_t tenedores[NUMERO_FILOSOFOS]; void *Filosofo(void *arg) { int nfilo=(int)arg;

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

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

pthread_mutex_unlock(&tenedores[t1]); pthread_mutex_unlock(&tenedores[t2]); usleep(300000); } } // un tiempo pensando...

Con el cdigo presentado, el filsofo 4 ser el nico que haga cierta la condicin (nfilo>((nfilo+1)%NUMERO_FILOSOFOS)). Por ello, todos los filsofos cogern primero el tenedor de su izquierda antes que el de su derecha (o al revs, eso depende de cmo se distribuyan los filsofos y los tenedores en la mesa), excepto el filsofo 4. Esto evitar el interbloqueo, pues si todos los filsofos intentaran coger a la vez su primer tenedor, todos lo conseguiran excepto uno (el 4 o el 0). Gracias a esto, cuando intentaran coger el segundo tenedor, uno de ellos lo podra hacer (el otro vecino del que antes se qued sin nada, es decir, el que no tena el tenedor que l intent coger en primer lugar). Como resultado, no podr haber espera circular, pues habr un filsofo que no retendr nada (el 4 o el 0) y otro que no esperar nada (el 3 o el 4), rompiendo as el ciclo dirigido. Otra explicacin ms sencilla se basa en la propiedad de prevencin de interbloqueos que deca que para evitar la espera circular haba que ordenar todos los recursos y pedirlos siguiendo el orden establecido. En el ejemplo presentado, todos los filsofos estn pidiendo los tenedores en orden creciente. Por ello, no podr cerrarse jams un ciclo dirigido.

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

Ej. 6-36

Recurso Cantidad

R0 R1 R2 R3 R4 RC 1 1 1 1 1 n

El perfil de ejecucin 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); Peticion(Ri); Peticion(R((i+1) mod 5))); UsoDeLosRecursos(); Libera(Ri); Libera(R((i+1) mod 5)); Libera(RC); SeccionRestante(); end while; Perfil de los procesos impares while TRUE do Peticion(RC); Peticion(R((i+1) mod 5))); Peticion(Ri); UsoDeLosRecursos(); Libera(Ri); Libera(R((i+1) mod 5)); Libera(RC); SeccionRestante(); end while;

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

Ej. 6-37

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

Analice la siguiente implementacin para las operaciones P y V sobre un semforo S:


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

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

Exponga de manera justificada, analizando con detalle cada una de las condiciones estudiadas, si dicha implementacin es vlida para resolver el problema de la seccin crtica. Considere que inicialmente S=1 y llave=false.

Ej. 6-38

Las condiciones que debe garantizar una solucin a la seccin crtica son: 1) Exclusin mutua 2) Progreso 3) Espera limitada. 1) Exclusin mutua, si hay un proceso ejecutando seccin crtica ningn otro puede ejecutar la suya. Como en el protocolo de entrada se ejecuta la operacin P(S), esto dejara S= 0 y llave= false. Por tanto otro proceso podra ejecutar el protocolo de entrada y pasar a la seccin crtica. 2) El problema est, en que un proceso se puede quedar en el bucle while (S<0) con lo cual no suelta la llave y por tanto ningn otro podr ejecutar el protocolo de salida para variar el valor de S. 3) Esta solucin no garantiza la espera limitada, ya que el primero que consiga la llave, entra.

Compruebe si la siguiente solucin al productor/consumidor es correcta o no. Si no es correcta, indique cul es el problema y modifique el cdigo para que funcione.
void *func_prod(void *p) { int item; while(1) { item = producir(); P(mutex); while (contador == N) /*bucle vacio*/ ; buffer[entrada] = item; entrada = (entrada + 1) % N; contador = contador + 1; V(mutex); } } } void *func_cons(void *p) { int item; while(1) { P(mutex); while (contador == 0) /*bucle vacio*/ ; item = buffer[salida]; salida = (salida + 1) % N; contador = contador - 1; V(mutex); consumir(item); }

Ej. 6-39 Ej. 6-40

No es correcta la solucin.
Esta solucin no funciona, porque un productor o consumidor que haya cerrado mutex y se quede en el bucle while, deja a todos los dems hilos bloqueados!! Se definen dos semforos ms lleno, inicializado a cero, suspende a los consumidores cuando est vaco vacio, inicializado a N, suspende a los productores cuando el buffer est lleno

Se define un evento como un tipo de datos, utilizado para la sincronizacin de procesos, que admite las siguientes dos operaciones sleep() suspende la ejecucin 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; pthread_mutex_t m; public: Evento() { c=PTHREAD_COND_INITIALIZER; m=PTHREAD_MUTEX_INITIALIZER; } void sleep(int x) { pthread_mutex_lock(&m); pthread_cond_wait(&c,&m); pthread_mutex_unlock(&m); } void wakeup() { pthread_mutex_lock(&m); pthread_cond_broadcast(&c); pthread_mutex_unlock(&m); } }

Sea un sistema con 3 recursos R1, R2 y R3 de los cuales se dispone de 2, 3 y 2 instancias respectivamente. Partiendo de una situacin en la que todos los recursos se encuentran disponibles, en dicho sistema se ejecutan tres procesos P1, P2 y P3 que realizan la siguiente traza de peticiones: 1. P1: solicita R1 2. P1: solicita R1 3. P2: solicita R2 4. P2: solicita R1 5. P3: solicita R2 6. P3: solicita R2 7. P1: solicita R2 8. P1: solicita R3 9. P1: solicita R3

Indique de forma razonada si es posible o no que se produzca un interbloqueo, para ello represente el grafo de asignacin de recursos que correspondera a dicha situacin y en caso negativo demustrelo, haciendo uso de las condiciones de Coffman.

Ej. 6-41

P1

P2

P3

R1

R2

R3

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

Siguiendo la estructura del microshell desarrollado en prcticas, 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 Ejecutndose, Terminado, Bloqueado, Zombie o Hurfano) en que se encuentran dichos procesos hijos si:
Situacin El proceso ush ya ha terminado su ejecucin, mientras que los hijos an continan. El padre y el hijo2 no cierran descriptores y siguen en ejecucin. El padre no ha ejecutado ningn wait cuando los hijos ya han terminado su ejecucin. El padre y el hijo1 no cierran descriptores y no terminan.

Ej. 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, es decir, en la que los N-1 primeros hilos deban pararse hasta la llegada del hilo N. El cdigo siguiente forma parte de dicha barrera la cual se encuentra dentro de un bucle infinito. La barrera est implementada usando semforos POSIX.
sem_t mutex, barrera; int c= 0; // Comtador de hilos sem_init(&mutex, 0, 1); sem_init(&barrera, 0, 0); ... while (1) { // Para cada hilo // Seccin fuera de la barrera 1: c++; 2: if (c == N) sem_post(&barrera); 3: sem_wait(&barrera); 4: sem_post(&barrera); // Seccin dentro de la barrera 5: c--; 6: if (c == 0) sem_wait(&barrera); // Secci posterior } // Bucle infinito

a) Indique cules de las lneas numeradas del cdigo tendran que estar protegidas entre sem_wait(&mutex) y sem_post(&mutex) obligatoriamente para evitar posibles condiciones de carrera.

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

Ej. 6-43 Ej. 6-44 Ej. 6-45

(a): Deberan estar protegidas por el mutex los grupos de lneas 1-2 y 5-6. En estas lneas se puede producir una
condicin de carrera. En ningn caso hay que proteger las lneas 3 ni 4.

(b): La barrera es totalmente correcta a no ser que se use en el interior de un bucle infinito como es el caso. Con este cdigo un hilo rpido podra finalizar su seccin dentro de la barrera, su seccin posterior y volver a entrar por la barrera modificando c antes de que los dems hilos finalicen su cdigo dentro de la barrera. En este supuesto, la solucin propuesta es totalmente incorrecta.

Un ordenador tiene n unidades de cinta idnticas (n instancias de un mismo recurso), con p procesos compitiendo por ellas. Cada proceso realiza el siguiente programa: solicita 2 unidades de cinta, escribe datos en ellas y las libera. Cul es el nmero mximo de procesos p para asegurar que el sistema est libre de interbloqueos? Justifquelo con las condiciones de Coffman. Se trata de impedir la espera circular, para ello lo nico que hay que garantizar, es que el nmero de procesos sea igual o inferior al nmero de cintas menos uno. Esto garantiza que siempre hay un proceso que pueda tener las dos cintas que necesita y no se produzca interbloqueos.
P<= n-1

Un semforo limitado se define como un semforo cuyo contador no puede superar un valor mximo predefinido n (si el contador vale n, una operacin V supone esperar). Se pide escribir un monitor que implemente las operaciones P y V sobre un semforo limitado completando el siguiente cdigo:
SOLUCION class SemafLimitado { private: int c,n; pthread_cond_t a,b; pthread_mutex_t m; public: SemafLimitado(int n0, int c0) { //maximo y valor inicial c=c0; n=n0; a = b = PTHREAD_COND_INITIALIZER; m=PTHREAD_MUTEX_INITIALIZER; } void P() { pthread_mutex_lock(&m); while (c<=0) pthread_cond_wait(&a,&m); c--; pthread_cond_signal(&b); pthread_mutex_unlock(&m); } void V() { pthread_mutex_lock(&m); while (c>=n) pthread_cond_wait(&b,&m); c++; pthread_cond_signal(&a); pthread_mutex_unlock(&m); } }

7. Problemas de sincronizacin
7.1 Dificultad de los ejercicios
Nivel de dificultad Principiante Medio Avanzado Ejercicios

7.2 Ejercicios
Tenemos un puente levadizo sobre un ro con las siguientes condiciones de utilizacin: Los barcos tienen siempre prioridad de paso, pero para levantar el puente han de esperar a que no haya ningn coche sobre l. Los coches pueden utilizar el puente si no hay ningn barco pasando (en cuyo caso el puente estar levantado) o esperando. Escribir los algoritmos para barcos y coches utilizando: a) semforos b) monitores.
a) Solucin con semforos:
#include <pthread.h> #include <semaphore.h> #define NUM_COCHES 5 #define NUM_BARCOS 5 void bajar_puente(void) {} void pasar(void) {} void levantar_puente(void) {} /*-----------------------------------------------------*/ /* Variables compartidas. */ /*-----------------------------------------------------*/ sem_t turno; /* Necesario para dar prioridad */ /* a los barcos. */ sem_t mutex1; /* Exclusin mutua entre coches.*/ sem_t mutex2; /* Exclusin mutua entre barcos.*/ sem_t libre; /* Indica si el puente est */ /* libre. */ int barcos = 0; /* Nmero de barcos intentando */ /* pasar. */ int coches = 0; /* Nmero de coches intentando */ /* pasar. */ /*-----------------------------------------------------*/ /* Cdigo de los coches. */ /*-----------------------------------------------------*/ void *coche(void *arg) { sem_wait(&turno); sem_post(&turno); sem_wait(&mutex1); coches++; if (coches==1) sem_wait(&libre); sem_post(&mutex1); /* Verificar si podemos pasar. /* /* /* /* /* /* /* */

Ej. 7-1

Exclusin mutua en el acceso */ a la variable coches. */ Incrementar el n de coches. */ Bloquear paso a los barcos, o*/ a los siguientes coches, si */ ya hubiese barcos. */ Fin de la S.C. sobre coches. */

bajar_puente(); pasar(); sem_wait(&mutex1); coches--; if (coches==0) sem_post(&libre); sem_post(&mutex1); }

/* Funciones ya definidas. /* /* /* /* /* S.C. para acceder a coches. Decrementar n de coches. Si es el ltimo, libera el paso a los barcos. Fin de la S.C. sobre coches.

*/ */ */ */ */ */

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

/* Fin de la S.C. sobre barcos. */

*/

b) Solucin mediante monitores (pseudo-Pascal):

TYPE puente_levadizo = MONITOR; VAR coches, barcos, barcos_bloqueados : integer; OkCoche, OkBarco : condition; PROCEDURE ENTRY entrar_coche; BEGIN (* Para dar prioridad a los barcos, los coches se suspenden si hay algn barco esperando. *) IF (barcos>0) OR (barcos_bloqueados>0) THEN OkCoche.wait; coches := coches + 1; OkCoche.signal; (* Liberamos en cascada al resto de coches, si los hay. *) bajar_puente END; PROCEDURE ENTRY salir_coche; BEGIN coches := coches 1; (* Si ya no quedan coches, dejamos pasar a los barcos. *) IF coches=0 THEN OkBarco.signal END; PROCEDURE ENTRY entrar_barco; BEGIN (* Si ya hay coches pasando, el barco se suspende. *) IF coches>0 THEN BEGIN barcos_bloqueados := barcos_bloqueados + 1; OkBarco.wait; barcos_bloqueados := barcos_bloqueados 1 END; barcos := barcos + 1; (* Reactivamos al resto de barcos, en cascada. *) OkBarco.signal; levantar_puente END; PROCEDURE ENTRY salir_barco; BEGIN barcos := barcos 1; (* Si es el ltimo barco, deja pasar a los coches. *) IF barcos=0 THEN OkCoche.signal END; BEGIN (* Inicializacin del monitor. *) barcos := 0; coches := 0; barcos_bloqueados := 0 END;

Solucin mediante monitores (implantacin en POSIX):


#include <pthread.h> /*-----------------------------------------------------*/ /* Variables globales. */ /*-----------------------------------------------------*/ pthread_mutex_t mutex_monitor; pthread_cond_t ok_barco; pthread_cond_t ok_coche; int coches = 0; int barcos = 0; int barcos_bloqueados = 0; /*-----------------------------------------------------*/ /* Procedimientos de entrada. */ /*-----------------------------------------------------*/ void entrar_coche(void) { pthread_mutex_lock(&mutex_monitor); /* Damos prioridad a los barcos.*/ /* Si alguno ha pedido entrar y */ /* est suspendido, bloquear al*/ /* coche que intenta entrar. */ while ((barcos>0) || (barcos_bloqueados>0)) pthread_cond_wait(&ok_coche, &mutex_monitor);

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

Disponemos de dos caminos que separan dos sentidos de circulacin: Norte y Sur. Ambos caminos convergen en uno solo cuando se trata de cruzar un ro. Existen coches que quieren pasar de norte a sur, y de sur a norte. En cualquier momento, slo pueden atravesar el puente uno o ms coches que vayan en el mismo sentido (no se mezclan coches que van en sentidos opuestos). Implementar una solucin mediante semforos.

Ej. 7-2

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

sem_t mutex1; sem_t mutex2; int norte=0; int sur=0;

/* /* /* /* /* /* /*

Exclusin mutua en el acceso a la variable norte. dem para la variable sur. N de vehculos en direccin norte-->sur. N de vehculos en direccin sur-->norte.

*/ */ */ */ */ */ */

/*-----------------------------------------------------*/ /* Funciones utilizadas en los hilos. */ /*-----------------------------------------------------*/ void pasar_puente(void) {} /*-----------------------------------------------------*/ /* Hilo que representa a un vehculo en sentido N-->S */ /*-----------------------------------------------------*/ void *norte_sur(void *arg) { sem_wait(&turno); /* Exclusin mutua a la hora de */ /* pedir el paso. */ sem_wait(&mutex1); /* S.C. para acceder a norte. */ norte++; if (norte==1) /* El primer vehculo del norte */ sem_wait(&libre); /* no deja pasar a los del sur. */ sem_post(&mutex1); /* Fin de la S.C. de norte. */ sem_post(&turno); /* Otro hilo podr pedir paso. */ pasar_puente(); sem_wait(&mutex1); norte--; if (norte==0) sem_post(&libre); sem_post(&mutex1); } /*-----------------------------------------------------*/ /* Hilo que representa a un vehculo en sentido S-->N */ /*-----------------------------------------------------*/ void *sur_norte(void *arg) { sem_wait(&turno); /* Exclusin mutua a la hora de */ /* pedir el paso. */ sem_wait(&mutex2); /* S.C. para acceder a sur. */ sur++; if (sur==1) /* El primer vehculo del sur no*/ sem_wait(&libre); /* deja pasar a los del norte. */ sem_post(&mutex2); /* Fin de la S.C. de sur. */ sem_post(&turno) ; /* Otro hilo podr pedir paso. */ pasar_puente(); sem_wait(&mutex2); sur--; if (sur==0) sem_post(&libre); sem_post(&mutex2); } /*-----------------------------------------------------*/ /* En el programa principal (no mostrado) tendramos */ /* estas instrucciones de inicializacin: */ /* sem_init(&turno, 0, 1); */ /* sem_init(&libre, 0, 1); */ /* sem_init(&mutex1, 0, 1); */ /* sem_init(&mutex2, 0, 1); */ /*-----------------------------------------------------*/ /* Proceder a cruzar. /* S.C. para acceder a sur. */ */ /* Proceder a cruzar. */

/* S.C. para acceder a norte. */ /* Si es el ltimo, permite que */ /* los del sur pasen. */ /* Fin de la S.C. de norte. */

/* Si es el ltimo, permite que */ /* los del norte pasen. */ /* Fin de la S.C. de sur. */

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

El problema consiste en disear un programa adecuado para que se logre la sincronizacin adecuada. Debe resolverse implementando en POSIX el equivalente a un monitor, respetando la interfaz que se presenta en el ejemplo de uso siguiente:
#include <pthread.h> typedef enum {LLENO, CORTADO} respuesta; /*-----------------------------------------------------*/ /* Cdigo del hilo que representa a un cliente. */ /*-----------------------------------------------------*/ void *cliente(void *arg) { respuesta res; do { /* Operacin del monitor. entrar_barberia(&res); if (res==LLENO) dar_una_vuelta(); } while (res!=CORTADO); } /*-----------------------------------------------------*/ /* Cdigo del hilo que representa al barbero. */ /*-----------------------------------------------------*/ void *barbero(void *arg) { while (1) { esperar_cliente(); /* Operacin del monitor. */ cortar_pelo(); /* No pertenece al monitor, slo*/ /* simula el corte. */ fin_corte(); /* Operacin del monitor. */ } } */

int main(void) { int i; pthread_t id_clientes[NUM_CLIENTES]; pthread_t id_barbero; /* Crear clientes y barbero. */ pthread_create(&id_barbero, NULL, barbero, NULL); for (i=0; i<NUM_CLIENTES; i++) pthread_create(&id_clientes[i], NULL, cliente, NULL); /* Esperar a los clientes. */ for (i=0; i<NUM_CLIENTES; i++) pthread_join(id_clientes[i], NULL); return 0; } /* Terminar. */

Ej. 7-3

El cdigo que aparece a continuacin formara parte del programa mostrado en el enunciado.
/*-----------------------------------------------------*/ /* Variables globales. */ /*-----------------------------------------------------*/ /* Para exclusin mutua en las */ /* operaciones del monitor. */ pthread_mutext_t m_monitor = PTHREAD_MUTEX_INITIALIZER; int n_clientes = 0; /* Nmero de clientes en la bar-*/ /* bera. */ /* Sillas de espera para los */ /* clientes (basta con una con- */

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

*/

Sea un proceso que crea tres tipos de hilos que ejecutan las funciones Proceso1, 2 y 3, respectivamente. Cada uno de ellos ejecuta una seccin crtica SC1, SC2 y SC3 tal como se muestra en el cdigo de las dos tablas. Ntese que existen muchos procesos de los tres tipos 1, 2 y 3 y que todos ellos antes de acceder a su seccin crtica ejecutan una funcin de entrada y cuando finalizan ejecutan una funcin de salida (comportamiento tipo monitor).

#include <semaphore.h> #include <pthread.h> #include <stdlib.h> #include <stdio.h> #define NUM_HILOS 20
void void void void void void EntraA(); EntraB(); EntraC(); SaleA(); SaleB(); SaleC();

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

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

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

a) Indique qu tipo de sincronizacin proporciona el monitor anterior. Para ello, conteste en la siguiente tabla en la cual debe colocar una X en el elemento (i,j) cuando la seccin crtica de un proceso de tipo i se pueda ejecutar concurrentemente con la seccin crtica de un proceso de tipo j.

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

SC1 SC2 SC3

SC1 X X ---

SC2 X -----

SC3 ----X

Ej. 7-4

a) SC1 SC1 X SC2 --SC3 X b)


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

SC2 --X ---

SC3 X -----

El cdigo que se proporciona a continuacin constituye una solucin al problema de un puente levadizo.
void entrar_coche(void) { sem_wait(&turno); sem_wait(&mutex1); c = c +1; if (c==1) sem_wait(&libre); sem_post(&mutex1); sem_post(&turno); bajar_puente(); entrar_puente(); } /* fin entrar_coche*/ void salir_coche(void) { void entrar_barco(void) { sem_wait(&mutex2); b = b+1; if (b==1) { sem_wait(&turno); sem_wait(&libre); } sem_post(&mutex2); levantar_puente(); entrar_puente(); } /* fin entrar_barco*/ void salir_barco(void)

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

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

Las condiciones de correccin mnimas que satisface esta solucin son: Los barcos pueden cruzar el puente cuando est levantado. Para levantarlo no debe haber ningn coche cruzando. Los coches pueden cruzar el puente cuando est bajado. Para bajarlo no debe haber ningn barco cruzando. Inicialmente, todos los semforos valen 1 y los contadores valen 0. Conteste a las siguientes preguntas suponiendo que los semforos tienen asociada una cola FIFO. a) Indique el estado de las colas asociadas a los semforos y los procesos que estn utilizando el puente despus de invocar las siguientes operaciones (se considerar que el instante final de la operacin es cuando sta acaba o cuando el proceso que la invoca se suspende). C0 invoca entrar_coche. B0 invoca entrar_barco. C1 invoca entrar_coche. B1 invoca entrar_barco. b) Indique el estado de las colas asociadas a los semforos y los procesos que estn utilizando el puente despus de invocar las siguientes operaciones: Todos los coches / barcos que han entrado en el puente en el apartado anterior han invocado salir_coche/ salir barco. C2 invoca entrar_coche. B2 invoca entrar_barco. C3 invoca entrar_coche. c) Qu ocurre si, hay coches en el puente, hay barcos esperando y un nuevo coche invoca entrar_coche?. d) Qu ocurre si hay barcos cruzando bajo el puente, hay coches esperando y un nuevo barco invoca entrar_barco? e) Qu ocurre si hay coches y barcos esperando y el ltimo barco bajo el puente invoca salir_puente? f) Como modificara el cdigo para que los coches pasaran de uno en uno (no necesita reescribirlo todo).

Ej. 7-5

a) C0 pasa, B0 se detiene en libre pero coge el turno, C1 se bloquea en turno que ha cogido B0, B1 no puede entrar porque B0 no ha liberado mutex2. Es decir:
variable contador 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), B1 pasa al liberar B0 mutex2, C2 llega y se suspende en turno, que an no ha sido liberado por los barcos, B2 llega y pasa, C3 llega y se suspende en turno.
contador Variable mutex1 1 mutex2 1 turno -3 libre 1 En el puente se encuentran B0, B1, B2 Procesos suspendidos

C1, C2, C3

c) El coche se suspende en el semforo turno. d) El nuevo barco cruza bajo el puente. e) Situacin imposible. f) En entrar_coche se suprime la llamada sem_post(&mutex1) y en salir_coche se suprime la llamada sem_post(&mutex1). Al no liberar el semforo mutex1 hasta despus de salir del puente, si llega un nuevo coche y tiene el turno, se suspender en mutex1.

Se pretende hacer un programa concurrente para simular un aeropuerto de pequeo tamao en el que se dispone de una nica pista, que utilizan tanto los aviones que despegan como los que aterrizan. El comportamiento ha de ser el siguiente: - Slo un avin puede estar utilizando la pista en cada momento, si un avin llega a la pista con intencin de despegar y en ese momento hay otro avin despegando o aterrizando, entonces se ha de esperar. - Igualmente, si un avin llega desde el aire y en ese momento hay otro avin utilizando la pista (despegando o aterrizando), tambin habr de esperar. - Cuando un avin termina de utilizar la pista, si haba algn otro avin esperando podr pasar a utilizarla, teniendo prioridad los aviones que desean aterrizar (la inanicin de stos podra llevar a que agotaran su combustible). Se pide implementar el programa basndose en el monitor que se muestra a continuacin. Completar las operaciones de acceso del monitor que aparecen en el cdigo. Se pueden aadir ms variables si fueran necesarias.
/**** variables globales ****/ pthread_mutex_t mutex_monitor = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond_despegar = PTHREAD_COND_INITIALIZER; pthread_cond_t cond_aterrizar = PTHREAD_COND_INITIALIZER; int ocupado=FALSE; int w_aterriza=0; /*** mtodos del monitor ****/ void inicia_despegue(){ pthread_mutex_lock(&mutex_monitor); while(ocupado || w_aterriza>0)

Ej. 7-6

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

Suponga que se encuentra en una discoteca donde est estropeado el servicio de las chicas y todos deben compartir el de los chicos. Se pretende establecer un protocolo de entrada al servicio utilizando semforos en el que se cumplan los siguientes requisitos: Slo puede haber una chica cada vez en el servicio. Puede haber ms de un chico a la vez, pero con un mximo de cinco. Las chicas tienen preferencia sobre los chicos. Esto quiere decir que si un chico est esperando y llega una chica, sta debe pasar antes. Complete el cdigo que se propone a continuacin para que se cumpla dicho protocolo, utilice las variables que ya han sido declaradas e inicializadas en el mismo.

Ej. 7-7

#include <semaphore.h> sem_t libre, turno, cinco,mutex_os,mutex_as; int num_chicos, num_chicas; //*****CHICOS**************// void chicos() { sem_wait(&cinco); sem_wait(&turno); sem_post(&turno); sem_wait(&mutex_os); /***COMPLETAR****/ /***ENTRADA CHICOS****/

//*****CHICAS**************// void chicas() { sem_wait(&mutex_as); /***COMPLETAR****/ /***ENTRADA CHICAS****/


num_chicas++; if (num_chicas==1)sem_wait(turno)

sem_post(&mutex_as); sem_wait(&libre); utiliza_servicio();

sem_post(&libre);
num_chicos++; if (num_chicos==1)sem_wait(libre)

sem_post(mutex_os); utiliza_servicio(); sem_wait(&mutex_os); /***COMPLETAR****/ /***SALIDA CHICOS****/


num_chicos--; if (num_chicos==0)sem_post(libre)

sem_wait(&mutex_as); /***COMPLETAR****/ /***SALIDA CHICAS****/


num_chicas--; if (num_chicas==0)sem_post(turno);

sem_post(&mutex_os); sem_post(&cinco);

sem_post(&mutex_as); } /***********************// int main() { int hilos_chicas, hilos_chicos; num_chicos=0; num_chicas=0; sem_init(&mutex_os,0,1); sem_init(&mutex_as,0,1); sem_init(&libre,0,1); sem_init(&turno,0,1); sem_init(&cinco,0,5);

Disee un monitor que garantice la utilizacin de una piscina (recurso P) a la que slo acceden entrenadores de natacin (hilos entrenadores E) y nios (hilos nios N), segn la siguiente normativa: Los hilos E invocan la operacin 'entraE' para acceder a la piscina, permanecen en ella (ejecutan cdigo arbitrario), y luego invocan 'saleE' para abandonarla Los hilos nios N invocan la operacin 'entraN' para acceder a la piscina, permanecen en ella (ejecutan cdigo arbitrario), y luego invocan 'saleN' para abandonarla. Los entrenadores, hilos E, pueden utilizar la piscina P simultneamente. Los nios, hilos N, slo la pueden utilizar la piscina P si hay al menos un entrenador, hilo E, utilizndola. Los hilos N nunca pueden utilizar la piscina P sin hilos E. o E....E ok o E....E N....N ok o N....N ilegal Se pide disear una solucin utilizando monitores para garantizar las condiciones anteriores.

Ej. 7-8

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

Sea una central de distribucin elctrica a la que se pueden conectar generadores y consumidores. Por requerimientos tcnicos, slo pueden conectarse un mximo de Ng generadores, mientras que no hay lmite en la conexin de consumidores, aunque para evitar sobrecargas, slo se aceptan nuevas conexiones de consumidores mientras el nmero de consumidores no supere el triple de los generadores conectados en ese

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

Ej. 7-9

/**** variables globales ****/ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond_gene = PTHREAD_COND_INITIALIZER; pthread_cond_t cond_cons = PTHREAD_COND_INITIALIZER; int n_gene=0; Int n_cons=0;

/*** funciones a implementar ****/ void conexin_generador() { pthread_mutex_lock(&mutex); while(n_gene>=Ng) pthread_cond_wait(&cond_gene,&mutex); n_gene++; pthread_cond_broadcast(&cond_cons); pthread_mutex_unlock(&mutex); } void desconexion_ generador() { pthread_mutex_lock(&mutex); n_gene--; pthread_cond_signal(&cond_gene); pthread_mutex_unlock(&mutex); } void conexin_consumidor() { pthread_mutex_lock(&mutex); while(n_cons>=3*n_gene) pthread_cond_wait(&cond_cons,&mutex); n_cons++; pthread_mutex_unlock(&mutex); } void desconexin_consumidor() { pthread_mutex_lock(&mutex); n_cons--; pthread_cond_signal(&cond_cons); pthread_mutex_unlock(&mutex); }

Una lnea de comunicaciones dispone de N canales para realizar llamadas telefnicas. Cada llamada telefnica usa un canal y lo libera al finalizar la conversacin. Las llamadas telefnicas pueden ser de tres tipos: EMERGENCIA VIP ORDINARIA Se pide disear 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 estn ocupados, la prioridad de acceso a la comunicacin ser: EMERGENCIA > VIP > ORDINARIA
Nota: nicamente completar el cdigo 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 prcticas.


8.1 Dificultad de los ejercicios
Nivel de dificultad Principiante Medio Avanzado Ejercicios

8.2 Ejercicios
Recordando lo realizado en la prctica de microshell sobre el tratamiento de seales, se pide escribir las sentencias que implementan la siguiente gestin de seales: El interprete de comandos debe ignorar las seales SIGINT, SIGQUIT, SIGTTIN y SIGTTOU Los procesos hijos deben ignorar las seales SIGINT y SIGQUIT y el resto de seales deben tener el tratamiento por omisin.

Escribir el cdigo del intrprete 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 solucin al problema de los 5 filsofos. Analice las condiciones de Coffman. Es posible que se produzcan interbloqueos?. En caso afirmativo modifique el cdigo 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]); } }

S es posible que se produzca un bloqueo. Si trylock da como resultado que el recurso est ocupado (EBUSY) entonces se intenta coger mediante un lock bloqueante, que evidentemente va a suspender el proceso. Se dan por tanto todas las condiciones de Coffman y existe riego de interbloqueo. Para resolverlo bastara con eliminar el segundo pthread_mutex_lock(), situado tras el trylock. Lo anterior es una utilizacin inadecuada de trylock. Casi parece un error de programacin al intentar escribir un cdigo correcto. Se dar como vlida tambin la respuesta en la que se supone una utilizacin adecuada de trylock: En este ltimo caso, no son posibles los interbloqueos porque se incumple la condicin de retener y esperar (asignacin gradual de recursos).

Explica para qu sirve la estructura CMDFD utilizada en la prctica de microshell.


typedef struct { int infd; int outfd; }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 tubera. 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 cdigo corresponde a proyecto microshell desarrollado en prcticas. Teniendo en cuenta el cdigo que se incluye y la funcionalidad que debe implementar la funcin pipeline() se pide escribir el cdigo que se ha omitido en las posiciones indicadas con los comentarios Completar 1, Completar 2 y Completar 3.
typedef struct { int infd; int outfd;

}CMDFD[PIPELINE]; CMDFD fd_pipes; CMDFD { int int int int * pipeline(CMD * ordenes) nordenes; i; pfdes[2]; fdin, fdout;

iniciar_pipes(); nordenes=ordenes->num_ordenes; for(i=0;i<nordenes-1;i++) { pipe(pfdes);

// Completar 1
} if(ordenes->fich_entrada[0]!='\0') { if ( (fdin=open(ordenes->fich_entrada,O_RDONLY)) == -1) {perror("No puedo abrir el fichero de entrada");exit(1);}

// Completar 2
} else {

// Completar 3
} // sigue el cdigo que no se incluye en el ejercicio. return(&fd_pipes); } /* pipeline */

Ej. 8-4

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

Completar 2
fd_pipes[0].infd=fdin;

Completar 3

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

Dado el siguiente fragmento de cdigo del programa de prcticas microshell correspondiente a la funcin ejecutar:
int ejecutar (int nordenes , int *nargs , char **ordenes , char ***args , int bgnd) { for (i=0; i <nordenes; i++) { if ((pidhijo=fork())==0) { if (cmdfd[i].infd !=0 )

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

Indicar: a) El cdigo ejecutado por el padre y el ejecutado por el hijo (mrquelo en el mismo cdigo) 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.- el padre ejecuta slo el ltimo cerrar_fd(). b.- dup duplica el descriptor que se le pasa como parmetro y lo hace en el primer hueco de la tabla de descriptores que encuentra. Esta bsqueda del hueco se realiza por orden creciente de descriptores comenzando por 0. Por lo tanto el close se aplica sobre el descriptor de fichero destino de la duplicacin. La combinacin close;dup es equigalente a dup2. c.- la funcin cerrar_fd cierra todos los descriptores de fichero abiertos (tubos y ficheros) excepto los descriptores 0, 1 y 2. La llamada a esta funcin se realiza despus de duplicar adecuadamante los descriptores que usar el proceso en cuestin y as evitar que un proceso mantenga descriptores de fichero abiertos que no va a utilizar. Esto evita que un proceso se quede bloqueado en una llamada read sobre un tubo al mantener otro proceso (o l minsmo) un descriptor de escritura sobre ese tubo que no va a usar.

Conocido el problema de los cinco filsofos y la solucin del comedor:


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

Ej. 8-5

Explicar: a) Para qu sirve la variable cuantos_dentro? b) Para qu sirve la variable puerta_comedor? c) Para qu sirve la variable mutex_del_contador?

Ej. 8-6

a.- Para registrar el nmero de filsofos a los que se les ha permitido pasar a comer al interior del comedor. b.- Para retener a los filsofos a los que no se les permite comer. c.- Para que no se produzca condicin de carrera en el acceso a cuantos_dentro. Adems el uso de variables condicin obliga al uso de mutex. Esto es necesario por que la evaluacin de la condicin booleana es siempre parte de la seccin crtica de acceso a las variables internas del monitor.

Construya una lnea de rdenes, tal que, al ejecutarla con el microshell desarrollado en prcticas, 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.txt Descriptor 7

Ej. 8-7

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

En el cdigo fuente del microshell, la funcin pipeline tiene como funcin principal inicializar e incluir en el vector cmdfd los descriptores de fichero necesarios para que las funciones de redireccin trabajen correctamente. Tenga en cuenta la estructura tpica de microshell desarrollado en el laboratorio y calcule, justificando su respuesta, el nmero de descriptores que tendr abierto el proceso microshell (padre) y a qu dispositivos o archivos apuntan cada uno de ellos tras la invocacin a la funcin pipeline. En particular, calcule estos descriptores cuando el usuario introduzca las siguientes rdenes por el teclado. a) $cat < file1 | grep perico & b) $cat | grep perico c) $cat > file1 & d) $cat <file1 | grep perico >> file2 & Nota: No es necesario que dibuje el vector cmdfd. Ejemplo: 0,1,2 /dev/tty ; 3 fichentrada.txt.

Ej. 8-8

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

/dev/tty

En el problema de los 5 filsofos (filsofos numerados como 0..4, tenedores numerados como 0..4), suponemos que cada filsofo pide primero el tenedor de menor ndice, y luego el de ndice mayor. Indica cmo afecta esa decisin al problema de los interbloqueos, y porqu.

Ej. 8-9

La ordenacin parcial de recursos hace que no se produzca espera circular, que es una condicin de Coffman, y por lo tanto es imposible que se produzca un interbloqueo.

Al problema tradicional de los 5 filsofos, se le han incorporado 5 nuevos filsofos, en total 10 filsofos. Los 5 primeros filsofos, numerados del 0 al 4, siguen necesitando 2 tenedores para comer, mientras que los 5 filsofos nuevos, numerados del 5 al 9, slo necesitan un tenedor cada uno, en concreto el tenedor derecho. La mesa que tienen para comer los 10 filsofos, es la tradicional con 5 platos y 5 tenedores, de manera que: para los filsofos 0 y 5 su tenedor derecho es el 0 y su izquierdo el 1, para los filsofos 1 y 6 su tenedor derecho es el 1 y su izquierdo el 2, etc. Tomando como base la solucin al problema tradicional de los cinco filsofos (en la que cada filsofo necesita dos tenedores para comer), donde se establece un protocolo de entrada al comedor de manera que slo entran 4 filsofos simultneamente, y cuyo cdigo se presenta a continuacin.
1 void filo(void * arg) 2 { 3 nfilo=(int) arg; 4 while(1){ 5 //PROTOCOLO ENTRADA en el comedor 6 pthread_mutex_lock(&m); 7 while(dentro==4) 8 pthread_cond_wait(&vc,&m); 9 dentro++; 10 pthread_mutex_unlock(&m); 11 //**FIN PROTOCOLO DE ENTRADA*******// 12 Si (nfilo<5) intento coger dos tenedores 13 Si (nfilo>4) intento coger el derecho 14 /***como comiendo**/ 15 Si (nfilo<5) dejo dos tenedores 16 Si (nfilo>4) dejo el tenedor derecho 17 //PROTOCOLO DE SALIDA del comedor. 18 pthread_mutex_lock(&m) 19 dentro--; 20 pthread_cond_signal(&vc); 21 pthread_mutex_unlock(&m); 22//**FIN PROTOCOLO DE ENTRADA*******// 23 pienso en si existo 24 } 25 }

Se pide: Modifique los protocolos de entrada y salida al comedor que aparecen en el pseudocdigo anterior. Para ello aada/elimine las instrucciones necesarias de manera que puedan comer los 10 filsofos con los 5 tenedores en las condiciones descritas, controlando la entrada al comedor para evitar interbloqueo y con una utilizacin lo ms eficiente posible de los tenedores. Justifique su implementacin.

Ej. 8-10

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

pthread_cond_wait(&vc,&m);

pthread_mutex_unlock(&m); B) Protocolo de salida Pthread_mutex_lock(&m) if (nfilo<5) dentro--; pthread_cond_signal(&vc); pthread_mutex_unlock(&m);

Considere el microshell desarrollado en el laboratorio. Si el usuario que utiliza el microshell introduce por teclado la orden: cat < file1 | sort > file2 y teniendo en cuenta el siguiente pseudocdigo correcto. Qu ocurrira si se omite la lnea 18? Justifique su respuesta.

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

Ej. 8-11
}

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

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

for (cont = 0; cont < REPETICIONES; cont = cont + 1) { while (test_and_set(&llave)==1) {}; temp=V; temp=temp-1; V = temp; llave=0; } printf("-------> Fin RESTA pthread_exit(0); } (V = %ld)\n", V);

Se pide dibujar el cronograma de ejecucin de los dos hilos, suponiendo que REPETICIONES=5 y que cada iteracin del bucle for tarda 1 milisegundo en ejecutarse,

En el problema de los filsofos, cada tenedor es un recurso compartido que nicamente puede ser accedido por un filsofo cada vez. En la implementacin que se ha estudiado en las prcticas de la asignatura, cada tenedor se ha representado mediante un mutex. Para cada uno de los supuestos siguientes, indique qu llamada realiza el hilo filsofo y cmo afecta dicha llamada al estado de ejecucin de los filsofos. a) Un filsofo intenta coger un tenedor que ya est siendo utilizado por otro filsofo b) Un filsofo intenta coger un tenedor que est libre c) Un filsofo suelta el tenedor

Ej. 8-12 Ej. 8-13

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

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

Dado el siguiente fragmento de cdigo correspondiente al problema de los cinco filsofos:


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

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

Punto 1
} else {

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

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

d) Se pide completar el cdigo en los puntos 1, 2 y 3.

Ej. 8-14 Ej. 8-15

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

Punto 2
los_tengo = true;

Si consigue coger el tenedor izquierdo debe actualizar la variable _los_tengo, para salir del bucle y poder indicar que est comiendo

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

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 parmetro, de manera que si el mutex est abierto, lo cierra, devolviendo como valor de retorno un 0. En el caso en el que el mutex est cerrado cuando se invoca la llamada, sta no suspende al hilo invocente, lo que hace es devolver el cdigo de error EBUSY

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

Ej. 8-16 Ej. 8-17

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

Recordando lo realizado en la prctica de microshell sobre el tratamiento de seales, se pide escribir las sentencias que implementan la siguiente gestin de seales: El intrprete de comandos debe ignorar las seales SIGINT, SIGQUIT, SIGTTIN y SIGTTOU Los procesos hijos deben ignorar las seales SIGINT y SIGQUIT y el resto de seales deben tener el tratamiento por omisin. Escriba el cdigo del intrprete y de los hijos

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