Página siguiente Página anterior Índice general

5. Mecanismos IPC

Este capítulo describe los mecanismos IPC semáforo, memoria compartida y cola de mensajes tal como han sido implementados en el núcleo Linux 2.4. Está organizado en 4 secciones. Las tres primeras secciones cubren las interfaces y las funciones de soporte para semaphores, message queues, y shared memory respectivamente. La sección last describe un conjunto de funciones comunes y estructuras de datos que son compartidas por los tres mecanismos.

5.1 Semáforos

Las funciones descritas en esta sección implementan el nivel de usuario de los mecanismos de los semáforos. Nótese que esta implementación ayuda en el uso de los spinlocks y semáforos del núcleo. Para eliminar esta confusión el término "semáforo del núcleo" será usado en referencia a los semáforos del núcleo. Todos los otros usos de la palabra "semáforo" será una referencia a los semáforos del nivel de usuario.

Interfaces de la Llamada al sistema de los Semáforos

sys_semget()

La llamada entera a sys_semget() es protegida por el semáforo global del núcleo sem_ids.sem

En el caso donde un nuevo conjunto de semáforos deben de ser creados, la función newary() es llamada para crear e inicializar un nuevo conjunto de semáforos. La ID del nuevo conjunto es retornada al llamante.

En el caso donde un valor de llave es suministrado por un conjunto de semáforos existentes, ipc_findkey() es llamado para buscar el correspondiente descriptor del semáforo en el índice de la matriz. Los parámetros y los permisos del llamante son verificados antes de devolver la ID del conjunto de semáforos.

sys_semctl()

Para los comandos IPC_INFO, SEM_INFO, y SEM_STAT, semctl_nolock() es llamado para realizar las funciones necesarias.

Para los comandos GETALL, GETVAL, GETPID, GETNCNT, GETZCNT, IPC_STAT, SETVAL, y SETALL, semctl_main() es llamado para realizar las funciones necesarias.

Para los comandos IPC_RMID y IPC_SET, semctl_down() es llamada para realizar las funciones necesarias. Durante todas estas operaciones, es mantenido el semáforo global del núcleo sem_ids.sem.

sys_semop()

Después de validar los parámetros de la llamada, los datos de las operaciones de los semáforos son copiados desde el espacio de usuario a una antememoria temporal. Si una pequeña antememoria temporal es suficiente, entonces es usada una antememoria de pila. En otro caso, es asignad una antememoria más grande. Después de copiar los datos de las operaciones de los semáforos, el spinlock global de los semáforos es cerrado, y la ID del conjunto de semáforos especificado por el usuario es validado. Los permisos de acceso para el conjunto de semáforos también son validados.

Todas las operaciones de los semáforos especificadas por el usuario son analizadas. Durante este proceso, es mantenida una cuenta para todas las operaciones que tienen la bandera SEM_UNDO establecida. Una bandera decrease es establecida si alguna de las operaciones quitan de un valor del semáforo, y una bandera alter es establecida si alguno de los valores de los semáforos es modificado (esto es, incrementados o decrementados). El número de cada semáforos a ser modificado es validado.

Si SEM_UNDO estaba asertado para alguna de las operaciones del semáforo, entonces la lista para deshacer la actual tarea es buscada por una estructura deshacer asociada con este conjunto de semáforos. Durante esta búsqueda, si la ID del conjunto de semáforos de alguna de las estructuras deshacer es encontrada será -1, entonces freeundos() es llamada para liberar la estructura deshacer y quitarla de la lista. Si no se encuentra ninguna estructura deshacer para este conjunto de semáforos entonces alloc_undo() es llamada para asignar e inicializar una.

La función try_atomic_semop() es llamada con el parámetro do_undo igual a 0 en orden de ejecutar la secuencia de operaciones. El valor de retorno indica si ambas operaciones han tenido éxito, han sido fallidas, o que no han sido ejecutadas porque necesitaban bloquear. Cada uno de estos casos son más ampliamente descritos a continuación:

Operaciones de semáforos no bloqueantes

La función try_atomic_semop() devuelve cero para indicar que todas las operaciones en la secuencia han sido realizadas con éxito. Es este caso, update_queue() es llamada para recorrer la cola de las operaciones pendientes del semáforo para el conjunto del semáforo y despertar cualquier tarea dormida que no necesite bloquear más. Esto completa la ejecución de la llamada al sistema sys_semop() para este caso.

Operaciones de Semáforo con fallos

Si try_atomic_semop() devuelve un valor negativo, entonces ha sido encontrada una condición de fallo. En este caso, ninguna de las operaciones han sido ejecutadas. Esto ocurre cuando una operación de un semáforo causaría un valor inválido del semáforo, o un operación marcada como IPC_NOWAIT es incapaz de completarse. La condición de error es retornada al llamante de sys_semop().

Antes de que sys_semop() retorne, es hecha una llamada a update_queue() para recorrer la cola de operaciones pendientes del semáforo para el conjunto del semáforo y despierta cualquier tarea dormida que no necesite más bloqueos.

Operaciones de Semáforo bloqueantes

La función try_atomic_semop() devuelve un 1 para indicar que la secuencia de operaciones del semáforo no fue ejecutada porque uno de los semáforos bloquearía. Para este caso, un nuevo elemento sem_queue es inicializado conteniendo estas operaciones del semáforo. Si alguna de estas operaciones afectaran al estado del semáforo, entonces un nuevo elemento de cola es añadido al final de la cola. En otro caso, el nuevo elemento es añadido al principio de la cola.

El elemento semsleeping de la tarea actual está establecido para indicar que la tarea está durmiendo en este elemento sem_queue. La tarea actual es marcada como TASK_INTERRUPTIBLE, y el elemento sleeper del sem_queue es establecido para identificar esta tarea por el durmiente. El spinlock global del semáforo es entonces desbloqueado, y schedule() es llamado para poner la tarea actual a dormir.

Cuando es despertada, la tarea vuelve a cerrar el spinlock global del semáforo, determina por qué fue despertada, y cómo debería de responder. Los siguientes casos son manejados:

Estructuras Específicas de Soporte de Semáforos

Las siguientes estructuras son usadas específicamente para el soporte de semáforos:

struct sem_array


/* Una estructura de datos sem_array para cada conjunto de semáforos en el sistema. */
struct sem_array {
    struct kern_ipc_perm sem_perm; /* permisos .. ver ipc.h */
    time_t sem_otime; /* último tiempo de la operación con el semáforo */
    time_t sem_ctime; /* último tiempo de cambio */
    struct sem *sem_base; /* puntero al primer semáforo en el array */
    struct sem_queue *sem_pending; /* operaciones pendientes para ser procesadas  */
    struct sem_queue **sem_pending_last; /* última operación pendiente */
    struct sem_undo *undo; /* peticiones deshechas en este array * /
    unsigned long sem_nsems; /* número de semáforos en el array */
};

struct sem


/* Una estructura de semáforo para cada semáforo en el sistema. */
struct sem {
        int     semval;         /* valor actual */
        int     sempid;         /* pid de la última operación */
};

struct seminfo


struct  seminfo {
        int semmap;
        int semmni;
        int semmns;
        int semmnu;
        int semmsl;
        int semopm;
        int semume;
        int semusz;
        int semvmx;
        int semaem;
};

struct semid64_ds


struct semid64_ds {
        struct ipc64_perm sem_perm;             /* permisos.. ver ipc.h */
        __kernel_time_t sem_otime;              /* último tiempo de operación con el semáforo */
        unsigned long   __unused1;
        __kernel_time_t sem_ctime;              /* último tiempo de cambio */
        unsigned long   __unused2;
        unsigned long   sem_nsems;              /* número de semáforos en la matriz */
        unsigned long   __unused3;
        unsigned long   __unused4;
};

struct sem_queue


