Comunicación entre Procesos
Funciones para IPC en System V y POSIX

Algo de hisotria: Unix System V parte el año 1983 y varias versiones se crearon. La versión más exitosa fue la 4, SVR4 (System V Release 4). Esta versión de Unix incluyó los mecanismos de comunicación entre procesos que hoy se conocen como System V IPC. Posteriormente POSIX "Portable Operating System Interface" estandariza los mecanismos de IPC y crea otra API para IPC. Como SVR4 fue muy difundida y está presente en muchos sistemas actuales, POSIX incluye también el API de IPC de SVR4.

  En la primera parte de esta página se describe el API de IPC de SVR4. La segunda parte presenta los llamados POSIX posteriores a SVR4.

Los tres mecanimos para IPC de SVR4 son colas de mensajes, semáforos y memoria compartida. Las colas de mensajes serán omitidas aquí pues sus servicios pueden ser cubiertos por las pipes (es más el autor del texto guía concluye que no deberían ser usadas en aplicaciones nuevas). El compartir memoria puede dar origen a carreras críticas, por ello es necesario usar los semáforos.

Como en el caso de los FIFOS, estos mecanismos se pueden aplicar a procesos que no están vinculados, pero residen en la misma máquina.

Cada uno de estos mecanismos de comunicación son referidos por el kernel vía un identificador entero no negativo.

Este identificador es retornado cuando el mecanismo para IPC (ya sea semáforo o memoria compartida) es creado.  El identificador es un nombre interno para el objeto IPC. Éste es usado para manipular el mecanismo IPC. Usted puede pensar que este identificador cumple un rol similar al de un descriptor de archivo.

Para crear un mecanismo IPC los procesos deben pasar como argumento un mismo valor, conocido como "clave", para referirse al mismo mecanismo IPC.

Esta clave (key) es de tipo long int y debe ser única en el sistema. Si no es así, la estructura para la comunicación entre procesos no es creada (arroja un error).

