PRÁCTICAS DE LABORATORIO DE SISTEMAS OPERATIVOS

DÉCIMA SESIÓN


  1. Multiplexión de entrada/salida síncrona.

    Hasta ahora en el curso hemos resuelto un problema muy importante en los sistemas operativos modernos. Consiste en poder especificar que un proceso se quede bloqueado (sin consumir CPU) mientras se encuentra ocupado un recurso que solicita. Con mayor o menor ventura, hemos podido hacer que incluso el proceso pueda realizar otras tareas mientras espera a la obtención del recurso.

    En esta ocasión, lo que haremos será aprender a solucionar el problema de que tengamos que esperar por varios recursos a la vez. En realidad, los únicos recursos por los que podemos esperar serán aquellos sobre los que se pueda abrir un descriptor de fichero válido (ficheros, dispositivos, sockets, tuberías, ...). Esto se conoce como multiplexión de entrada/salida síncrona.

    Un ejemplo de un programa que usa esta técnica es el demonio de telnet. Este programa es el que se encarga de recoger los caracteres que tecleáis desde vuestros terminales de la red y pasarlos al teminal local del servidor. Y al contrario, cuando el terminal local del servidor da caracteres, el programa se encarga de reemitirlos por la red para que os lleguen a vosotros. Esto hace que el programa tenga que quedarse bloqueado esperando por dos sucesos: la llegada de un carácter pulsado por vosotros a través de la red o la llegada de salida de los programas que estéis ejecutando.

  2. La llamada al sistema select.

    El prototipo de select es:
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds,
               struct timeval *timeout); 
    A estas alturas de curso, con la página de manual y el resumen tenéis que ser capaces de usar una llamada al sistema. Solamente os daré unos apuntes acerca de select. A select se le ha de proporcionar tres conjuntos de descriptores de ficheros. El primer conjunto especificará los descriptores de ficheros sobre los que queremos ser informados cuando haya datos para leer de ellos. En el segundo incluiremos aquellos descriptores sobre los que queremos ser informados cuando se pueda escribir en ellos sin quedarnos bloqueados (p. ej. tubería llena). En el último conjunto están los descriptores de los que seremos informados cuando haya sobre ellos condición de error.

    Evidentemente, para poder especificar conjuntos necesitamos una implementación del TDA conjunto. Esto es lo que se hace con el tipo especial fd_set. Además, debemos disponer de unas funciones para actuar sobre este tipo. Son las funciones de biblioteca FD_ZERO, FD_SET y FD_CLR, que vienen en el resumen. También allí se puede ver el significado del último parámetro.

    Ejemplo: si queremos que select se quede bloqueado hasta que lleguen caracteres por la entrada estándar o la tubería cuyo descriptor tenemos en la variable fd, la llamada tendría los siguientes parámetros:
    1. fd+1 (este parámetro es el número más alto de descriptor que se comprobará más uno. Como la entrada estándar es el 0, será fd+1.
    2. El conjunto {0, fd}, que se construirá con ayuda de las macros para manejar el tipo fd_set.
    3. NULL, no nos preocupa la posibilidad de escritura.
    4. NULL, no nos preocupan los errores.
    5. NULL, pues queremos que select se bloquee indefinidamente.

    Cuando select vuelva, si ha tenido éxito y hay datos que leer, nos devolverá cuántos descriptores requieren nuestra atención. En el ejemplo, puede devolver 1 ó 2. Además, select habrá dejado en los conjuntos que le hayamos pasado sólo aquellos descriptores que requieran atención. Por consiguiente, no podemos volver a usar el mismo conjunto para llamar otra vez a select. Hay que refrescarlo.

  3. Cierre de ficheros/comunicación y select.

    Hemos dicho que el primer conjunto de descriptores de fichero que se le pasa a select es aquel sobre el que queremos esperar hasta que haya algún dato que leer. Hay una pequeña excepción. Cuando se alcanza el final de un fichero (o se corta la comunicación en una tubería o un socket), select se desbloquea si el objeto está entre los especificados en su primer conjunto de descriptores de fichero. Ya sabemos que al hacer un read sobre uno de ellos, la llamada al sistema devolverá cero inmediatamente, señal que nos sirve para detectar el fin del fichero o el corte de la comunicación.

    Más correctamente (aunque quizá no tan claro), podemos decir que select volverá y señalará un descriptor de su primer/segundo conjunto cuando puede garantizar que una llamada a read/write no se va a quedar bloqueada sobre él.

  4. Práctica.

    El programa admitirá un parámetro, el nombre de un fichero. El programa constará de dos procesos, padre e hijo, comunicados por una tubería sin nombre. El hijo escribirá en la tubería cada 10 segundos "[[x segundos]]", siendo x el número de segundos que han transcurrido. El programa padre atenderá a la entrada estándar y a la tubería. Por cualquier medio que le llegue algo, escribirá lo que le llegue en el fichero que se le ha pasado por la línea de órdenes. Cuando el usuario presione CTRL+D, el programa acabará, no sin antes haber matado todos los procesos. Se puede usar sleep en el hijo. Haced todo el programa en un único fichero fuente .c.

    Soluciones y comentarios.

  5. Órdenes de la shell relacionadas.



  6. Funciones de biblioteca relacionadas.

    FD_ZERO, FD_SET, FD_CLR
    para manejar el tipo fd_set


  7. LPEs.


© 2000 Guillermo González Talaván.