/* Una cola para cada proceso durmiendo en el sistema. */
struct sem_queue {
        struct sem_queue *      next;    /* siguiente entrada en la cola */
        struct sem_queue **     prev;    /* entrada anterior en la cola, *(q->prev) == q */
        struct task_struct*     sleeper; /* este proceso */
        struct sem_undo *       undo;    /* estructura deshacer */
        int                     pid;     /* id del proceso del proceso pedido */
        int                     status;  /* status de terminación de la operación  */
        struct sem_array *      sma;     /* matriz de semáforos para las operaciones */
        int                     id;      /* id interna del semáforo */
        struct sembuf *         sops;    /* matriz de operaciones pendientes*/
        int                     nsops;   /* número de operaciones */
        int                     alter;   /* operaciones que alterarán el semáforo  */
};

struct sembuf


/* las llamadas al sistema cogen una matriz de estas. */
struct sembuf {
        unsigned short  sem_num;        /* indice del semáforo en la matriz */
        short           sem_op;         /* operación del semáforo */
        short           sem_flg;        /* banderas de la operación */
};

struct sem_undo


/* Cada tarea tiene una lista de peticiones de deshacer. Ellas son
 * ejecutadas cuando el proceso sale.
 */
struct sem_undo {
        struct sem_undo *       proc_next;      /* siguiente entrada en este proceso */
        struct sem_undo *       id_next;        /* siguiente entrada en
                                                este conjunto de semáforos */
        int                     semid;          /* identificador del
                                                conjunto de semáforos */
        short *                 semadj;         /* matriz de ajustes, una
                                                por semáforo */

};

Funciones de Soporte de Semáforos

Las siguientes funciones son usadas específicamente para soportar los semáforos:

newary()

newary() confía en la función ipc_alloc() para asignar la memoria requerida para el nuevo conjunto de semáforos. El asigna suficiente memoria para el conjunto de descriptores del semáforo y para cada uno de los semáforos en el conjunto. La memoria asignada es limpiada, y la dirección del primer elemento del conjunto de descriptores del semáforo es pasada a ipc_addid(). ipc_addid() reserva una entrada de la matriz para el conjunto de descriptores del semáforo e inicializa los datos ( struct kern_ipc_perm) para el conjunto. La variavle global used_sems es actualizada por el número de semáforos en el nuevo conjunto y la inicialización de los datos ( struct kern_ipc_perm) para el nuevo conjunto es completada. Otras inicializaciones realizadas para este conjunto son listadas a continuación:

Todas las operaciones siguiendo la llamada a ipc_addid() son realizadas mientras se mantiene el spinlock global de los semáforos. Después de desbloquear el spinlock global de los semáforos, newary() llama a ipc_buildid() (a través de sem_buildid()). Esta función usa el índice del conjunto de descriptores del semáforo para crear una única ID, que es entonces devuelta al llamador de newary().

freeary()

freeary() es llamada por semctl_down() para realizar las funciones listadas a continuación. Es llamada con el spinlock global de los semáforos bloqueado y regresa con el spinlock desbloqueado.

semctl_down()

semctcl_down() suministra las operaciones IPC_RMID y IPC_SET de la llamada al sistema semctl(). La ID del conjunto de semáforos y los permisos de acceso son verificadas en ambas operaciones, y en ambos casos, el spinlock global del semáforo es mantenido a lo largo de la operación.

IPC_RMID

La operación IPC_RMID llama a freeary() para borrar el conjunto del semáforo.

IPC_SET

La operación IPC_SET actualiza los elementos uid, gid, mode, y ctime del conjunto de semáforos.

semctl_nolock()

semctl_nolock() es llamada por sys_semctl() para realizar las operaciones IPC_INFO, SEM_INFO y SEM_STAT.

IPC_INFO y SEM_INFO

IPC_INFO y SEM_INFO causan una antememoria temporal seminfo para que sea inicializada y cargada con los datos estadísticos sin cambiar del semáforo, los elementos semusz y semaem de la estructura seminfo son actualizados de acuerdo con el comando dado (IPC_INFO o SEM_INFO). El valor de retorno de las llamadas al sistema es establecido al conjunto máximo de IDs del conjunto de semáforos.

SEM_STAT

SEM_STAT causa la inicialización de la antememoria temporal semid64_ds. El spinlock global del semáforo es entonces mantenido mientras se copian los valores sem_otime, sem_ctime, y sem_nsems en la antememoria. Estos datos son entonces copiados al espacio de usuario.

semctl_main()

semctl_main() es llamado por sys_semctl() para realizar muchas de las funciones soportadas, tal como se describe en la sección posterior. Anteriormente a realizar alguna de las siguientes operaciones, semctl_main() cierra el spinlock global del semáforo y valida la ID del conjunto de semáforos y los permisos. El spinlock es liberado antes de retornar.

GETALL

La operación GETALL carga los actuales valores del semáforo en una antememoria temporal del núcleo y entonces los copia fuera del espacio de usuario. La pequeña pila de antememoria es usada si el conjunto del semáforo es pequeño. En otro caso, el spinlock es temporalmente deshechado en orden de asignar una antememoria más grande. El spinlock es mantenido mientras se copian los valores del semáforo en la antememoria temporal.

SETALL

La operación SETALL copia los valores del semáforo desde el espacio de usuario en una antememoria temporal, y entonces en el conjunto del semáforo. El spinlock es quitado mientras se copian los valores desde el espacio de usuario a la antememoria temporal, y mientras se verifican valores razonables. Si el conjunto del semáforo es pequeño, entonces una pila de antememoria es usada, en otro caso una antememoria más grande es asignado. El spinlock es recuperado y mantenido mientras las siguientes operaciones son realizadas en el conjunto del semáforo:

IPC_STAT

En la operación IPC_STAT, los valores sem_otime, sem_ctime, y sem_nsems son copiados en una pila de antememoria. Los datos son entonces copiados al espacio de usuario después de tirar con el spinlock.

GETVAL

Para GETVALL en el caso de no error, el valor de retorno para la llamada al sistema es establecido al valor del semáforo especificado.

GETPID

Para GETPID en el caso de no error, el valor de retorno para la llamada al sistema es establecido al pid asociado con las última operación del semáforo.

GETNCNT

Para GETNCNT en el caso de no error, el valor de retorno para la llamada al sistema de establecido al número de procesos esperando en el semáforo siendo menor que cero. Este número es calculado por la función count_semncnt().

GETZCNT

Para GETZCNT en la caso de no error, el valor de retorno para la llamada al sistema es establecido al número de procesos esperando en el semáforo estando establecido a cero. Este número es calculado por la función count_semzcnt().

SETVAL

Después de validar el nuevo valor del semáforo, las siguientes funciones son realizadas:

count_semncnt()

count_semncnt() cuenta el número de tareas esperando por el valor del semáforo para que sea menor que cero.

count_semzcnt()

count_semzcnt() cuenta el número de tareas esperando por el valor del semáforo para que sea cero.

update_queue()

update_queue() recorre la cola de semops pendientes para un conjunto de un semáforo y llama a try_atomic_semop() para determinar qué secuencias de las operaciones de los semáforos serán realizadas. Si el estado de la cola de elementos indica que las tareas bloqueadas ya han sido despertadas, entonces la cola de elementos es pasada por alto. Para los otros elementos de la cola, la bandera q-alter es pasada como el parámetro deshacer a try_atomic_semop(), indicando que cualquier operación alterante debería de ser deshecha antes de retornar.

Si la secuencia de operaciones bloquearan, entonces update_queue() retornará sin hacer ningún cambio.

Una secuencia de operaciones puede fallar si una de las operaciones de los semáforos puede causar un valor inválido del semáforo, o una operación marcada como IPC_NOWAIT es incapaz de completarse. En este caso, la tarea que es bloqueada en la secuencia de las operaciones del semáforo es despertada, y la cola de status es establecida con un código de error apropiado. El elemento de la cola es también quitado de la cola.