La clave (key) puede ser obtenida de tres formas:

  • El servidor crea una nueva estructura especificando una clave de IPC_PRIVATE. El procedimiento creador retorna un identificador para la nueva estructura. El problema es que ésta debe ser comunicada al proceso cliente de alguna manera. Por ejemplo, a través de un archivo.
  • Otra forma es que el servidor y cliente se pongan de acuerdo en una clave. El problema es que ésta puede coincidir con otra ya existente.
  • El servidor y el cliente pueden convenir un path y un identificador de proyecto ( 0<= project ID <=255) y llamar a la función ftok, la cual convierte estos dos valores en una clave.

  •         #include <sys/types.h>
            #include <sys/ipc.h>
            key_t ftok(const char * path, int projectID);  /* key_t es generalmente un long int */  /* se toma los 8 bits menos significativos de projectID */
    Para crear una nueva estructura IPC, el servidor llama a una de las funciones "get". Ésta requiere un clave (IPC_PRIVATE, un valor fijo, o el retornado por ftok) y en el argumento flag se debe activar el bit IPC_CREATE. Activando el bit IPC_EXCL se puede asegurar que la estructura es nueva (de no serlo se retorna un error).

    El cliente accede a la estructura creada por el servidor llamando a la función get con la misma clave y el bit del flag IPC_CREATE desactivado.

    Algunas estructuras de datos son creadas globalmente en el sistema, el cual no maneja un contador de referencias. La estructura permanece en el sistema hasta que es removida por un proceso o la máquina es "rebooted". (Parecido a procesos zombies). En caso de ser necesario, con los comandos ipcs e ipcrm se puede listar y remover estos mecanismos de comunicación entre procesos. 


    Semáforos
        No son realmente un mecanismo de IPC (como ustedes pueden ya saber de sistemas operativos). Un semáforo es un contador usado para proveer acceso a datos compartidos por múltiples procesos.
    Para obtener acceso a un recurso compartido el proceso, debemos:
    1. Chequea el valor del semáforo
    2. Si el valor es mayor que 0, se puede usar el recurso. El semáforo es decrementado en 1.
    3. Si el valor en 0, el proceso se va a dormir (bloquea) hasta que el valor sea mayor que 0.
    Creación de semáforo
        #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/sem.h>

        int semget(key_t key, int nsems, int semflg);

    Para remover un semáforo o hacer otra tareas de control
        int semctl( int semid, int semnum, int cmd, union semun arg);

    El parámetro cmd puede adoptar los siguientes valores entre otros:
    IPC_STAT:  para leer el estado del semáforo.
    IPC_SET: permite cambiar los permisos de acceso del semáforo.
    IPC_RMID: Permite remover el semáforo.
     

    Para operar el semáforo:
        int semop (int semid, struct sembuf *ops, size_t nops);

        struct sembuf {
            ushort    sem_num;
            short      sem_op;
            short      sem_flg;
        };

    En general estas funciones no son amigables para operar con semáforos debido a:

    1. Se crea un conjunto de semáforos en lugar de sólo uno.
    2. La creación de un semáforo (semget () ) es independiente de su iniciación (semctl() )
    3. El semáforo permanece si ningún proceso lo remueve.
    Por estas razones aquí tienen un archivo que encapsula estas llamadas en otras "más amigables".

    Estos mecanismos de comunicación entre procesos toman recursos del sistema que si no son removidos permanecen hasta que la máquina sea reiniciada. Para ayudar a identificar usos no intencionales de ellos usar el comando ipcs para listar los recursos activos y ipcrm para removerlos.



    Memoria Compartida
        Memoria compartida permite a dos o más procesos compartir una región de memoria. Ésta es la forma más rápida de comunicación entre procesos porque no hay necesidad de hacer copias de datos.
        Algún mecanismo de sincronización debe ser empleado para impedir accesos mientras se escribe.

    Creación de una región de memoria compartida

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>

    int shmget (key_t key, int size, int flag);

    Con esta llamada se crea una estructura de control asociada a esta memoria compartida.

    Esta estructura se puede manipular a través de la función shmctl()
            shmctl ( int shmid, int cmd, struct shmid_ds * buf );

    Uno de los comandos permite remover la región de memoria compartida.

    Para hacer uso de la memoria compartida los procesos deben ligar (attach) el segmento de memoria compartida. Esto se hace con la función shmat()
        void * shmat (int shmid, void *shmaddr, int shmflg);

    shmaddr es la dirección a la cual la memoria debe ser ligada. Se recomienda especificar 0 (y el sistema elige la mejor dirección).
    Si shmflg contiene el flag SHM_RDONLY, la memoria es de sólo lectura, en otro caso es lectura y escritura.

    Para desligar (detach) la memoria compartida:
        int shmdt( void *shmaddr);

    Donde shmaddr es el valor retornado por shmat().

    Ejemplo:  Productor consumidor usando memoria compartida. Un programa actúa como servidor y otro como cliente. El servidor lee información desde la entrada estándar y la transfiere a la memoria compartida. El cliente la lee desde la memoria compartida y la escribe en la salida estándar.

    La memoria compartida creada permanece en el sistema hasta que esta sea removida. Es así como podemos crear un programa que escriba en memoria y otro que más tarde la lea. En este caso no hemos usado la versión amigable para manejo de semáforo pues no es requerida. Una versión más completa de este programa, es decir que permita su ejecución conjunta o separada, no puede usar las funciones amigables para semáforos porque éstas remueven el semáforo cuando el proceso termina.

    Otros ejemplos de uso de memoria compartida y semáforos.


    Memoria Compartida en POSIX
      POSIX define un API con las siguientes funciones:
        shm_open(3): crea y abre un objeto de memoria compartida nuevo, o abre una objeto existente. Es análogo a open(2).  El valor retornado es un descriptor de archivo para ser usado en los otros llamados del API.
        ftruncate(2)   Fija el tamaño del objeto de memoria compartida.  Objetos recien creados de memoria compartida tienen largo cero.
        Los dos llamados previos en su conjunto cumplen funcionalidad similar a shmget.

        mmap(2)       Mapea el objeto de memoria compartida en el espacio de direcciones virtuales del proceso llamador. Este llamado se puede considerar análogo a shmat.
        munmap(2)    Desmapea el objeto de memoria compartida del espacio de direcciones del proceso llamador.
    Este llamado se puede considerar análogo a shmdt.
        shm_unlink(3)  Remueve el segmento de memoria compartida del sistema de archivos (es una visión lógica, no en disco).
        close(2)       Cierra el descriptor de archivo retornado por shm_open(3). El proceso hace este llamado cuando deja de necesitar el descriptor.
        fstat(2)       Obtiene el estado del segmento de memoria compartida. Entre la información retornada está: el tamaño del objeto, sus permisos, dueño y grupo.
        fchown(2)      Para cambiar el dueño del segmento de memoria compartida.
        fchmod(2)      Para cambiar los permisos del objeto de memoria compartida.

    Ver ejemplos aquí: Se trata de un sistema productor-consumidor sin mecanismos de sincronización. Es similar al sistema de escritura y lectura off-line del código presentado antes (shmReaderOffLine y shmWriterOffLine)


    Semáforos en POSIX
    Si bien POSIX define un conjunto de llamados para manejar semáforos, su implentación no está tan difundida como aquella de SVR4 presentada arriba. De man sem_overview se tiene:"System  V  semaphores (semget(2), semop(2), etc.) are an older semaphore API.  POSIX semaphores provide a simpler, and better designed interface than System V semaphores; on the other hand POSIX semaphores are less widely available (especially on older systems) than System V semaphores."

    POSIX define un API para el manejo de semáforos para mayor información revisar páginas man de: sem_close(3), sem_destroy(3), sem_getvalue(3), sem_init(3), sem_open(3), sem_post(3), sem_unlink(3),
           sem_wait(3).
    POSIX permite crear dos tipos de semáforos: semáforos con nombre y sin nombre. Los primeros pueden ser usados entre varios proceso usando el mismo nombre en el llamado a sem_open.
    Llegaremos hasta aquí en este punto.