PRÁCTICAS DE LABORATORIO DE SISTEMAS OPERATIVOS

PRIMERA PRÁCTICA EVALUABLE (2006-07)

Bolos


  1. Enunciado.

    El programa que hay que presentar constará de un único fichero fuente de nombre bolos.c. La correcta compilación de dicho programa, producirá un fichero ejecutable, cuyo nombre será obligatoriamente bolos. Respetad las mayúsculas/minúsculas de los nombres, si las hubiere.

    La ejecución del programa creará una serie de procesos que se transmitirán señales entre ellos. En concreto, el árbol de procesos que hay que crear es el siguiente:


    Para comprender mejor el funcionamiento de la práctica, cambiemos la posición de los procesos en el dibujo de manera que formen una figura semejante a la posición de partida de los bolos en una bolera
    Las relaciones paterno-filiales son las indicadas por las líneas sólidas.

    Una vez construido el arbol, el proceso padre muere y los demás permanecen vivos, bloqueados y sin consumir CPU. En este momento, habremos recuperado el control desde nuestra shell y enviaremos al proceso A una señal SIGTERM. La señal se va a ir transmitiendo a otros procesos según el esquema que se indicará más abajo, volcando a su paso otros bolos.

    Transcurridos tres segundos, el proceso A comprobará cuántos procesos siguen vivos, imprimirá un dibujo con la situación por la pantalla, ejecutará una orden ps -fu usuario que sirva de comprobación y acabará, no dejando ningún proceso vivo.

    En el siguiente dibujo, se observa cómo se propagan las señales:
    Si un proceso recibe la señal SIGTERM y no es un proceso del final (no es ni G, ni H, ni I, ni J) puede hacer una de estas cuatro cosas:
    1. No enviar la señal a ningún proceso.
    2. Enviar la señal al proceso situado debajo a la izquierda.
    3. Enviar la señal al proceso situado debajo a la derecha.
    4. Enviar la señal a ambos procesos.
    El que realice una u otra acción va a depender del reloj del sistema. Consultará los microsegundos marcados por el reloj del sistema mediante una llamada gettimeofday. Dividirá el valor entre cuatro y obtendrá el resto. Dependiendo del valor del resto de la división (0, 1, 2 ó 3) se realizará la acción correspondiente de la lista anterior.

    Para que el proceso A sepa si siguen vivos los bolos E, H o I, podéis usar la llamada al sistema waitpid en su versión no bloqueante. Para saber qué ha ocurrido con los procesos de la ristra B-D-G, se procederá así:
    1. Si A no mandó la señal a B, sabe que los tres están en pie.
    2. Si A mandó la señal a B, hará un wait bloqueante y B le dirá en su código de retorno cuántos bolos de la ristra B-D-G siguen en pie. Para que B sepa cuántos bolos quedan en pie se hace lo mismo, pero ahora el papel de A lo ocupa B y el de B lo ocupa D.
    En el caso de la ristra C-F-J, se procede de modo equivalente.

    Ejemplo: en el caso de la ristra B-D-G, supongamos que A mató a B y B mató a D, pero D no mató a G. En ese caso, D devuelve a B un uno, indicando que hay un bolo en pie y B devuelve a A otro uno. A sabe que queda un bolo en pie de la ristra, por lo que tiene que ser G a la fuerza.

    Los procesos, una vez creados, deben permanecer bloqueados, sin consumir CPU mientras esperan a recibir la señal y enviarla a otros procesos, si procede.

    Si se requiere sincronizar alguna acción, se pueden usar señales, pero la espera se debe realizar sin consumo de CPU. Para simplificar el problema, podéis suponer que al ser mandada la señal inicial manualmente, ya ha habido tiempo suficiente para que todos los procesos hayan sido creados para cuando el primer proceso la reciba. Sin embargo, esta concesión en aras de simplificación, no implica suponer ninguna condición, incluida ésta, en ninguna otra parte de la práctica.

    Para ver si habéis conseguido crear el árbol de procesos correctamente, al menos en la primera fase, podéis descargaros el siguiente ejecutable: Arbol_dominO. No os va a funcionar en Linux, pues es un ejecutable binario y los procesadores son diferentes. Desde el servidor, para ver el árbol de procesos que habéis generado, usad la orden: ps -f | grep "bolos$" | grep -v grep | sort | Arbol_dominO. Si os aparece el mensaje "Árbol con múltiples raíces. Se aborta.", significa que habéis corrido varias veces vuestra práctica sin matar los procesos de la anterior. En el caso de Linux, podéis usar la opción -f de la orden ps.

  2. Restricciones



  3. Plazo de presentación.

    Hasta el viernes, 23 de marzo de 2007, inclusive.

  4. Normas de presentación.

    Acá están.

  5. LPEs.

    1. Las tareas que tiene que realizar el programa son variadas. Os recomiendo que vayáis programándolas y comprobándolas una a una. No es muy productivo hacer todo el programa de seguido y corregir los errores al final. El esquema que os recomiendo seguir para facilitaros la labor se os muestra a continuación:
      1. Haced un pequeño programa que cree los procesos P y A. Compiladlo, ejecutadlo, comprobadlo con Arbol_dominO y depuradlo, si fuera necesario.
      2. Lograd que P muera y A se quede solo.
      3. Cread ahora los procesos B, H, E, I y C. Alternativamente, podéis intentar generar los procesos mediante un bucle, sin hacerlo en el código explícitamente.
      4. Acabad generando las dos ristras B-D-G y C-F-J, por el mismo procecimiento.
      5. Una vez el árbol esté bien generado, comprobad que los procesos no consumen CPU con top.
      6. Se debe lograr que los procesos recién creados cambien el nombre que aparece al hacer ps. Para ello, un buen truco consiste en que ejecuten un exec que conduzca a su propio programa otra vez pero en el que hacemos que argv[0] pase a ser el nombre deseado. Al entrar por el main, miramos el valor de argv[0] y sabemos que si vale bolos es la entrada inicial y si vale A es la segunda entrada del proceso A y lo podemos dirigir a una función propia.
      7. Ahora debéis conseguir que A no se muera cuando le mandéis la señan SIGTERM sino que imprima algo por la pantalla, por ejemplo.
      8. Llegamos a la parte más difícil. Para empezar, debemos hacer que los procesos propaguen la señal SIGTERM. Es mejor que al principio la propagen a los dos que tienen debajo siempre. Es probable que os encontréis con problemas para que un proceso consiga los PIDs de los procesos que tiene que matar. Os recomiendo que antes de matar, impriman lo que van a hacer y el PID de quien van a matar por la pantalla. Es probable que llegados a este punto tengáis que cambiar el orden en que se crean los procesos...
      9. Una vez todos matan a los que tienen que matar, introducid la condición del gettimeofday.
      10. Haced que A espere tres segundos. Podéis usar la función de biblioteca sleep. A continuación que mire si E, H e I siguen vivos y lo ponga por la pantalla.
      11. Nos ocupamos ahora de la ristra B-D-G. Si A mató a B, que haga un wait y pida el código de retorno. Si B mató a D que coja su código de retorno y se lo trasmita a A. Si no, devuelve a A un 2. Si D mató a G, hace un wait y devuelve a B un cero. Si no es así, devuelve un uno. A imprime con esta información cuántos están vivos.
      12. Hacer lo mismo con la ristra C-F-J.
      13. Borrad la información de depuración y que A imprima de forma bonita qué bolos quedan vivos.
      14. A finaliza ejecutando el ps para que podamos comprobar que todo está bien.
    2. No se puede usar sleep() o similares para sincronizar los procesos. Hay que usar otros mecanismos.
    3. Sabéis que si usáis espera ocupada en lugares donde explícitamente no se haya dicho que se puede usar, la práctica está suspensa. No obstante, existe existe una variedad de espera ocupada que podríamos denominar espera semiocupada. Consiste en introducir una espera de algún segundo en cada iteración del bucle de espera ocupada. Con esto el proceso no consume tanta CPU como en la espera ocupada, pero persisten los demás problemas de la técnica del sondeo, en particular el relativo a la elección del periodo de espera. Aunque la práctica no estará suspensa si hacéis espera semiocupada, se penalizará en la nota bastante si la usáis. En conveniente que la evitéis.
    4. Evitad, en lo posible, el uso de variables globales. Tenéis la posibilidad de declarar variables estáticas.
    5. Tened cuidado con el uso de pause(). Salvo en bucles infinitos de pauses, su uso puede estar mal. Mirad la solución a la práctica propuesta en la sesión quinta acerca de él o el siguiente LPE.
    6. Es conveniente que, antes de entregar la práctica, comprobéis que los kills están enviando la señal a los procesos correctos. Si al imprimirlo resulta que a veces mandan la señal al proceso "0", es que está mal. Lo que ocurre es que el proceso que realiza el kill recibe la señal y salta a la manejadora antes de actualizar la variable que contiene el pid de su hijo. Por eso la variable tiene 0. La solución consiste en que el patriarca bloquee la señal y cada proceso la desbloquee justo antes de quedarse parado y después de haber hecho todas sus tareas.
    7. El programa que he hecho se para a veces. O en mi casa se para, pero en clase, no. O en clase sí, pero en casa, no.
      Solución.
    8. ¿Qué hago cuando mi programa se desboca para no perjudicar el funcionamiento de la máquina?
      Solución.
    9. Habréis observado, como no puede ser de otro modo, que al ejecutar un exec las variables de un proceso se borran. Es natural pues el proceso vuelve a cargar el programa y comienza de cero con la función main. Así es muy difícil pasar un pid a otro proceso que lo necesite. Una solución es usar argumentos adicionales en la llamada a exec de modo que se reciba la información en argv[1], argv[2], ...
    10. Para resolver el problema de que el proceso A tiene, al final, que ejecutar un ps y matar a todos los procesos, podéis hacer que A tenga un hijo que ejecute el ps al hacer un exec. El proceso A esperará a que el hijo acabe con un wait y matará a todos los procesos que queden vivos.
    11. Para que A al final pueda matar a todos los procesos que queden vivos, podéis intentarlo mandando una señal SIGINT en un kill desde A, indicando como PID un 0. Comentadme si es efectivo.
    12. Es evidente que si A tiene que hacer un ps o mandar hacerlo, va a aparecer en la lista como vivo, aunque represente a un bolo muerto. No os preocupéis por este detalle. Solucionarlo complicaría la práctica innecesariamente.


  6. Prácticas propuestas en años anteriores.


© 2007 Guillermo González Talaván y Ana Belén Gil González.