Si la secuencia de las operaciones no es alterante, entonces ellas deberían de pasar un valor cero como parámetro deshacer a try_atomic_semop(). Si estas operaciones tienen éxito, entonces son consideradas completas y son borradas de la cola. La tarea bloqueada es despertada, y el elemento de la cola status es establecido para indicar el éxito.

Si la secuencia de las operaciones pueden alterar los valores del semáforo, pero puede tener éxito, entonces las tareas durmiendo que no necesiten ser más bloqueadas tienen que ser despertadas. La cola status es establecida a 1 para indicar que la tarea bloqueada ha sido despertada. Las operaciones no han sido realizadas, por lo tanto el elemento de la cola no es quitado de la cola. Las operaciones del semáforo serán ejecutadas por la tarea despertada.

try_atomic_semop()

try_atomic_semop() es llamada por sys_semop() y update_queue() para determinar si una secuencia de operaciones del semáforo tendrán éxito. El determina esto intentando realizar cada una de las operaciones.

Si una operación bloqueante es encontrada, entonces el proceso es abortado y todas los operaciones son deshechas. -EAGAIN es devuelto si IPC_NOWAIT es establecido. En otro caso, es devuelto 1 para indicar que la secuencia de las operaciones del semáforo está bloqueada.

Si un valor del semáforo es ajustado más alla de los límites del sistema, entonces todas las operaciones son deshechas, y -ERANGE es retornado.

Si todas las operaciones de la secuencia tienen éxito, y el parámetro do_undo no es cero, entonces todas las operaciones son deshechas, y 0 es devuelto. Si el parámetro do_undo es cero, entonces todas las operaciones tienen éxito y continúan obligadas, y el sem_otime, campo del conjunto de semáforos es actualizado.

sem_revalidate()

sem_revalidate() es llamado cuando el spinlock global del semáforo ha sido temporalmente tirado y necesita ser bloqueado otra vez. Es llamado por semctl_main() y alloc_undo(). Valida la ID del semáforo y los permisos, y si tiene éxito retorna con el spinlock global de los semáforos bloqueado.

freeundos()

freeundos() recorre las lista de procesos por deshacer en busca de la estructura deshacer deseada. Si es encontrada, la estructura deshacer es quitada de la lista y liberada. Un puntero a la siguiente estructura deshacer en la lista de procesos es devuelta.

alloc_undo()

alloc_undo() espera ser llamada con el spinlock global de los semáforos cerrado. En el caso de un error, regresa con él desbloqueado.

El spinlock global de los semáforos es desbloqueado, y kmallock() es llamado para asignar suficiente memoria para la estructura sem_undo, y también para un array de uno de los valores de ajuste para cada semáforo en el conjunto. Si tiene éxito, el spinlock es recuperado con una llamada a sem_revalidate().

La nueva estructura semundo es entonces inicializada, y la dirección de esta estructura es colocada en la dirección suministrada por el llamante. La nueva estructura deshacer es entonces colocada en la cabeza de la lista deshacer para la actual tarea.

sem_exit()

sem_exit() es llamada por do_exit(), y es la responsable de ejecutar todos los ajustes deshacer para la tarea saliente.

Si el actual proceso fue bloqueado en un semáforo, entonces es borrado desde la lista sem_queue mientras se mantiene el spinlock global de los semáforos.

La lista deshacer para la actual tarea es entonces recorrida, y las siguientes operaciones son realizadas mientras se mantienen y liberan los spinlocks globales de los semáforos a lo largo del procesamiento de cada elemento de la lista. Las siguientes operaciones son realizadas para cada uno de los elementos deshacer:

Cuando el procesamiento de la lista está completo, el valor current->semundo es limpiado.

5.2 Colas de Mensajes

Interfaces de las llamadas al sistema de Colas

sys_msgget()

La llamada entera a sys_msgget() es protegida por el semáforo global de la cola de mensajes ( msg_ids.sem).

En el caso donde una nueva cola de mensajes tiene que ser creada, la función newque() es llamada para crear e inicializar una nueva cola de mensajes, y la nueva ID de la cola es devuelta al llamante.

Si un valor llave es suministrado para una cola de mensajes existente, entonces ipc_findkey() es llamada para mirar el índice correspondiente en la matriz de colas globales de descriptores de mensajes (msg_ids.entries). Los parámetros y los permisos del llamante son verificados antes de devolver la ID de la cola de mensajes. Las operaciónes de búsqueda y verificación son realizadas mientras el spinlock global de la cola de mensajes (msg_ids.ary) es mantenido.

sys_msgctl()

Los parámetros pasados a sys_msgctl() son: una ID de una cola de mensajes (msqid), la operación (cmd), y un puntero al espacio de la antememoria msgid_ds del tipo (buf). Seis operaciones son suministradas en esta función: IPC_INFO, MSG_INFO,IPC_STAT, MSG_STAT, IPC_SET y IPC_RMID. La ID de la cola de mensajes y los parámetros de la operación son validados; entonces, la operación (cmd) es realizada como sigue:

IPC_INFO (o MSG_INFO)

La información de la cola global de mensajes es copiada al espacio de usuario.

IPC_STAT (o MSG_STAT)

Una antememoria temporal del tipo struct msqid64_ds es inicializado y el spinlock de la cola de mensajes global es cerrado. Después de verificar los permisos de acceso del proceso llamante, la información de la cola de mensajes asociada con la ID de la cola de mensajes es cargada en una antememoria temporal, el spinlock de la cola de mensajes global es abierto, y los contenidos de la antememoria temporal son copiados fuera del espacio de usuario por copy_msqid_to_user().

IPC_SET

Los datos del usuario son copiados a través de copy_msqid_to_user(). El semáforo de la cola de mensajes global y el spinlock son obtenidos y liberados al final. Después de que la ID de la cola de mensajes y los permisos de acceso del actual proceso hayan sido validados, la información de la cola de mensajes es actualizada con los datos suministrados por el usuario, Después, expunge_all() y ss_wakeup() son llamadas para despertar todos los procesos durmiendo en las colas de espera del emisor y del receptor de las colas de mensajes. Esto es el motivo por el que algunos receptores quizás sean ahora excluidos por permisos de acceso estrictos y alguno de los emisores sean capaces ahora de enviar el mensaje debido a un incremento del tamaño de la cola.

IPC_RMID

El semáforo de la cola de mensajes global es obtenido y el spinlock global de la cola de mensajes es cerrado. Después de validar la ID de la cola de mensajes y los permisos de acceso de la actual tarea, freeque() es llamado para liberar los recursos relacionados con la ID de la cola de mensajes. El semáforo de la cola de mensajes global y el spinlock son liberados.

sys_msgsnd()

