PRÁCTICAS DE SISTEMAS OPERATIVOS II

SEGUNDA PRÁCTICA EVALUABLE

Aparcando II


  1. Enunciado.

    Esta funcionamiento de esta práctica es muy similar a la anterior, con algunas diferencias que se explican en este documento, entre las que destacan:
    1. Se usarán llamadas de la API de WIN32
    2. Habrá un único proceso, eso sí, con varios hilos
    3. Se proporciona una biblioteca de enlazado dinámico, DLL, en lugar de una de enlazado estático
    4. Desaparecen los chóferes: para aparcar o desaparcar coches se creará un hilo nuevo
    5. Tampoco es necesario programar prioridades: FIFO, aparcar o desaparcar
    6. No se requiere de ningún proceso extra que duerma para avisar del fin de la simulación
    7. Aparecen cuatro manejadoras más, además de las de llegadas, para gestionar las salidas de cada algoritmo
    8. No hay buzón de paso de mensajes para comunicar aparcamientos y salidas

    El programa propuesto constará de un único fichero fuente, parking2.cpp, cuya adecuada compilación producirá el ejecutable parking2.exe. Se trata de simular mediante un programa que realice llamadas a la API de WIN32 la gestión de memoria de un sistema operativo que use particiones de tamaño dinámico. Para ello, se establece el símil con el problema de elegir el mejor aparcamiento para un coche de todos los posibles en una acera. Según se va ejecutando el programa, se ha de ver una imagen parecida a la siguiente:

    Captura de pantalla


    Los algoritmos, características de acera, calzada y coches son iguales a los correspondientes de la práctica anterior. En particular, también se requiere que los coches aparquen en orden numérico consecutivo en cada algoritmo.

    Parking2 acepta un máximo de dos argumentos por la línea de órdenes. Si no se introducen argumentos, se imprimirá un mensaje con la forma de uso del programa por el canal de error estándar. En el caso de teclear un argumento, dicho argumento será un número entero mayor o igual que cero relacionado con la rapidez con que se producen los acontecimientos en el programa. Cero indica la máxima rapidez y números mayores suponen una rapidez progresivamente menor. Finalmente, si son dos los parámetros introducidos, el primero es idéntico al caso anterior y el segundo será una "D", indicando que se desea que el programa produzca información de depuración por el canal de errores estándar.

    Para facilitar la tarea, tenéis a vuestra disposición una biblioteca de funciones de enlazado dinámico parking2.dll y un fichero de cabeceras, parking2.h. Gracias a la biblioteca, muchas de las funciones no las tendréis que programar sino que invocarlas a la DLL, tal como se explica en la sesión novena.

    La biblioteca creará un hilo adicional a los vuestros por cada algoritmo para su funcionamiento interno, de los cuales no tendréis que ocuparos. Una descripción detallada de las funciones de la biblioteca aparece más abajo en esta misma página.

    El programa, desde vuestro punto de vista, se simplifica bastante. Se ha de llamar a la función PARKING2_inicio de la DLL. El hilo dormirá 30 segundos e invocará a la función PARKING2_fin, para acabar con la simulación.

    Se deben programar cuatro funciones, una por algoritmo, que serán llamadas por la DLL cada vez que un coche llegue y cuatro funciones a las que la DLL llamará cuando un coche tenga que marcharse. Dichas funciones de rellamada son registradas en la función PARKING2_inicio de la DLL.

    Los prototipos de los dos tipos de funciones de rellamada, se describen aquí:

    La funciones PARKING2_aparcar y PARKING2_desaparcar son especiales puesto que gestionan el movimiento de los coches automáticamente mediante funciones de rellamada a vuestro código. Estos son sus prototipos con la correspondiente explicación:

    Características adicionales que programar



    Biblioteca parking.dll

    Con estra práctica se trata de que aprendáis a sincronizar y comunicar hilos en Windows. Su objetivo no es la programación. Es por ello que se os suministra una biblioteca dinámica de funciones ya programadas para tratar de que no tengáis que preocuparos por la presentación por pantalla, la gestión de estructuras de datos (colas, pilas, ...) , etc. También servirá para que se detecten de un modo automático errores que se produzcan en vuestro código. Para que vuestro programa funcione, necesitáis la biblioteca parking.dll y el fichero de cabeceras parking2.h.
    Ficheros necesarios:


    Las funciones que la biblioteca exporta para que las uséis son:

    Sincronización interna de la DLL

    La sincronización interna de la DLL está basada en un mutex por cada algoritmo. El esquema de sincronización interna es el siguiente:
    
        Mutex Mu[nAlgoritmos];
        Mu[x]=1;
        [...]
        SimulaciOn: (un hilo por cada algoritmo)
        ===========
        mientras no haya que acabar
          Calcular el tiempo de la prOxima salida o llegada
          Dormir hasta ese tiempo o si nos avisan de coches reciEn aparcados/desaparcados
    
          Wait(Mu[algoritmo]);
            if (hay coches reciEn aparcados)
              calcular su tiempo de salida
              pasarlos a la lista de aparcados
            if (hay coches reciEn desaparcados)
              borrar los coches del sistema
          Signal(Mu[algoritmo]);
          
          mientras el tiempo de la prOxima llegada sea menor que el reloj
            if (lista de espera no estA llena (500 coches))
              crear nuevo coche y meterlo en la lista de espera
            calcular el tiempo de la prOxima llegada
    
          mientras haya coches en la lista de espera
            hc=tomar primer coche de la lista
            Wait(Mu[algoritmo])
              posBuena=Calcular la posiciOn buena
              vuestraPos=Pedir la posiciOn a vuestra funciOn
              poner en hc la posiciOn buena
              if (vuestraPos==-2)
                hacer que prOxima llegada sea infinito
                vaciar la lista de espera
                Signal(Mu[algoritmo])
                break
              if (posBuena!=vuestraPos)
                poner el error
                Signal(Mu[algoritmo])
                break
              if (vuestraPos==-1)
                Signal(Mu[algoritmo])
                break
              Sacar el coche de la lista de espera
              Reservar la acera
            Signal(Mu[algoritmo])
    
        Aparcar: (la realiza un hilo independiente)
        ========
        si no le toca a este coche, poner el error
        incrementar el nUmero que toca
        almacenar el puntero de los datos de usuario pasado en el coche
        desde la pos=79 hasta la posición en que el coche tiene que aparcarse
          pausa de avance
          llamar a permiso de avance
          dibujar el coche
          llamar a permiso de avance commit
          si pos=79, llamar a aparcar commit
        hacer lo mismo para los dos avances verticales para acabar de aparcar
        Wait(Mu[algoritmo])
          encolar el coche en los recién aparcados
          avisar al hilo de la simulaciOn
        Signal(Mu[algoritmo])
        
        Desaparcar: (la realiza un hilo independiente)
        ===========
        permiso avance, dibujo y llamar a avance commit para el primer avance vertical
        permiso avance para el segundo avance vertical
        Wait(Mu[algoritmo])
          dibujar el coche
          liberar la reserva de la acera
          llamar a permiso avance commit
        Signal(Mu[algoritmo])
        hasta que el coche desaparece: pausa, permiso avance, dibujo, llamar a avance commit
        Wait(Mu[algoritmo])
          meter el coche en la lista de recién desaparcados
          avisar al hilo de la simulaciOn
        Signal(Mu[algoritmo])
    
    
    
  2. Pasos recomendados para la realización de la práctica

    En esta práctica, no os indicaremos los pasos que podéis seguir. El proceso de aprendizaje es duro, y ya llega el momento en que debéis andar vuestros propios pasos sin ayuda, aunque exista la posibilidad de caerse al principio.

  3. Plazo de presentación.

    Consúltese la página de entrada de la asignatura.

  4. Normas de presentación.

    Acá están. Además de estas normas, en esta práctica se debe entregar un esquema donde aparezcan los mecanismos de sincronización usados, sus valores iniciales y un seudocódigo sencillo para cada hilo con las operaciones realizadas sobre ellos. Por ejemplo, si se tratara de sincronizar con eventos dos hilos C y V para que produjeran alternativamente consonantes y vocales, comenzando por una consonante, deberíais entregar algo parecido a esto:
         EVENTOS Y VALOR INICIAL: EC* (automático), EV (automático).
    
         SEUDOCÓDIGO:
    
                 C                                V
                ===                              ===
           Por_siempre_jamás               Por _siempre_jamás
              {                               {
               W(EC)                           W(EV)
               escribir_consonante             escribir_vocal
               Set(EV)                         Set(EC)
               }                               }
    
    Debéis indicar, asimismo, en el caso de que las hayáis realizado, las optimizaciones de código realizadas.

  5. Evaluación de la práctica.

    Dada la dificultad para la corrección de programación en paralelo, el criterio que se seguirá para la evaluación de la práctica será: si
    1. la práctica cumple las especificaciones de este enunciado y,
    2. la práctica no falla en ninguna de las ejecuciones a las que se somete y,
    3. no se descubre en la práctica ningún fallo de construcción que pudiera hacerla fallar, por muy remota que sea esa posibilidad...
    se aplicará el principio de "presunción de inocencia" y la práctica estará aprobada. La nota, a partir de ahí, dependerá de la simplicidad de las técnicas de sincronización usadas, de las optimizaciones realizadas para producir más aparcamientos, etc.

  6. LPEs.

    1. No debéis usar la función TerminateThread para acabar con los hilos o TerminateProcess para acabar con los procesos. El problema de estas funciones es que están diseñada para ser usada sólo en condiciones excepcionales y los hilos mueren abruptamente. Puede dejar estructuras colgando, ir llenando la memoria virtual del proceso con basura o no invocar adecuadamente las funciones de descarga de la DLL.

    2. Al ejecutar la práctica, no puedo ver lo que pasa, porque la ventana se cierra justo al acabar.

      Para evitar esto, ejecutad la práctica desde el "Símbolo del sistema", que se encuentra en el menú de "Accesorios". También es necesario que la ventana que uséis tenga un tamaño de 80x25 caracteres. Si no lo tenéis así, cambiadlo en el menú de propiedades de la ventana.

    3. Al ejecutar la función LoadLibrary, en lugar de aparecer la pantalla de presentación, aparece un mensaje que pone "En DllMain".

      Es necesario que la ventana que uséis tenga un tamaño de 80x25 caracteres. Si no lo tenéis así, cambiadlo en el menú de propiedades de la ventana.

    4. Cuando ejecuto la práctica depurando la pantalla se emborrona. ¿Cómo lo puedo arreglar?

      Mejor depurad la práctica enviando la información de trazado escrita con fprintf(stderr,...) a un fichero, añadiendo al final de la línea de órdenes 2>salida. De este modo, toda la información aparecerá en el fichero salida para su análisis posterior. No os olvidéis de incluir el identificador del hilo que escribe el mensaje.

    5. Tengo muchos problemas a la hora de llamar a la función XXXX de la biblioteca. No consigo de ningún modo acceder a ella.

      El proceso detallado viene en la última sesión. De todos modos, soléis tener problemas en una conversión de tipos, aunque no os deis cuenta de ello. No vamos a deciros qué es lo que tenéis que poner para que funcione, pues lo pondríais y no aprenderíais nada. Sin embargo y dada la cantidad de personas con problemas, aquí viene una pequeña guía:
      1. Primero debéis definir una variable puntero a función. El nombre de la variable es irrelevante, pero podemos llamarle XXXX por lo que veremos más abajo. Para definir el tipo de esta variable correctamente, debéis conocer cómo son los punteros a función. En la última sesión de Sistemas Operativos I, se describe una función, atexit. Dicha función en sí no es importante para lo que nos traemos entre manos, pero sí el argumento que tiene. Ese argumento es un puntero a función. Fijándoos en ese argumento, no os resultará difícil generalizarlo para poner un puntero a funciones que admiten otro tipo de parámetros y devuelve otra cosa. Notad, además, que, al contrario que ocurre con las variables "normales", la definición de una variable puntero a función es especial por cuanto su definición no va solo antes del nombre de la variable, sino que lo rodea. Tenéis que poner algo similar a: #$%&%$ XXXX $%&$·@;, es decir, algo por delante y algo por detrás.
      2. Después de cargar la biblioteca como dice en la última sesión, debéis dar valor al puntero de función. Dicho valor lo va a proporcionar GetProcAddress. Pero, ¡cuidado!, GetProcAddress devuelve un FARPROC, que sólo funciona con punteros a funciones que devuelven int y no se les pasa nada (void). Debéis hacer el correspondiente casting. Para ello, de la definición de vuestro puntero, quitáis el nombre, lo ponéis todo entre paréntesis y lo añadís delante de GetProcAddress, como siempre.
      3. Ya podéis llamar a la función como si de una función normal se tratara. Ponéis el nombre del puntero y los argumentos entre paréntesis. Como os advertí más arriba, si habéis puesto XXXX como nombre al puntero, ahora no se diferenciarán en nada vuestras llamadas a la función respecto a si dicha función no perteneciera a una DLL y la hubierais programado vosotros.


    6. Os puede dar errores en el fichero de cabecera .h si llamáis a vuestro fichero fuente con extensión .c. Llamadlo siempre con extensión .cpp.

    7. Tened mucho cuidado si usáis funciones de memoria dinámicas de libc (malloc y free). Son funciones que no están sincronizadas, es decir, no se comportan bien en entornos multihilo. O bien las metéis en una sección crítica o, mejor aún, tratad de evitarlas.

    8. En algunas versiones de Visual Studio os puede dar un error del tipo: error XXXXX: 'FuncionW': no se puede convertir de 'const char[X]' a 'LPCWSTR'. El motivo del error es que, por defecto, esa versión de Visual Studio supone que deseáis usar UNICODE (caracteres de 16 bits) en lugar de los normales (caracteres de 8 bits). La solución pasa por transformar el código fuente para que se ajuste a la programación en UNICODE de Microsoft o decirle a Visual Studio que no, que no queréis trabajar con UNICODE. Unos compañeros vuestros nos escriben diciendo que si en la configuración del proyecto seleccionáis "Juego de Caracteres->Sin establecer", se soluciona.

    9. Cuando ejecuto la práctica depurando me aparece Se ha producido un choque.... Cuando examino la casilla, compruebo que todo es correcto. ¿A qué puede ser debido?

      Este mensaje u otros similares que parecen estar equivocados tienen su razón en que estáis "manchando" la pantalla con mensajes propios o de depuración. Para evitar este tipo de mensajes, mejor depurad la práctica enviando la información de trazado a un fichero añadiendo al final de la línea de órdenes 2>salida. De este modo, toda la información aparecerá en el fichero salida.
    10. En esta práctica se crean muchos hilos de los que es muy difícil hacer un seguimiento para obtener su código de retorno. Cada hilo creado necesita un manejador. Aunque no suele ser un aspecto restrictivo con los números que se manejan en esta práctica, conviene cerrar los manejadores que devuelve CreateThread. Esto se puede incluso hacer nada más creado el hilo por el hilo padre. El hilo hijo se ejecutará sin problemas.
    11. Dada la sincronización interna de la biblioteca, si un coche tiene que esperar porque no es todavía su turno, no lo puede hacer en la función de llegada. Lo debe hacer en el nuevo hilo que se crea para aparcar, evidentemente antes de llamar a PARKING2_aparcar.



© 2004 2017 Guillermo González Talaván.