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.
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.
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.
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.
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:
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.
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.
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:
status
de la estructura
sem_queue está establecido
a 1, entonces la tarea es despertada en orden a reintentar las
operaciones del semáforo, Otra llamada a
try_atomic_semop() es
realizada para ejecutar la secuencia de las operaciones del
semáforo. Si try_atomic_sweep() devuelve 1, entonces la tarea
debe de bloquearse otra vez como se describió anteriormente.
En otro caso, se devuelve 0 en caso de éxito, o un
código de error apropiado en caso de fallo.
Antes de que sys_semop() regrese, current->semsleeping es
limpiado, y
sem_queue es
borrado de la cola. Si alguna de las operaciones del semáforo
especificada eran operaciones alteradoras (incremento o
decremento), entonces
update_queue() es llamado
para recorrer la cola de operaciones pendientes del semáforo
para el conjunto del semáforo y despertar cualquier tarea
dormida que no necesite bloquear más.
status
de la estructura
sem_queue NO está establecida
a 1, y el elemento
sem_queue
no ha sido quitado de la cola, entonces la tarea ha sido
despertada por una interrupción. Es este caso, la llamada al
sistema falla con EINTR. Antes de regresar, current->semsleeping
es limpiado, y
sem_queue es
borrado de la cola. También
update_queue() es llamado
si alguna de las operaciones eran operaciones alterantes.
status
de la estructura
sem_queue NO está
establecido a 1, y el elemento
sem_queue ha sido quitado
de la cola, entonces las operaciones del semáforo ya han sido
ejecutadas por
update_queue().
La cola status
, la cual será 0 si se tiene éxito
o un código negativo de error en caso de fallo, se convertirá
en el valor de retorno de la llamada al sistema.
Las siguientes estructuras son usadas específicamente para el soporte de semáforos:
/* 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 */
};
/* 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 {
int semmap;
int semmni;
int semmns;
int semmnu;
int semmsl;
int semopm;
int semume;
int semusz;
int semvmx;
int semaem;
};
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;
};
/* 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 */
};
/* 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 */
};
/* 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 */
};
Las siguientes funciones son usadas específicamente para soportar los semáforos:
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:
sem_base
para el conjunto es inicializado
a la dirección inmediatamente siguiente siguiendo la porción
(
struct sem_array) de los
nuevos segmentos asignados. Esto corresponde a la localización
del primer semáforon en el conjunto.
sem_pending
es inicializada como vacía.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() 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.
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.
La operación IPC_RMID llama a freeary() para borrar el conjunto del semáforo.
La operación IPC_SET actualiza los elementos uid
, gid
,
mode
, y ctime
del conjunto de semáforos.
semctl_nolock() es llamada por sys_semctl() para realizar las operaciones IPC_INFO, SEM_INFO y SEM_STAT.
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 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() 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.
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.
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:
sem_ctime
para el conjunto de semáforos
es establecido.
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.
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.
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.
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().
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().
Después de validar el nuevo valor del semáforo, las siguientes funciones son realizadas:
sem_ctime
para el conjunto
del semáforo es actualizado.
count_semncnt() cuenta el número de tareas esperando por el valor del semáforo para que sea menor que cero.
count_semzcnt() cuenta el número de tareas esperando por el valor del semáforo para que sea cero.
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() 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() 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() 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() 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() 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:
sem_otime
del conjunto de semáforos es
actualizado.Cuando el procesamiento de la lista está completo, el valor current->semundo es limpiado.
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.
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:
La información de la cola global de mensajes es copiada al espacio de usuario.
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().
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.
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() 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:
msg
del tipo
struct msg_msg.
El tipo de mensaje y los campos del tamaño del mensaje de
msg
también son inicializados.msgflg
el
spinlock global de la cola de mensajes es abierto, los
recursos de memoria para el mensaje son liberados, y
EAGAIN es retornado.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.q_lspid
y q_stime
del descriptor
de la cola de mensajes y abre el spinlock de colas de mensajes
global.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:
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.msgtyp
.msgflg
indica que no se permiten
errores, abre el spinlock de cola de mensajes global
y retorna E2BIG.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:
msr
es asignada y es añadida en la cabeza de
la cola de espera.r_tsk
de msr
es
establecido a la tarea actual.r_msgtype
y r_mode
son inicializados con el tipo y modo del mensaje deseado
respectivamente.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.r_msg
es inicializado para indicar
que todavía no ha sido recibido el mensaje.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.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.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.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.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().Las estructuras de datos para las colas de mensajes están definidas en msg.c.
/* 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;
};
/* 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 */
};
/* segmento de mensaje para cada mensaje */
struct msg_msgseg {
struct msg_msgseg* next;
/* la siguiente parte del mensaje sigue inmediatamente */
};
/* un msg_sender para cada emisor durmiendo */
struct msg_sender {
struct list_head list;
struct task_struct* tsk;
};
/* 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 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 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 */
};
struct msq_setbuf {
unsigned long qbytes;
uid_t uid;
gid_t gid;
mode_t mode;
};
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:
q_stime
y q_rtime
del descriptor
de cola de mensajes son inicializados a 0. El campo
q_ctime
es establecido a CURRENT_TIME.q_qbytes
) es establecida a MSGMNB,
y el número de bytes actualmente usados por la cola
(q_cbytes
) es inicializada a 0.q_messages
),
la cola de receptores esperando (q_receivers
),
y la cola de emisores esperando (q_senders
)
son inicializadas como vacías.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().
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() 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() 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.
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() 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.
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.
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.
La función free_msg() libera la memoria para una estructura de datos de mensaje msg_msg, y los segmentos del mensaje.
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.
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() 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() 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.
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.
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.
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.
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.
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.
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.
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.
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() 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.
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.
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 {
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 /* 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 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 {
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;
};
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.
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() 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:
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() 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() 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.
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.
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.
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.
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() 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() 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() 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() 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() 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() 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() 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() libera el spinlock global para el tipo IPC indicado.
ipc_lockall() cierra el spinlock global para el mecanismo IPC dado (esto es: memoria compartida, semáforos, y mensajes).
ipc_unlockall() abre el spinlock global para el mecanismo IPC dado (esto es: memoria compartida, semáforos, y mensajes).
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() borra la bandera IPC_64 desde el comando si está presente y devuelve IPC_64 o IPC_OLD.
Todos los semáforos, mensajes, y mecanismos de memoria compartida hacen un uso de las siguientes estructuras comunes:
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;
};
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;
};
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;
};