sys_msgsnd() recibe como parámetros una ID de una cola de mensajes (msqid), un puntero a la antememoria del tipo struct msg_msg (msgp), el tamaño del mensaje a ser enviado (msgsz), y una bandera indicando esperar o no esperar (msgflg). Hay dos tareas esperando las colas y un mensaje esperando la cola asociada con la ID de la cola de mensajes. Si hay una nueva tarea en la cola de espera del receptor que está esperando por este mensaje, entonces el mensaje es enviado directamente al receptor. En otro caso, si hay suficiente espacio disponible en la cola de espera de mensajes, el mensaje es guardado en esta cola. Como último recurso, la tarea emisora se encola a si misma en la cola de espera del emisor. Una discusión con más profundidad de las operaciones realizadas por sys_msgsnd() es la siguiente:

  1. Valida la dirección de la antememoria del usuario y el tipo de mensaje, entonces invoca a load_msg() para cargar el contenido del mensaje del usuario en un objeto temporal msg del tipo struct msg_msg. El tipo de mensaje y los campos del tamaño del mensaje de msg también son inicializados.
  2. Cierra el spinlock global de la cola de mensajes y coge el descriptor asociado con la cola de mensajes con la ID de la cola de mensajes. Si dicha cola de mensajes no existe, retorna EINVAL.
  3. Invoca a ipc_checkid() (a través de msg_checkid()) para verificar que la ID de la cola de mensajes es válida y llama a ipcperms() para chequear los permisos de acceso de proceso llamante.
  4. Chequea el tamaño del mensaje y el espacio que sobra en la cola de espera de mensajes para ver si hay suficiente espacio para almacenar el mensaje. Si no, los siguiens subpasos son realizados:
    1. Si IPC_NOWAIT es especificado en msgflg el spinlock global de la cola de mensajes es abierto, los recursos de memoria para el mensaje son liberados, y EAGAIN es retornado.
    2. Invoca a ss_add() para encolar la actual tarea en la cola de espera del emisor. También abre al spinlock global de la cola de mensajes e invoca a schedule() para poner la actual tarea a dormir.
    3. Cuando es despertado, obtiene el spinlock global otra vez y verifica que la ID de la cola de mensajes es todavía válida. Si la ID de la cola de mensajes no es valida, ERMID es retornado.
    4. Invoca ss_del() para quitar la tarea emisora de la cola de espera del emisor. Si hay alguna señal pendiente para la tarea, sys_msgsnd() abre el spinlock global, invoca a free_msg() para liberar la antememoria del mensaje, y retorna EINTR. En otro caso, la función va a back para chequear otra vez cuando hay otra vez suficiente sitio en la cola de espera de mensajes.
  5. Invoca pipelined_send() para intentar enviar el mensaje a la cola receptora directamente.
  6. Si no hay receptores esperando por este mensaje, desencola msg en la cola de mensajes esperando (msq->q_messages). Actualiza los campos q_cbytes y q_qnum del descriptor de la cola de mensajes, al igual que las variables globales msg_bytes y msg_hdrs, las cuales indican el número de bytes total usados por los mensajes y el número total de mensajes a lo largo del sistema.
  7. Si el mensaje ha sido enviado con éxito o encolado, actualiza los campos q_lspid y q_stime del descriptor de la cola de mensajes y abre el spinlock de colas de mensajes global.

sys_msgrcv()

La función sys_msgrcv() recibe como parámetro una ID de una cola de mensajes (msqid), un puntero a una antememoria del tipo msg_msg (msgp), el tamaño del mensaje deseado (msgsz), el tipo de mensaje (msgtyp), y las banderas (msgflg). Busca las colas de mensajes esperando asociadas con la ID de la cola de mensajes, encuentra el primer mensaje en la cola, la cual comprueba el tipo pedido y lo copia en la antememoria del usuario dado. Si tal mensaje no es encontrado en la cola de mensajes esperando, la tarea pedida es encolada en la cola de receptores esperando hasta que el mensaje deseado está disponible. Una discución más profunda de las operaciones realizadas por sys_msgrcv() es la que sigue:

  1. Primero, invoca a convert_mode() para derivar el modo de búsqueda desde msgtyp. sys_msgrcv() entonces cierra el spinlock global de la cola de mensajes y obtiene el descriptor de la cola de mensajes asociado con la ID de la cola de mensajes. Si tal cola de mensajes no existe, retorna EINVAL.
  2. Chequea cuando la tarea actual tiene los permisos correctos para acceder a la cola de mensajes.
  3. Empezando desde el primer mensaje en la cola de mensajes de espera, invoca a testmsg() para chequear cuando el tipo del mensaje se empareja con el tipo requerido. sys_msgrcv() continúa buscando hasta que un mensaje emparejado es encontrado o la cola de espera entera está exausta. Si el modo de búsqueda es SEARCH_LESSEQUAL, entonces es buscado el primer mensaje en la cola con un tipo más bajo o igual a msgtyp.
  4. Si un mensaje en encontrado, sys_msgrcv() realiza los siguientes subpasos:
    1. Si el tamaño del mensaje es más grande que el tamaño deseado y msgflg indica que no se permiten errores, abre el spinlock de cola de mensajes global y retorna E2BIG.
    2. Quita el mensaje de la cola de mensajes esperando y actualiza las estadísticas de la cola de mensajes.
    3. Despierta todas las tareas durmiendo en las colas de espera de los emisores. El borrado de un mensaje de la cola en los pasos previos hace posible que uno de los emisores progresen. Va a través de last step.
  5. Si no hay mensajes emparejados el criterio del receptor es encontrado en la cola de mensajes esperando, entonces msgflg es chequeado. Si IPC_NOWAIT está establecido, entonces el spinlock global de la cola de mensajes es abierto y ENOMSG es retornado. En otro caso, el receptor es encolado en la cola de espera del receptor como sigue:
    1. Una estructura de datos msg_receiver msr es asignada y es añadida en la cabeza de la cola de espera.
    2. El campo r_tsk de msr es establecido a la tarea actual.
    3. Los campos r_msgtype y r_mode son inicializados con el tipo y modo del mensaje deseado respectivamente.
    4. Si msgflg indica MSG_NOERROR, entonces el campo r_maxsize de msr es establecido para ser el valor de msgsz. En otro caso es establecido para ser INT_MAX.
    5. El campo r_msg es inicializado para indicar que todavía no ha sido recibido el mensaje.
    6. Después de que la inicialización está completa, el estado de la tarea receptora es establecido a TASK_INTERRUPTIBLE, el spinlock global de colas de mensajes es abierto, y schedule() es invocado.
  6. Después de que el receptor es despertado, el campo r_msg de msr es chequeado. Este campo es usado para almacenar el mensaje entubado o, en el caso de un error, almacenar el estado del error. Si el campo r_msg es rellenado con el mensaje deseado, entonces va a last step. En otro caso, el spinlock global de colas de mensajes es cerrado otra vez.
  7. Después de obtener el spinlock, el campo r_msg es re-chequeado para ver si el mensaje fue recibido mientras se estaba esperando por el spinlock. Si el mensaje ha sido recibido, ocurre el last step.
  8. Si el campo r_msg todavía está sin cambiar, entonces la tarea tiene que ser despertada en orden de reintentarlo. En este caso, msr es quitado de la cola. Si hay una señal pendiente para la tarea, entonces el spinlock de la cola de mensajes global es abierto y EINTR es retornado. En otro caso, la función necesita ir a back y reintentarlo.
  9. Si el campo r_msg muestra que ha ocurrido un error mientras estaba durmiendo, el spinlock de la cola de mensajes global es abierto y el error es devuelto.
  10. Después de validar que la dirección de la antememoria del usuario msp es válida, el tipo de mensaje es cargado en el campo mtype de msp, y store_msg() es invocado para copiar el contenido del mensaje al campo de msp. Finalmente la memoria para el mensaje es liberada por la función free_msg().

Estructuras Específicas de Mensajes

Las estructuras de datos para las colas de mensajes están definidas en msg.c.

struct msg_queue


/* una estructura msq_queue para cada cola presente en el sistema */
struct msg_queue {
        struct kern_ipc_perm q_perm;
        time_t q_stime;                 /* último tiempo del mensaje enviado */
        time_t q_rtime;                 /* último tiempo del mensaje recibido */
        time_t q_ctime;                 /* útimo tiempo de cambio */
        unsigned long q_cbytes;         /* número actual de bytes en la cola */
        unsigned long q_qnum;           /* número de mensajes en la cola */
        unsigned long q_qbytes;         /* máximo número de bytes en la cola */
        pid_t q_lspid;                  /* último pid del mensaje recibido */
        pid_t q_lrpid;                  /* último pid del mensaje recibido */

        struct list_head q_messages;
        struct list_head q_receivers;
        struct list_head q_senders;
};

struct msg_msg


