PRÁCTICAS DE LABORATORIO DE SISTEMAS OPERATIVOS

DUODÉCIMA SESIÓN


  1. Funciones de sincronización.

    7 SISTEMAS DE EJEMPLO.

    7.2 Windows NT.

    Windows NT trata la sincronización apoyado en el concepto de objeto.

    Algunos objetos pueden estar en el estado de señalado o de no señalado. Un hilo puede esperar a que un objeto pase de no señalado a señalado, bloqueando su ejecución.

    Los objetos implicados y la razón por la que pasan a estar señalados pueden ser:

      • Procesos: cuando acaba su último hilo.
      • Hilos: cuando terminan su ejecución.
      • Ficheros: cuando se completa una operación de entrada/salida.
      • Sucesos: cuando ocurre el suceso.
      • Semáforo: cuando la variable asociada al semáforo es estrictamente mayor que cero.
      • Temporizador: cuando expira el intervalo de tiempo prescrito.
      • Exclusiones mutuas (mutexes): cuando un hilo sale de la zona de exclusión mutua.

    En general, cuando un objeto pasa a estar señalado, se desbloquean todos los hilos que estuvieran esperando por él. Esto es cierto salvo en el caso de las exclusiones mutuas, en las que, como es lógico, sólo se desbloquea un hilo.

     

    Para que un hilo de un proceso se bloquee esperando a que un objeto de Windows NT pase al estado de señalado, hay que usar la función del API WaitForSingleObject. El funcionamiento de esta función no es difícil por lo que os remito al resumen.

    Si lo que se desea es esperar por varios objetos a la vez, usaremos la función WaitForMultipleObjects. En este caso, podemos esperar hasta que todos pasen al estado de señalado o hasta que al menos uno de ellos pase al estado de señalado. Esto lo controlará el parámetro de la función fWaitAll:
    DWORD WaitForMultipleObjects( DWORD nCount, CONST HANDLE *lpHandles, 
                                  BOOL fWaitAll, DWORD dwMilliseconds); 
    El resto de los parámetros no presenta ninguna dificultad. Nos encontramos de nuevo un array que, como ya sabemos, necesita que pasemos en otro parámetro su longitud pues, en C, para la función sería desconocida si no lo hacemos.

    Estas funciones de espera van a suplir a muchas otras de UNIX. Con ellas, podremos decrementar semáforos, esperar por la muerte de un proceso o un hilo, hacer multiplexión de entrada/salida asíncrona, etc.

    En cuanto a la sincronización en sí, dispondremos de más mecanismos que en UNIX. Tendremos semáforos, pero además secciones críticas, mutexes (exclusiones mutuas) y eventos. Empezaremos por ver los semáforos, pues ya los conocemos de UNIX y veremos en qué se diferencian de los otros mecanismos.

  2. Semáforos.

    Para recordar el funcionamiento de un semáforo, nada mejor que ir a la sesión sexta. Algunas diferencias de los semáforos de Windows NT frente a los semáforos de UNIX son:
    1. Se deberá especificar un valor máximo para los semáforos de Windows NT.
    2. No se podrá incrementar varios semáforos a la vez.
    3. No se podrá decrementar un semáforo en más de una unidad cada vez.
    Para poder usar un semáforo, primero hay que crearlo. Al crearlo, obtendremos un manejador (handle) con el que acceder a él. Se usará la función del API:
    HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
                            LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName); 
    Como tenemos por costumbre, dejaremos los atributos de seguridad a su valor por defecto (NULL). Debemos especificar en la llamada de creación el valor inicial del semáforo y su valor máximo (esto es diferente con respecto a UNIX).

    Cuando veíamos los mecanismos IPC en UNIX comentábamos cómo debe existir alguna manera en que diferentes procesos se pusieran de acuerdo en usar una misma instancia de un determinado mecanismo. Esto se hacía mediante una clave. En Windows NT, se hace mediante un nombre que deben conocer todos los procesos que quieran manejar el mecanismo. Este nombre es el último parámetro de CreateSemaphore.

    También veíamos en UNIX que se podía crear un mecanismo IPC sin nombre con la macro IPC_PRIVATE. Esto se consigue en Windows NT poniendo NULL como último parámetro. Si queremos compartir el semáforo sin nombre, debemos usar la función del API que vimos en la sesión anterior DuplicateHandle.

    Cuando un proceso quiere usar un semáforo que ya está creado, debe usar la función OpenSemaphore:
    HANDLE OpenSemaphore( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); 
    En el primer parámetro especificaremos el tipo de acceso deseado. Normalmente será SEMAPHORE_ALL_ACCESS, aunque también es posible obtener accesos parciales. El segundo parámetro nos servirá para especificar si queremos que el handle sea heredable por los procesos que creemos. El último parámetro es el nombre del semáforo.

    Una vez tenemos un handle del semáforo, podemos realizar operaciones sobre él. La más sencilla es la operación de incremento. Se realiza con la función:
    BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);
    Especificaremos que queremos incrementar el semáforo en lReleaseCount unidades. La función nos devolverá en la dirección apuntada por lpPreviousCount el valor anterior del semáforo.

    Llegada la hora de decrementar el semáforo y, en el caso de que proceda, quedarnos bloqueados, usaremos cualquiera de las funciones de espera de Windows NT, por ejemplo:
    DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds); 
    El proceso que la invoque con el handle del semáforo efectuará una operación wait sobre él. No es posible decrementar atómicamente el semáforo en más de una unidad.

  3. Mutexes.

    Los mutexes, abreviatura de exclusiones mutuas son otros objetos de sincronización de Windows NT. Son muy parecidos a los semáforos. Se diferencian en tres aspectos:

    1. Son binarios. Es decir, sólo pueden tomar dos valores (asimilables a ocupado(0) y libre(1)).
    2. Existe el concepto de posesión del mutex. La analogía que usamos para los semáforos de un cenicero con pajitas presenta el inconveniente de que inconscientemente se tiende a pensar que un proceso sólo puede dejar una pajita si antes logró cogerla, y eso es falso para los semáforos en general, tanto de UNIX como de Windows NT. Cualquier proceso puede incrementar el semáforo.

      Sin embargo, los mutexes funcionan de modo diferente. El hilo del proceso que haya logrado coger la única pajita del mutex será el único que pueda dejarla de nuevo y así permitir a otros hilos que estén bloqueados poder optar a ella.
    3. El hilo que posee el mutex puede tomar posesión de él varias veces más sin quedarse bloqueado. Si es este el caso, para liberarlo debe soltarlo tantas veces como lo cogió.


    En cuanto a las funciones que manejan los mutexes, son muy parecidas a las de los semáforos y están en el resumen. Tan sólo mencionar:
    HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner,
                        LPCTSTR lpName); 
    El valor bInitialOwner indicará si el hilo que crea el mutex tendrá inmediata posesión de él (y el mutex valor nulo) o si, por el contrario, el mutex estará libre (con valor unidad).

  4. Eventos.

    Mejor traducido por suceso, respetaremos el nombre más parecido al inglés. En su funcionamiento más elemental es un objeto con el cual podemos controlar de un modo manual su estado (señalado o no). Lo creamos con la función CreateEvent:
    HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, 
                        BOOL bInitialState, LPCTSTR lpName); 
    Con esta función podemos indicar si queremos que el objeto nazca señalado o no (bInitialState). Como todo buen objeto de Windows NT, los hilos de los procesos se quedarán bloqueados esperando sobre él hasta que pase al estado de señalado. Lo podemos poner en estado de señalado con SetEvent. Lo podemos pasar a estado de no señalado con ResetEvent. Podemos, incluso, invocar PulseEvent y lograremos que esté temporalmente en estado de señalado para que se desbloqueen todos los hilos que estén esperando sobre él y pasar a continuación al estado de no señalado.

    Finalmente, si en CreateEvent hacemos que bManualReset sea falso, cada vez que hagamos SetEvent no hará falta que hagamos el correspondiente ResetEvent, pues se hará de forma automática.

  5. Secciones críticas.

    Ya conocemos de la parte de teoría de los sistemas operativos lo que es una sección crítica. Sabemos que podemos delimitar secciones críticas de nuestros programas mediante, por ejemplo, un semáforo. Sin embargo, Windows NT nos proporciona un mecanismo más sencillo para marcar las zonas de un programa que forman una sección crítica. Se puede hacer con ayuda de un objeto de tipo CRITICAL_SECTION. Normalmente se usará para delimitar secciones críticas dentro de un mismo proceso en las cuales sólo puede haber un hilo ejecutando de por vez.

    Para usar estos objetos, hay que primero crear una variable de tipo CRITICAL_SECTION y darle valor inicial así:
    CRITICAL_SECTION sc1;
    [...]
    InitializeCriticalSection(&sc1); 
    Cada una de las partes del código que formen la sección crítica hay que rodearla por las instruciones:
    EnterCriticalSection(&sc1);
    [[...Sección crítica...]]
    LeaveCriticalSection(&sc1);
    No hay que olvidarse de liberar la sección crítica cuando ya no se vaya a usar más con DeleteCriticalSection. También existe la posibilidad de probar a entrar en la sección crítica pero no quedarse bloqueado si no se puede (algo así como la versión no bloqueante o IPC_NOWAIT de UNIX) con TryEnterCriticalSection.

  6. Operaciones atómicas sobre variables de tipo LONG.

    Windows NT nos permite realizar operaciones atómicas sobre variables de tipo LONG. Las operaciones incluyen el incremento, decremento e intercambio atómico:
    LONG InterlockedIncrement( LPLONG lpAddend);
    LONG InterlockedDecrement( LPLONG lpAddend);
    LONG InterlockedExchange( LPLONG Target, LONG Value); 
    Estas operaciones son importantes como vimos en teoría para poder dar soluciones de tipo hardware a la concurrencia. En este caso, además, tenemos mayor problema que con los procesos de UNIX pues la memoria es compartida por todos los hilos de ejecución de un proceso y el acceso compartido a las variables puede dar problemas. Estos problemas a veces permanecen ocultos hasta que usamos el programa en un ordenador multiprocesador, pues entonces aumenta la probabilidad de fallo.

  7. Práctica.

    Windows NT no tiene la posibilidad de decrementar en más de una unidad un semáforo atómicamente. UNIX sí. Con esto es casi trivial hacer la práctica. Sin ello, hay que pensar un poco :).

    Cuando construyeron el edificio de usos múltiples de Volrejas no lo hicieron con ascensor. Ahora ya lo instalaron. En el cartel pone "Para tres personas", pero eso es porque no conocen a los gemelos Robustines. Con sus 163.5 kg por barba, cada uno ocupa por dos. Eso por no comentar su afición a subir por el ascensor continuamente. Toda la mañana se pasan subiendo por el ascensor y bajando por la escalera, para volver a subir. Esta compulsión la comparte con Muzzala y Mocosette.

    Haced un programa que, durante un minuto muestre cómo suben y bajan los personajes descritos. Considerad que el viaje en ascensor tarda 3 s y que en bajar por las escaleras tardan 1 s.

    La salida por pantalla ha de consistir en líneas como esta:
    Esperando: Mu R2          Ascensor: Mo             Escalera: R1        
    Esperando: Mu R1 R2       Ascensor: Mo             Escalera:         
    Esperando: Mu R1 R2       Ascensor:                Escalera: Mo        
                etc. 
    Soluciones y comentarios.

  8. Aplicaciones relacionadas.



  9. Funciones de biblioteca relacionadas.



  10. LPEs.


© 2000 Guillermo González Talaván.