Dentro del núcleo Linux 2.4 - Mecanismos IPC (III)
Tutorial creado por Tigran Aivazian. Extraido de: http://es.tldp.org/Manuales-LuCAS/DENTRO-NUCLEO-LINUX/dentro-nucleo-linux-html/
14 de Febrero de 2006
Linux
33 - Mecanismos IPC (III)
**Memoria Compartida**∞
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;
};
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:
- find_lock_page() es llamado para cerrar la página (estableciendo PG_locked) y para incrementar la cuenta de referencia de la página. Incrementando la cuenta de referencia se asegura que el segmento de memoria compartida permanece bloqueado en memoria durante esta operación.
- Si el estado deseado es cerrado, entonces PG_locked es limpiado, pero la cuenta de referencia permanece incrementada.
- Si el estado deseado es abierto, entonces la cuenta de referencia es decrementada dos veces durante la actual referencia, y una vez para la referencia existente que causó que la página permanezca bloqueada en memoria. Entonces PG_locked es limpiado.
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.
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;
};
Valora este capítulo:
Autor y licencia de 'Dentro del núcleo Linux 2.4 - Mecanismos IPC (III)'
|
Opiniona sobre 'Dentro del núcleo Linux 2.4 - Mecanismos IPC (III)' (4)
Tu nombre debe tener tres caracteres como mínimo.
Es necesario que te des de alta con una cuenta de correo válida.
Es necesario que te des de alta con una cuenta de correo válida.
El contenido del título de tu opinión debe tener tres caracteres como mínimo.
Es obligatorio que selecciones una valoración del recurso.
El contenido del comentario de tu opinión debe tener tres caracteres como mínimo.
Opina sobre este tutorial |
Wikis relacionados con 'Dentro del núcleo Linux 2.4 - Mecanismos IPC (III)'
Este documento pretende ser el punto de entrada de los hispanohablantes al mundo Linux, intentando...
Más »
Manual Compacto para nuevos usuarios.
El presente texto es una versión ampliada de la conferencia impartida en el ciclo organizado...
Más »
Este documento contiene una lista de las aplicaciones para Linux capaces de reproducir diversos formatos...
Más »
La comunicación dentro de las organizaciones se caracteriza por su naturaleza multifuncional, que responde a...
Más »