/* una estructura msg_msg para cada mensaje */
struct msg_msg {
        struct list_head m_list;
        long  m_type;
        int m_ts;           /* tamaño del mensaje de texto */
        struct msg_msgseg* next;
        /* el mensaje actual sigue inmediatamente */
};

struct msg_msgseg


/* segmento de mensaje para cada mensaje */
struct msg_msgseg {
        struct msg_msgseg* next;
        /* la siguiente parte del mensaje sigue inmediatamente */
};

struct msg_sender


/* un msg_sender para cada emisor durmiendo */
struct msg_sender {
        struct list_head list;
        struct task_struct* tsk;
};

struct msg_receiver


/* una estructura msg_receiver para cada receptor durmiendo */
struct msg_receiver {
        struct list_head r_list;
        struct task_struct* r_tsk;

        int r_mode;
        long r_msgtype;
        long r_maxsize;

        struct msg_msg* volatile r_msg;
};

struct msqid64_ds


struct msqid64_ds {
        struct ipc64_perm msg_perm;
        __kernel_time_t msg_stime;      /* último tiempo del mensaje enviado */
        unsigned long   __unused1;
        __kernel_time_t msg_rtime;      /* último tiempo del mensaje recibido */
        unsigned long   __unused2;
        __kernel_time_t msg_ctime;      /* último tiempo de cambio */
        unsigned long   __unused3;
        unsigned long  msg_cbytes;      /* número actual de bytes en la cola */
        unsigned long  msg_qnum;        /* número de mensajes en la cola */
        unsigned long  msg_qbytes;      /* número máximo de bytes en la cola */
        __kernel_pid_t msg_lspid;       /* pid del último mensaje enviado */
        __kernel_pid_t msg_lrpid;       /* pid del último mensaje recibido */
        unsigned long  __unused4;
        unsigned long  __unused5;
};

struct msqid_ds


 struct msqid_ds {
        struct ipc_perm msg_perm;
        struct msg *msg_first;          /* primer mensaje en la cola, no usado */
        struct msg *msg_last;           /* último mensaje en la cola, no usado */
        __kernel_time_t msg_stime;      /* último tiempo del mensaje enviado */
        __kernel_time_t msg_rtime;      /* último tiempo del mensaje recibido */
        __kernel_time_t msg_ctime;      /* último tiempo de cambio */
        unsigned long  msg_lcbytes;     /* reusar los campos borrados para 32 bit */
        unsigned long  msg_lqbytes;     /* idem */
        unsigned short msg_cbytes;      /* número actual de bytes en la cola */
        unsigned short msg_qnum;        /* número de mensajes en la cola */
        unsigned short msg_qbytes;      /* número máximo de bytes en la cola */
        __kernel_ipc_pid_t msg_lspid;   /* último pid del mensaje enviado */
        __kernel_ipc_pid_t msg_lrpid;   /* último pid del mensaje recibido */
};

msg_setbuf


struct msq_setbuf {
        unsigned long   qbytes;
        uid_t           uid;
        gid_t           gid;
        mode_t          mode;
};

Funciones de Soporte de Mensajes

newque()

newqueue() asigna la memoria para un nuevo descriptor de una cola de mensajes ( struct msg_queue) y entonces llama a ipc_addid(), la cual reserva una entrada de la matriz de colas de mensaje para el nuevo descriptor de cola de mensajes. El descriptor de cola de mensajes es inicializado como sigue:

Todas las operaciones siguiendo la llamada a ipc_addid() son realizadas mientras se mantiente el spinlock global de cola de mensajes. Después de abrir el spinlock, newque() llama a msg_buildid(), que mapea directamente a ipc_buildid(). ipc_buildid() usa el índice del descriptor de cola de mensajes para crear una única ID de cola de mensaje que es entonces retornada al llamante de newque().

freeque()

Cuando una cola de mensajes va a ser borrada, la función freeque() es llamada. Esta función asume que el spinlock global de la cola de mensajes ya está cerrado por la función llamante. Libera todos los recursos del núcleo asociados con esta cola de mensajes. Primero, llama a ipc_rmid() (a través de msg_rmid()) para borrar el descriptor de cola de mensajes del array de descriptores de cola de mensajes global. Entonces llama a expunge_all para despertar a todos los receptores durmiendo en esta cola de mensajes. Posteriormente el spinlock global de la cola de mensajes es liberado. Todos los mensajes almacenados en esta cola de mensajes son liberados y la memoria para los descriptores de cola son liberados.

ss_wakeup()

ss_wakeup() despierta todas las tareas en la cola de mensajes del emisor dado. Si esta función es llamada por freeque(), entonces todos los emisores en la cola son quitados de ella.

ss_add()

ss_add() recibe como parámetro un descriptor de cola de mensajes y un mensaje de estructura de datos del emisor. Rellena el campo tsk del mensaje de la estructura de datos del emisor con el proceso actual, cambia el estado del proceso actual a TASK_INTERRUPTIBLE, entonces inserta el mensaje de la estructura de datos del emisor a la cabeza de la cola de emisores esperando la cola de mensajes dada.

ss_del()

Si el mensaje de la estrutura de datos del emisor dado (mss) aún está en la cola de espera del emisor asociado, entonces ss_del() quita mss de la cola.

expunge_all()

expunge_all() recibe como parámetros un descriptor de la cola de mensajes (msq) y un valor entero (res) indicando el motivo para despertar a los receptores. Para cada receptor durmiendo asociado con msq, el campo r_msg es establecido para indicar el motivo para despertar (res), y la tarea asociada recibiendo es despertada. Esta función es llamada cuando una cola de mensajes es quitada o un operación de control de mensajes ha sido realizada.

load_msg()

Cuando un proceso envía un mensaje, la función sys_msgsnd() primero invoca a la función load_msg() para cargar el mensaje desde al espacio de usuario al espacio del núcleo. El mensaje es representado en la memoria del núcleo como una lista enlazada de bloques de datos. Asociado con el primer bloque de datos está una estructura msg_msg que describe el mensaje completo. El bloque de datos asociado con la estructura msg_msg está limitado por el tamaño de DATA_MSG_LEN. El bloque de datos y la estructura son asignados en un bloque contiguo de memoria que puede ser tan grande como un página en memoria. Si el mensaje total no se ajusta en este primer bloque de datos, entonces bloques de datos adicionales son asignados y son reorganizados en una lista enlazada. Estos bloques de datos están limitados por un tamaño de DATA_SEG_LEN, y cada uno incluye una estructura msg_msgseg). La estructura msg_msgseg y los bloques de datos asociados son asignados en un bloque de memoria contigua que puede ser tan grande como una página en memoria. Esta función retorna la dirección de la nueva estructura msg_msg si es que tiene éxito.

store_msg()

La función store_msg() es llamada por sys_msgrcv() para reensamblar un mensaje recibido en la antememoria del espacio de usuario suministrado por el llamante. Los datos descritos por la estructura msg_msg y cualquier estructura msg_msgseg son secuencialmente copiados a la antememoria del espacio de usuario.

free_msg()

La función free_msg() libera la memoria para una estructura de datos de mensaje msg_msg, y los segmentos del mensaje.

convert_mode()

convert_mode() es llamada por sys_msgrcv(). Recibe como parámetros las direcciones del tipo del mensaje especificado (msgtyp) y una bandera (msgflg). Devuelve el modo de búsqueda al llamante basado en el valor de msgtyp y msgflg. Si msgtyp es null (cero), entonces SEARCH_ANY es devuelto, Si msgtyp es menor que 0, entonces msgtyp es establecido a su valor absoluto y SEARCH_LESSEQUAL es retornado. Si MSG_EXCEPT es especificado en msgflg, entonces SEARCH_NOTEQUAL es retornado. En otro caso SEARCH_EQUAL es retornado.

testmsg()

