PRÁCTICAS DE SISTEMAS OPERATIVOS I

SEGUNDA PRÁCTICA EVALUABLE (2020-21)

Tú la llevas


  1. Enunciado.

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

    La ejecución del programa creará una serie de procesos que accederán a una zona de exclusión mutua. La regulación del acceso a dicha zona se hará mediante el paso de un testigo.

    Los procesos se dispondrán en círculo. Mientras ningún proceso quiera entrar en la sección crítica, el testigo circula por el círculo de procesos. Cuando un proceso quiera entrar, ha de esperar a estar en posesión del testigo. Entrará entonces en la sección crítica. Cuando salga, procederá con la circulación del testigo.

    La invocación de la práctica se hará con dos argumentos, el último de ellos opcional:
       lallevas n_procs [debug]
    
    El primero es un número entero comprendido entre 3 y 33. Es el número de procesos que participan en la práctica. De ellos, uno es el padre y el resto son hijos suyos. El modo en que se forma el corro para la circulación del testigo se deja a la libertad de los autores.

    Si el segundo argumento es la palabra debug, la práctica se ejecuta en modo de depuración. Las características de este modo se explican más abajo. Si los parámetros introducidos no respetan las reglas anteriores, el programa lo detectará, informará al usuario y acabará.

  2. Funcionamiento de cada proceso

    Durante el período de funcionamiento de la práctica, los procesos estarán en el siguiente bucle infinito:
    1. Si estamos en el modo de depuración, duerme 1 segundo. Si no, no hace nada.
    2. Entra en la sección crítica.
    3. Si estamos en el modo de depuración, imprime en la pantalla: E(pid). Si no, imprime solamente una E. En ningún caso imprimirá un salto de línea.
    4. Si estamos en el modo de depuración, duerme 2 segundo. Si no, no hace nada.
    5. Si estamos en el modo de depuración, imprime en la pantalla: S(pid). Si no, imprime solamente una S. En ningún caso imprimirá un salto de línea.
    6. Sale de la sección crítica.
    Viendo la salida por pantalla, es evidente que para que la práctica funcione es necesario que aparezcan en alternancia perfecta, Ees y eSes.

    El paso del testigo se va a simular mediante el envío de señales. Cuando un proceso en posesión del testigo, desea pasárselo a otro, le enviará la señal SIGUSR1. Un proceso que no tenga el testigo pasa a poseerlo cuando reciba la señal. Si debe reenviarlo, esperará al menos una décima y media de segundo si estamos en el modo de depuración. De no estar en dicho modo, el reenvío será inmediato. Para efectuar esa pausa, se usará la función nanosleep (mirad la página de manual).

    Para hacer que el proceso duerma en el resto de casos, se usará la señal SIGALRM, nunca la función sleep. Dormir hará que el proceso, en modo de depuración, apenas consuma CPU. Lo podéis comprobar con la orden top.

    Para que el buffer intermedio usado por printf no interfiera con la salida de los procesos, es importante usar write para la salida por pantalla en su lugar.

  3. Finalización ordenada

    La práctica acabará cuando el usuario pulse CTRL-C. Los procesos deben morir y el padre, una vez hayan muerto todos imprimirá un salto de línea y la frase: "Programa acabado correctamente".


  4. Restricciones



  5. Plazo de presentación.

    Consultad la entrada de la página web de la asignatura.


  6. Normas de presentación.

    Acá están.


  7. 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 un hijo y deje a padre e hijo en un pause(). Compiladlo, ejecutadlo, comprobadlo con ps -fu y depuradlo, si fuera necesario.
      2. El siguiente paso es modificar el programa para que tenga 7 hijos.
      3. Si todo va bien, lograd que se tengan tantos hijos como los especificados en una variable.
      4. Es el momento de que se creen tantos hijos como se indique en la línea de órdenes.
      5. Completad el tratamiento de la línea de órdenes, añadiendo la posibilidad de debug y detectando errores.
      6. Ahora empecemos a programar la finalización ordenada. Al pulsar CTRL+C, el padre debe esperar por la muerte de los hijos e imprimir por pantalla su mensaje. Registrará para ello la señal SIGINT.
      7. El siguiente paso consiste en hacer circular el testigo. Debéis registrar la señal SIGUSR1 y diseñar un esquema de circulación de la señal. Haced la pausa del modo de depuración. Los procesos pueden imprimir su pid cada vez que reciban el testigo para poder depurar. Uno de los procesos debe comenzar la circulación del testigo.
      8. Si no funciona, pensad dónde se está recibiendo la señal y si la solución que planteamos es válida se reciba donde se reciba. Pensad que también puede estar recibiéndose la siguiente señal en la propia manejadora. Si no funciona, una solución es bloquear con sigprocmask SIGUSR1 en la zona conflictiva y desbloquearla cuando no tenga peligro el recibirla (quizá en un sigsuspend).
      9. El resto del camino no es más fácil. Debéis programar la entrada y salida de la sección crítica y las pausas correspondientes, registrando la señal SIGALRM. El problema fundamental de esta etapa es que tenéis que poner orden en la posible recepción concurrente de varias señales.
      10. Si veis que la cosa se complica, quizá podéis replantear la programación usada a una orientada a sucesos, tal y como vimos en clase...
    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 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 décima acerca de él o el siguiente LPE.
    6. 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.
    7. ¿Qué hago cuando mi programa se desboca para no perjudicar el funcionamiento de la máquina?
      Solución.

© 2020 Guillermo González Talaván.