PRÁCTICAS DE LABORATORIO DE SISTEMAS OPERATIVOS

PRIMERA PRÁCTICA EVALUABLE (2005-06)

Infinito


  1. Enunciado.

    El programa que hay que presentar constará de un único fichero fuente de nombre infinito.c. La correcta compilación de dicho programa, producirá un fichero ejecutable, cuyo nombre será obligatoriamente infinito. 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 forma que formen una figura semejante al símbolo de infinito:
    Las relaciones paterno-filiales son las indicadas por las líneas sólidas.

    Durante treinta segundos, deben estar circulando señales entre los procesos en el sentido que indican las flechas rojas de la figura. El tipo de señal o señales usadas se deja libre. El proceso padre es el que controlará estos treinta segundos (no usad sleep).

    Transcurridos los treinta segundos, el proceso padre dejará de enviar señales, arbitrará un mecanismo para que los procesos mueran (o se maten), esperarán a que hayan muerto y escribirá por la salida estándar "La señal ha dado x vueltas.", donde x será el número de vueltas que ha dado la señal a los procesos del dibujo.

    Los procesos, una vez creados, deben permanecer bloqueados, sin consumir CPU mientras esperan a recibir la señal y enviar esa u otra al siguiente proceso.

    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, 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 "infinito$" | 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 jueves, 23 de marzo de 2006, 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 H1, H2, H3 y H4. Compiladlo, ejecutadlo, comprobadlo con Arbol_dominO y depuradlo, si fuera necesario.
      2. Repetid el procedimiento con los procesos N2 y N3. Alternativamente, podéis intentar generar los procesos mediante un bucle, sin hacerlo en el código explícitamente.
      3. Una vez el árbol esté bien generado, comprobad que los procesos no consumen CPU con top.
      4. Es mejor que a continuación resolváis el problema de matar a los procesos y los treinta segundos. Debéis usar SIGALRM en el proceso padre para que, transcurridos los treinta segundos salte a la manejadora y mate todos los procesos. El esquema para que los procesos mueran lo podéis hacer como gustéis, aunque lo más natural es que los padres vayan matando a los hijos y esperen por su muerte con waits. De momento, que el proceso padre ponga que se han dado cero vueltas. También, para trabajar y depurar con más comodidad, sustituid temporalmente los treinta segundos por cinco.
      5. Localizad en vuestro programa el código que ejecuta el proceso H2. Construid para ese proceso una manejadora que intercepte SIGUSR1, por ejemplo. Dentro de esa manejadora, poned un mensaje indicando que se ha recibido la señal. Que P le mande la señal a H2. Comprobad y corregid si no funciona.
      6. Localizad el código de N2 y repetid lo hecho con H2 en el paso anterior. Dentro de la manejadora de H2 le enviáis la señal a N2. Comprobad y corregid si no funciona.
      7. Repetid el esquema para la pareja N2-H1. Aquí os encontraréis con un problema que debéis discurrir cómo resolverlo. Para que N2 mande la señal a H1 es necesario que conozca su PID. No podéis usar un fichero para transmitirlo.
      8. La siguiente dificultad os la encontraréis cuando H4 tenga que mandar la señal a P, justo al final del recorrido. P debe diferenciar la señal que le llega de H4 de la que le llega de H1. Hay que volver a pensar.
      9. Completad la práctica haciendo que el padre cuente las vueltas y completando los detalles del enunciado que os falten.
    2. Para matar los procesos después de cada ejecución, hay una manera muy cómoda. Si habéis usado "infinito &" para ejecutar la práctica, podéis usar "kill %" para matar a todos los procesos de golpe.
    3. No se puede usar sleep() o similares para sincronizar los procesos. Hay que usar otros mecanismos.
    4. 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.
    5. Evitad, en lo posible, el uso de variables globales. Tenéis la posibilidad de declarar variables estáticas.
    6. 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.
    7. 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.
    8. 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.
    9. ¿Qué hago cuando mi programa se desboca para no perjudicar el funcionamiento de la máquina?
      Solución.
    10. La práctica en ejecución os va a consumir CPU. La razón es que las señales están circulando sin descanso. Esto es normal siempre que tengáis bien programado el sigsuspend. Un efecto curioso del que se ha dado cuenta un compañero es que el proceso padre consume el doble de CPU que los demás debido a que por él las señales pasan dos veces en una vuelta. Esto es síntoma de que la práctica funciona.


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


© 2006 Guillermo González Talaván y Susana Álvarez Rosado.