La función testmsg() chequea cuando un mensaje conoce el criterio especificado por el receptor. Devuelve 1 si una de las siguientes condiciones es verdad:

pipelined_send()

pipelined_send() permite a un proceso enviar directamente un mensaje a la cola de receptores mejor que depositar el mensaje en la cola asociada de mensajes esperando. La función testmsg() es invocada para encontrar el primer receptor que está esperando por el mensaje dado. Si lo encuentra, el receptor esperando es quitado de la cola de receptores esperando, y la tarea receptora asociada es despertada. El mensaje es almacenado en el campo r_msg del receptor, y 1 es retornado. En el caso donde no hay un receptor esperando por el mensaje, 0 es devuelto.

En el proceso de búsqueda de un receptor, los receptores potenciales quizás encuentren que han solicitado un tamaño que es muy pequeño para el mensaje dado. Tales receptores son quitados de la cola, y son despertados con un status de error de E2BIG, el cual es almacenado en el campo r_msg. La búsqueda entonces continúa hasta que alguno de los receptores válidos es encontrado, o la cola está exausta.

copy_msqid_to_user()

copy_msqid_to_user() copia el contenido de una antememoria del núcleo a la antememoria del usuario. Recibe como parámetros una antememoria del usuario, una antememoria del núcleo del tipo msqid64_ds, y una bandera versión indicando la nueva versión IPC vs. la vieja versión IPC. Si la bandera de la versión es igual a IPC_64, entonces copy_to_user() es llamado para copiar desde la antememoria del núcleo a la antememoria del usuario directamente. En otro caso una antememoria temporal del tipo struct msqid_ds es inicializada, y los datos del núcleo son trasladados a esta antememoria temporal. Posteriormente copy_to_user() es llamado para copiar el contenido de la antememoria temporal a la antememoria del usuario.

copy_msqid_from_user()

La función copy_msqid_from_user() recibe como parámetros un mensaje de la antememoria del núcleo del tipo struct msq_setbuf, una antememoria de usuario y una bandera de la versión indicando la nueva versión IPC vs. la vieja versión IPC. En la caso de la nueva versión IPC, copy_from_user() es llamada para copiar el contenido de la antememoria del usuario a una antememoria temporal del tipo msqid64_ds. Entonces, los campos qbytes,uid, gid, y mode de la antememoria del núcleo son rellenados con los valores de los campos correspondientes desde la antememoria temporal. En el caso de la vieja versión IPC, una antememoria temporal del tipo struct msqid_ds es usado en su vez.

5.3 Memoria Compartida

Interfaces de las llamadas al sistema de la MemoriaCompartida

sys_shmget()

La llamada entera a sys_shmget() es protegida por el semáforo global de memoria compartida.

En el caso donde un valor de llave es suministrado para un segmento existente de memoria compartida, el correspondiente índice es buscado en la matriz de descriptores de memoria compartida, y los parámetros y los permisos del llamante son verificados antes de devolver la ID del segmento de memoria compartida. Las operaciones de búsqueda y verificación son realizadas mientras es mantenido el spinlock global de memoria compartida.

sys_shmctl()

IPC_INFO

Una antememoria temporal shminfo64 es cargada con los parámetros del sistema de memoria compartida y es copiada fuera del espacio de usuario para el acceso de la aplicación llamante.

SHM_INFO

El semáforo global de memoria compartida y el spinlock global de memoria compartida son mantenidos mientras se obtienen estadísticas de la información del sistema para la memoria compartida. La función shm_get_stat() es llamada para calcular el número de páginas de memoria compartidas que están residentes en memoria y el número de páginas de memoria compartida que han sido intercambiadas (swapped out). Otras estadísticas incluyen el número total de páginas de memoria compartida y el número de segmentos de memoria compartida en uso. Las cuentas de swap_attempts y swap_successes son codificadas fuertemente a cero. Estas estadísticas son almacenadas en una antememoria temporal shm_info y copiadas fuera del espacio de usuario para la aplicación llamante.

SHM_STAT, IPC_STAT

Para SHM_STAT y IPC_STATA, una antememoria temporal del tipo struct shmid64_ds es inicializada, y el spinlock global de memoria compartida es cerrado.

Para el caso SHM_STAT, el parámetro ID del segmento de memoria compartida se espera que sea un índice exacto (esto es, 0 a n donde n es el número de IDs de memoria compartida en el sistema). Después de validar el índice, ipc_buildid() es llamado (a través de shm_buildid()) para convertir el índice en una ID de memoria compartida. En el caso transitorio de SHM_STAT, la ID de la memoria compartida será el valor de retorno. Notar que esta es una característica no documentada, pero es mantenida para el programa ipcs(8).

Para el caso IPC_STAT, el parámetro ID del segmento de memoria compartida se espera que sea una ID que ha sido generada por una llamada a shmget(). La ID es validada antes de proceder. En el caso transitorio de IPC_STAT, el valor de retorno será 0.

Para SHM_STAT y IPC_STAT, los permisos de acceso del llamante son verificados. Las estadísticas deseadas son cargadas en la antememoria temporal y entonces copiadas fuera de la aplicación llamante.

SHM_LOCK, SHM_UNLOCK

Después de validar los permisos de acceso, el spinlock global de memoria compartida es cerrado, y la ID del segmento de memoria compartida es validado. Para SHM_LOCK y SHM_UNLOCK, shmem_lock() es llamada para realizar la función. Los parámetros para shmem_lock() identifican la función a realizar.

IPC_RMID

Durante el IPC_RMID el semáforo global de memoria compartida y el spinlock global de memoria compartida son mantenidos a través de esta función. La ID de la Memoria Compartida es validada, y entonces si no hay conexiones actuales, shm_destroy() es llamada para destruir el segmento de memoria compartida. En otro caso, la bandera SHM_DEST es establecida para marcarlo para destrucción, y la bandera IPC_PRIVATE es establecida para prevenir que otro proceso sea capaz de referenciar la ID de la memoria compartida.

IPC_SET

Después de validar la ID del segmento de memoria compartida y los permisos de acceso del usuario, las banderas uid, gid, y mode del segmento de la memoria compartida son actualizadas con los datos del usuario. El campo shm_ctime también es actualizado. Estos cambios son realizados mientras se mantiene el semáforo global de memoria compartida global y el spinlock global de memoria compartida.

sys_shmat()

sys_shmat() toma como parámetro, una ID de segmento de memoria compartida, una dirección en la cual el segmento de memoria compartida debería de ser conectada (shmaddr), y las banderas que serán descritas más adelante.

Si shmaddr no es cero, y la bandera SHM_RND es especificada, entonces shmaddr es redondeado por abajo a un múltiplo de SHMLBA. Si shmaddr no es un múltiplo de SHMLBA y SHM_RND no es especificado, entonces EINVAL es devuelto.

Los permisos de acceso del llamante son validados y el campo shm_nattch del segmento de memoria compartida es incrementado. Nótese que este incremento garantiza que la cuenta de enlaces no es cero y previene que el segmento de memoria compartida sea destruido durante el proceso de enlazamiento al segmento. Estas operaciones son realizadas mientras se mantiene el spinlock global de memoria compartida.

La función do_mmap() es llamada para crear un mapeo de memoria virtual de las páginas del segmento de memoria compartida. Esto es realizado mientras se mantiene el semáforo mmap_sem de la tarea actual. La bandera MAP_SHARED es pasada a do_mmap(). Si una dirección fue suministrada por el llamante, entonces la bandera MAP_FIXED también es pasada a do_mmap(). En otro caso, do_mmap() seleccionará la dirección virtual en la cual mapear el segmento de memoria compartida.

NÓTESE que shm_inc() será invocado con la llamada a la función do_mmap() a través de la estructura shm_file_operations. Esta función es llamada para establecer el PID, para establecer el tiempo actual, y para incrementar el número de enlaces a este segmento de memoria compartida.

Después de la llamada a do_mmap(), son obtenidos el semáforo global de memoria compartida y el spinlock global de la memoria compartida. La cuenta de enlaces es entonces decrementada. El siguiente cambio en la cuenta de enlaces es 1 para una llamada a shmat() por culpa de la llamada a shm_inc(). Si, después de decrementar la cuenta de enlaces, la cuenta resultante que se encuentra es cero, y el segmento se marca para la destrucciónn (SHM_DEST), entonces shm_destroy() es llamado para liberar los recursos del segmento de memoria compartida.

Finalmente, la dirección virtual en la cual la memoria compartida es mapeada es devuelta al llamante en la dirección especificada por el usuario. Si un código de error ha sido retornado por do_mmap(), entonces este código de fallo es pasado en el valor de retorno para la llamada al sistema.

sys_shmdt()

El semáforo global de la memoria compartida es mantenido mientras se realiza sys_shmdt(). La mm_struct del actual proceso es buscado para la vm_area_struct asociada con la dirección de memoria compartida. Cuando es encontrada, do_munmap() es llamado para deshacer el mapeo de direcciones virtuales para el segmento de la memoria compartida.

Nótese también que do_munmap() realiza una llamada atrás a shm_close(), la cual realiza las funciones manteniendo el libro de memoria compartida, y libera los recursos del segmento de memoria compartida si no hay más enlaces.

sys_shmdt() incondicionalmente devuelve 0.

Estructuras de Soporte de Memoria Compartida

struct shminfo64


struct shminfo64 {
        unsigned long   shmmax;
        unsigned long   shmmin;
        unsigned long   shmmni;
        unsigned long   shmseg;
        unsigned long   shmall;
        unsigned long   __unused1;
        unsigned long   __unused2;
        unsigned long   __unused3;
        unsigned long   __unused4;
};

struct shm_info


struct shm_info {
        int used_ids;
        unsigned long shm_tot;  /* shm asignada total */
        unsigned long shm_rss;  /* shm residente total */
        unsigned long shm_swp;  /* shm intercambiada total */
        unsigned long swap_attempts;
        unsigned long swap_successes;
};

struct shmid_kernel


struct shmid_kernel /* privadas del núcleo */
{
        struct kern_ipc_perm    shm_perm;
        struct file *           shm_file;
        int                     id;
        unsigned long           shm_nattch;
        unsigned long           shm_segsz;
        time_t                  shm_atim;
        time_t                  shm_dtim;
        time_t                  shm_ctim;
        pid_t                   shm_cprid;
        pid_t                   shm_lprid;
};

struct shmid64_ds


struct shmid64_ds {
        struct ipc64_perm       shm_perm;       /* permisos de la operación */
        size_t                  shm_segsz;      /* tamaño del segmento (bytes) */
        __kernel_time_t         shm_atime;      /* último tiempo de enlace */
        unsigned long           __unused1;
        __kernel_time_t         shm_dtime;      /* último tiempo de desenlace */
        unsigned long           __unused2;
        __kernel_time_t         shm_ctime;      /* último tiempo de cambio */
        unsigned long           __unused3;
        __kernel_pid_t          shm_cpid;       /* pid del creador */
        __kernel_pid_t          shm_lpid;       /* pid del último operador */
        unsigned long           shm_nattch;     /* número de enlaces actuales */
        unsigned long           __unused4;
        unsigned long           __unused5;
};

struct shmem_inode_info


struct shmem_inode_info {
        spinlock_t      lock;
        unsigned long   max_index;
        swp_entry_t     i_direct[SHMEM_NR_DIRECT]; /* para el primer bloque */
        swp_entry_t   **i_indirect; /* doble bloque indirecto */
        unsigned long   swapped;
        int             locked;     /* en la memoria */
        struct list_head        list;
};

Funciones de Soporte De Memoria Compartida

newseg()

La función newseg() es llamada cuando un nuevo segmento de memoria compartida tiene que ser creado. Actúa con tres parámetros para el nuevo segmento: la llave, la bandera y el tamaño, Después de validar que el tamaño del segmento de la memoria compartida que va a ser creado está entre SHMMIN y SHMMAX y que el número total de segmentos de memoria compartida no excede de SHMALL, asigna un nuevo descriptor de memoria compartida. La función shmem_file_setup() es invocada posteriormente a crear un archivo no enlazado del tipo tmpfs. El puntero del archivo retornado es guardado en el campo shm_file del descriptor del segmento de memoria compartida asociado. El nuevo descriptor de memoria compartida es inicializado e insertado en la matriz global de IPC de descriptores de segmentos de memoria compartida. La ID del segmento de memoria compartida es creado por shm_buildid() (a través de ipc_buildid()). La ID de este segmento es guardada en el campo id del descriptor de memoria compartida, al igual que en el campo i_ino del inodo asociado. En adicción, la dirección de las operaciones de memoria compartida definidas en la estructura shm_file_operation son almacenadas en el fichero asociado. El valor de la variable global shm_tot, que indica el número total de segmentos de memoria compartida a lo largo del sistema, es también incrementado para reflejar este cambio. Si tiene éxito, la ID del segmento es retornada a la aplicación llamante.

shm_get_stat()

Los ciclos de shm_get_stat() van a través de todas las estructuras de memoria compartida, y calcula el número total de páginas de memoria en uso por la memoria compartida y el número total de páginas de memoria compartida que están intercambiadas. Hay una estructura de fichero y una estructura de inodo para cada segmento de memoria compartida. Como los datos requeridos son obtenidos a través del inodo, el spinlock para cada estructura inodo que es accedido es cerrado y abierto en secuencia.

shmem_lock()

shmem_lock() recibe como parámetros un puntero al descriptor del segmento de memoria compartida y una bandera indicando cerrado vs. abierto. El estado de bloqueo del segmento de memoria compartida es almacenado en el inodo asociado. Este estado es comparado con el estado de bloqueo deseado: shmem_lock() simplemente retorna si ellos se corresponden.

Mientras se está manteniendo el semáforo del inodo asociado, el estado de bloqueo del inodo es establecido. La siguiente lista de puntos ocurren en cada página en el segmento de memoria compartida:

shm_destroy()

Durante shm_destroy() el número total de páginas de memoria compartida es ajustada para que cuente el borrado del segmento de memoria compartida. ipc_rmid() es llamado (a través de shm_rmid()) para borrar la ID de Memoria Compartida. shmem_lock es llamado para abrir las páginas de memoria compartida, efectivamente, decrementando la cuenta de referencia a cero para cada página. fput() es llamado para decrementar el contador de uso para f_count para el objeto fichero deseado, y si es necesario, para liberar los recursos del objeto fichero. kfree() es llamado para liberar el descriptor de segmento de memoria compartida.

shm_inc()

shm_inc() establece el PID, establece el tiempo actual, e incrementa el número de enlaces para el segmento de memoria compartida dado. Estas operaciones son realizadas mientras se mantiene el spinlock global de memoria compartida.

shm_close()

shm_close() actualiza los campos shm_lprid y shm_dtim y decrementa el número de segmentos enlazados de memoria compartida. Si no hay otros enlaces al segmento de memoria compartida, entonces shm_destroy() es llamado para liberar los recursos de la memoria compartida. Estas operaciones son todas realizadas mientras se mantienen el semáforo global de memoria compartida y el spinlock global de memoria compartida.

shmem_file_setup()

La función shmem_file_setup() configura un archivo sin enlazar que vive en el sistema de ficheros tmpfs con el nombre y tamaño dados. Si hay suficientes recursos de memoria para este fichero, crea una nueva dentry bajo la raiz montada de tmpfs, y asigna un nuevo descriptor de fichero y un nuevo objeto inodo del tipo tmpfs. Entonces asocia el nuevo objeto dentry con el nuevo objeto inodo llamando a d_instantiate() y guarda la dirección del objeto dentry en el descriptor de fichero. El campo i_size del objeto inodo es establecido para ser del tamaño del fichero y el campo i_nlink es establecido para ser 0 en orden a marcar el inodo no enlazado. También, shmem_file_setup() almacena la dirección de la estructura shmem_file_operations en el campo f_op, e inicializa los campos f_mode y f_vfsmnt del descriptor de fichero propio. La función shmem_truncate() es llamada para completar la inicialización del objeto inodo. Si tiene éxito, shmem_file_setup() devuelve el nuevo descriptor de fichero.

5.4 Primitivas IPC de Linux

Primitivas IPC de Linux Genéricas usadas con Semáforos, Mensajes yMemoria Compartida

Los semáforos, mensajes, y mecanismos de memoria compartida de Linux son construidos con un conjunto de primitivas comunes. Estas primitivas son descritas en las secciones posteriores.

ipc_alloc()

Si el asignamiento de memoria es mayor que PAGE_SIZE, entonces vmalloc() es usado para asignar memoria. En otro caso, kmalloc() es llamado con GFP_KERNEL para asignar la memoria.

ipc_addid()

Cuando un nuevo conjunto de semáforos, cola de mensajes, o segmento de memoria compartido es añadido, ipc_addid() primero llama a grow_ary() para asegurarse que el tamaño de la correspondiente matriz de descriptores es suficientemente grande para el máximo del sistema. La matriz de descriptores es buscada para el primer elemento sin usar. Si un elemento sin usar es encontrado, la cuenta de descriptores que están en uso es incrementada. La estructura kern_ipc_perm para el nuevo recurso descriptor es entonces inicializado, y el índice de la matriz para el nuevo descriptor es devuelto. Cuando ipc_addid() tiene éxito, retorna con el spinlock global cerrado para el tipo IPC dado.

ipc_rmid()

ipc_rmid() borra el descriptor IPC desde la matriz de descriptores global del tipo IPC, actualiza la cuenta de IDs que están en uso, y ajusta la máxima ID en la matriz de descriptores correspondiente si es necesario. Un puntero al descriptor asociado IPC con la ID del IPC dado es devuelto.

ipc_buildid()

ipc_buildid() crea una única ID para ser asociada con cada descriptor con el tipo IPC dado. Esta ID es creada a la vez que el nuevo elemento IPC es añadido (ej. un nuevo segmento de memoria compartido o un nuevo conjunto de semáforos). La ID del IPC se convierte fácilmente en el índice de la correspondiente matriz de descriptores. Cada tipo IPC mantiene una secuencia de números la cual es incrementada cada vez que un descriptor es añadido. Una ID es creada multiplicando el número de secuencia con SEQ_MULTIPLIER y añadiendo el producto al índice de la matriz de descriptores. La secuencia de números usados en crear una ID de un IPC particular es entonces almacenada en el descriptor correspondiente. La existencia de una secuencia de números hace posible detectar el uso de una ID de IPC sin uso.

ipc_checkid()

ipc_checkid() divide la ID del IPC dado por el SEQ_MULTIPLIER y compara el cociente con el valor seq guardado en el descriptor correspondiente. Si son iguales, entonces la ID del IPC se considera válida y 1 es devuelto. En otro caso, 0 es devuelto.

grow_ary()

grow_ary() maneja la posibilidad de que el número máximo (ajustable) de IDs para un tipo IPC dado pueda ser dinámicamente cambiado. Fuerza al actual límite máximo para que no sea mayor que el limite del sistema permanente (IPCMNI) y lo baja si es necesario. También se asegura de que la matriz de descriptores existente es suficientemente grande. Si el tamaño de la matriz existente es suficientemente grande, entonces el límite máximo actual es devuelto. En otro caso, una nueva matriz más grande es asignada, la matriz vieja es copiada en la nueva, y la vieja es liberada. El correspondiente spinlock global es mantenido mientras se actualiza la matriz de descriptores para el tipo IPC dado.

ipc_findkey()

ipc_findkey() busca a través de la matriz de descriptores del objeto especificado ipc_ids, y busca la llave especificada. Una vez encontrada, el índice del descriptor correspondiente es devuelto. Si la llave no es encontrada, entonces es devuelto -1.

ipcperms()

ipcperms() chequa el usuario, grupo, y otros permisos para el acceso de los recursos IPC. Devuelve 0 si el permiso está garantizado y -1 en otro caso.

ipc_lock()

ipc_lock() coge una ID de IPC como uno de sus parámetros. Cierra el spinlock global para el tipo IPC dado, y devuelve un puntero al descriptor correspondiente a la ID IPC especificada.

ipc_unlock()

ipc_unlock() libera el spinlock global para el tipo IPC indicado.

ipc_lockall()

ipc_lockall() cierra el spinlock global para el mecanismo IPC dado (esto es: memoria compartida, semáforos, y mensajes).

ipc_unlockall()

ipc_unlockall() abre el spinlock global para el mecanismo IPC dado (esto es: memoria compartida, semáforos, y mensajes).

ipc_get()

ipc_get() coge un puntero al tipo particular IPC (memoria compartida, semáforos o colas de mensajes) y una ID de un descriptor, y devuelve un puntero al descriptor IPC correspondiente. Nótese que aunque los descriptores para cada tipo IPC son tipos de datos diferentes, el tipo de estructura común kern_ipc_perm está embebida como la primera entidad en todos los casos. La función ipc_get() devuelve este tipo de datos común. El modelo esperado es que ipc_get() es llamado a través de la función envoltorio (ej. shm_get()) la cual arroja el tipo de datos al tipo de datos correcto del descriptor.

ipc_parse_version()

ipc_parse_version() borra la bandera IPC_64 desde el comando si está presente y devuelve IPC_64 o IPC_OLD.

Estructuras Genéricas IPC usadas con Semáforos,Mensajes, y Memoria Compartida

Todos los semáforos, mensajes, y mecanismos de memoria compartida hacen un uso de las siguientes estructuras comunes:

struct kern_ipc_perm

Cada descriptor IPC tiene un objeto de datos de este tipo como primer elemento. Esto hace posible acceder a cualquier descriptor desde cualquier función genérica IPC usando un puntero de este tipo de datos.


/* usados por la estructuras de datos en el núcleo */
struct kern_ipc_perm {
    key_t key;
    uid_t uid;
    gid_t gid;
    uid_t cuid;
    gid_t cgid;
    mode_t mode;
    unsigned long seq;
};

struct ipc_ids

La estructura ipc_ids describe los datos comunes para los semáforos, colas de mensajes, y memoria compartida. Hay tres instancias globales de esta estructura de datos --semid_ds, msgid_ds y shmid_ds-- para los semáforos, mensajes y memoria compartida respectivemente. En cada instancia, el semáforo sem es usado para proteger el acceso a la estructura. El campo entries apunta a una matriz de descriptores de IPC, y el spinlock ary protege el acceso a esta matriz. El campo seq es una secuencia de números global la cual será incrementada cuando un nuevo recurso IPC es creado.


struct ipc_ids {
    int size;
    int in_use;
    int max_id;
    unsigned short seq;
    unsigned short seq_max;
    struct semaphore sem;
    spinlock_t ary;
    struct ipc_id* entries;
};

struct ipc_id

Una matriz de estructuras ipc_id existe en cada instancia de la estructura ipc_ids. La matriz es dinámicamente asignada y quizás sea reemplazada con una matriz más grande por grow_ary() tal como requiere. La matriz es a veces referida por la matriz de descriptores, desde que el tipo de datos kern_ipc_perm es usado como tipo de datos de descriptores comunes por las funciones genéricas IPC.


struct ipc_id {
    struct kern_ipc_perm* p;
};


Página siguiente Página anterior Índice general