Escuela de Informática de Sabadell Ramírez Jorge Durán Unidad de Arquitectura de Ordenadores y Sistemas Operativos Shared Memory & Semaphores Recopilado por: Remo Suppi Octubre 1997 Publicación exclusiva para fines docentes Bibliografia: Advanced Unix Programming-Rockhind, Solaris 2.5 User´s manuals o comando man Semáforos en Unix System V Estos apuntes son una breve introducción a la utilización de semáforos en sistema operativo Unix SV. Para mayor detalle recurra a la bibliografía recomendada y a los manuales de usuario/programador. Introducción: La utilización de mensajes para sincronizar procesos, si bien es mucho más eficiente que utilizar archivos, introducen una carga considerable en el sistema ya que el contenido del mensaje se 'mueve' entre los procesos. Una alternativa más eficiente sería incrementar/decrementar en forma controlada una variable. Unix SV posee un conjunto de llamadas específicas para trabajar con estas variables denominadas semáforos. Estas llamadas son más elaboradas que las utilizadas P & V (Wait & Signal Down & Up) utilizadas en la bibliografía de Sistemas Operativos (por ejemplo W. Stalling). Estas llamadas son complejas para describirlas completamente debido a sus múltiples prosibilidades ( "To be honest, I must admit that I don´t understand them completely" Rockhind). Aqui se describirá su utilización y la implementación de las primitivas P & V, pero para mayor referencia utilizar el Programmer User´s Manual - Solaris 2.5 (o comando man). Existen tres llamadas para trabajar con semáforos: #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key, nsems, flags) /*obtiene el sid (identificador del semáforo)*/ key_t key; /*Llave del semáforo*/ int sems; /*Cantidad de semáforos*/ int flags; /*Opciones*/ Retorna el sid (semaphore set identificator) del semáforo o -1 sobre un error int semop(sid, ops, nops) /*opera sobre un semáforo*/ int sid; /*ID del semáforo*/ struct sembuf (*ops)[]; /*Ptr al array de operaciones*/ size_t nops; /*Nº de operaciones*/ Retorna el valor del semáforo previo a la última operación o -1 sobre un error int semctl(sid, snum, cmd, arg) /*Control sobre un semáforo*/ int sid; /*ID del semáforo*/ int snum; /*Nº del semáforo*/ int cmd; /*Comando*/ union *arg; /*Argumentos*/ Retorna el valor dependiendo del comando o -1 sobre un error Semget obtiene un array de semáforos, y ellos son manjados todos a la vez -atómicamente- por un array de operaciones dado en Semop. Semget traslada una LLAVE (key) sobre un ID que representa un conjunto de semáforos. Si el bit de IPC_CREAT que existe en flags está activo, el set de semáforos es Escuela de Informática de Sabadell Ramírez Jorge Durán creado si no existe. Existen nsems semáforos en el conjunto y la numeración de los semáforos comineza en 0. NO existe ningún problema que nsems=1 por lo cual el conjunto de semáforos tendrá solo un elemento. Semop es utilizado para operar sobre el semáforo (adquire & release). Ops es una estructura con tres enteros: struct sembuf short sem_num; /*Nº del semáforo*/ short sem_op; /*Operación*/ short sem_flg; /*Opciones de operación*/ La operación puede ser positiva o negativa o cero. A diferencia de la primitiva P, la cual se espera hasta que el semáforo pueda ser decrementado en 1, esta permite esperar hasta que el semáforo pueda ser decrementado por una cantidad arbitraria. Esta característica incluye los valores -1 y 1 las cuales corresponderían a P y V. If op=-1 el proceso se bloqueará hasta que el semáforo pueda ser decrementado en 1 sin que el valor sea negativo. Si op=1 el semáforo es incrementado en 1. Para ambas operaciones, flags puede ser igual a SEM_UNDO, el cual permite que P y V sean balanceadas automáticamentecuando el proceso realiza un exit. Semctl puede ser utilizado para interrogar o cambiar el dueño de un semáforo, permisos, etc. También permite obtener cuantos procesos están esperando sobre un semáforo y el identificador del proceso (pid) del último que cambio su valor. P & V implementadas con las llamadas Semget & Semop Para implementar estas primitivas, se ha utilizado una función -semcreat- que traslada la LLAVE al SID (indentificador del conjunto de semáforos). En general, semcreat será llamada una vez al cominezo de cada proceso que utiliza el semáforo. #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semcreat(key) /*Traslada key en SID*/ int key; { int sid; if ((sid = semget ((key_t)key, 1, 0666|IPC_CREAT)) == -1) perror("SemCreat: "); return (sid); } static void semcall(sid, op) /*Opera sobre el semáforo*/ int key; int op; { struct sembuf sb; sb.sem_num = 0; sb.sem_op = op; sb.sem_flg = 0; if (semop (sid, &sb, 1) == -1) perror("SemCall: "); } void P(sid) /*Adquiere el semáforo*/ int sid; { semcall (sid, -1); } Escuela de Informática de Sabadell Ramírez Jorge Durán void V(sid) /*Libera el semáforo*/ int sid; { semcall (sid, 1); } Esta implementación de P & V es mucho más rápida en tiempo de ejecución que utilizando mensajes o FIFOS y por supuesto que utilizando archivos. Memoria compartida (Shared Memory) - Introducción La forma más rápida de que un proceso tenga la misma información que otro es no mover los datos entre procesos sino compartirlos. El que envía (Sender) y el que recibe (Receiver) comparten alguna zona de memoria principal, y cuando el dato es colocado aquí por el S, el dato es instantaneamente disponible para ser utilizado por el R. Un semáforo o mensaje deberá ser utilizado para preveer que el R no lea los datos antes que los mismos estén disponibles y prevenir que el S escriba nuevos datos antes que los anteriores hayan sido leídos por el R. Unix SV soporta shared memory entre un conjunto (cualquiera) de procesos. La memoria compartida es llamada segmento. Cada proceso puede tener diferentes zona de memoria compartida entre diferentes subconjuntos de procesos y cada proceso podrá acceder a las zonas de memoria que comparte. El segmento de memoria es creado al inicio fuera del espacio de direcciones de cada proceso, por ello cada proceso que desee acceder a esta zona compartida ejecutará una llamada al sistema para "mapear" esta zona dentro de su espacio de direcciones. Una vez realizada esta operación, acceder a la zona compartida es equivalente a acceder a una variable local del proceso. Memoria compartida (Shared Memory) en Unix SV Las llamadas de al Shared Memory (SHM) tienen algún parecido a las utilizadas en semáforos. #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget(key, nbytes, flags) /*obtiene el segment-ID de la SHM*/ key_t key; /*Llave*/ int nbytes; /*tamaño del segmento*/ int flags; /*Opciones*/ Retorna el segment-ID o -1 sobre un error int *shmat(segid, addr, flags) /*"Mapea" el segment en la zona de memoria del usuario*/ int segid; /*ID del segment*/ char *addr;/*Posición deseada*/ int flags; /*Opciones*/ Retorna la dirección del segmento o -1 sobre un error int shmdt(addr) /*"Libera" el segment de la zona de memoria del usuario*/ char *addr;/*Dirección del segmento*/ Retorna 0 si bien o -1 sobre un error int shmctl(segid, cmd, sbuf) /*Control sobre un segment*/ int sid; /*ID del segment*/ int cmd; /*Comando*/ struct shmid_ds *sbuf; /*Puntero al status buffer*/ Retorna 0 si bien o -1 sobre un error Shmget traslada la LLAVE en un identificador de segmento. Si el flags es IPC_CREATE, el segmento es creado. Los 9 bits menos significativos de flags son los permisos; permiso de lectura significa sólo lectura, mientras que escritura significa lectura y escritura. Escuela de Informática de Sabadell Ramírez Jorge Durán Shmat "mapea" (attach) un segmento de SHM creado con shmget dentro del espacio de direcciones del usuario. El usuario de este segmento puede solicitar un lugar en particular indicando la dirección por medio de addr. Si este valor es igual a 0, el kernel del operativo seleccionará un dirección (recomendable). Shmat retornará un puntero al número de bytes solicitados. A partir de aquí se puede leer-escribir datos utilizando los operadores normales de C. Otro proceso que necesite compartir estos datos, "mapeará" este segmento (identificado por key) en su espacio de direcciones. A partir de este momento los procesos puede comaprtir datos libremente. Shmdt es ultizado para liberar un segmento compartido cuando se lo no necesite más. El segmento de memoria no desaparece, sino que desde el proceso que realiza el shmdt no es visible, pudiendo mapearlo nuevamente. El segmento puede ser destruido utilizando Shmctl con el comando IPC_RMID. Debe tenerse cuidado con esta llamada ya que es un error serio destruir un segmento que está "mapeado" en el espacio de direcciones de otro proceso. Detalles sobre las Llamadas: Los flags que utiliza shmat() están dados por una unión de diferentes elementos (generalmente se hace con un and o un or ). En el manual cada uno de los operandos del or se denominan { token}. Los token que pueden ser utilizados como permisos para la zona de memoria asociada pueden ser interpretados como los siguientes: 00400 READ by user 00200 WRITE by user 00040 READ by group 00020 WRITE by group 00004 READ by others 00002 WRITE by others Cualquier combinación válida de estos valores es posible por ejemplo 00666. Consultar Shared Memory Operation Permissions de intro en la sección 2 del manual para más información (man -s 2 intro). El SHM segment es "mapeado" al segmentos de datos de un proceso cuando el proceso lo solicita a través de shmat() (Cuando (flags&SHM_SHARE_MMU) es verdadero el segmento en cuestión es shared y además puede ser compartido por los restantes procesos que los soliciten) y la dirección especificada sigue algunos de los criterios siguientes: Si shmadd es igual a ( void *) 0, el segmento es "mapeado" a la primera dirección disponible seleecionada por el SOp. Si shmadd es igual a (void *) 0 y (flags&SHM_SHARE_MMU) es verdadero, el segmento es "mapeado a la primera dirección "aliñada" (first available aligned address). Ver las NOTAS en el manual para más detalles. Si shmadd NO es igual a ( void *) 0 y (flags&SHM_RND) es verdadero el segmento es "mapeado en la dirección dada por ( shmaddr - ( shmaddr modulus SHMLBA)). Si shmadd NO es igual a ( void *) 0 y (flags&SHM_RND) es falso, el segmento es "mapeado en la dirección dada por shmaddr. El segmento es asociado para lectura si (flags&SHM_RONLY) es verdadero, caso contario es asociado como lectura escritura. Cuando (flags&SHM_SHARE_MMU) es verdadero, los permisos dados por semget() determinan si el segmento asociado es de lectura o lectura/escritura. La llamada shmat retorna la dirección del segmento asociado cuando no existen errores o -1 cuando existen, la variable errno indica el tipo del error (utilizar perror() para visualizar el error) que pueden ser de los siguiente tipos (ver el manual para su significado): EACCES EINVAL EMFILE ENOMEM Escuela de Informática de Sabadell Ramírez Jorge Durán Los flags que utiliza shmctl() provee una variedad de control sobre las operaciones efectuadas sobre la SHM y es especificado por cmd. Los valores para los permisos de lectura/escritura son equivalente a los antes mencionados en shmat(). Existen un conjunto de operaciones específicas ue deben ser indicadas por los siguientes tokens: IPC_STAT: Ubica el valor actual de cada mienbro de la estructura de datos asociada con segid dentro de la estructura apuntada por sbuf. IPC_SET: Inicializa el valor de los mienbros de la estructura de datos asociada a segid al acoorespondiente valor en la estructura apuntada por sbuf: shmperm.uid, shmperm.gid, shmperm.mode /*solo accede a los bits de permisos*/. Este comando sólo puede ser ejecutado por el proceso que tiene el "effective user ID" igual al del super-usuario o al valor del shm_perm.cuid, o shm_perm.uid en la estructura de datos asociada con segid. IPC_RMID: Borra el SHM segment especidicado por segid del sistema y destruye las estructura de datos asociada a él. Este comando sólo puede ser ejecutado por el proceso que tiene el "effective user ID" igual al del super-usuario o al valor del shm_perm.cuid, o shm_perm.uid en la estructura de datos asociada con segid. SHM_LOCK: Bloquea la SHM segment especificada por segid. Este comando sólo puede ser ejecutado por el proceso que tiene el "effective user ID" igual al del super-usuario. SHM_UNLOCK: Desbloquea el SHM segmend especificada por segid. Este comando sólo puede ser ejecutado por el proceso que tiene el "effective user ID" igual al del super-usuario. Los errores que retorna esta función pueden ser del tipo: EACCES EFAULT EINVAL ENOMEM EOVERFLOW EPERM Para shmget() un identificador de segmento y al estructura de datos asociada es creada para los bytes solicitados si uno de los siguientes valores es verdadero: key es igual a IPC_PRIVATE. key no tiene asociado un SHM y (flags&IPC_CREAT) es verdadero. La estructura de datos asociada con el nuevo segmento (cuando es creado) es inicializado con: shm_perm.cuid, shm_perm.uid, shm_perm.cgid y shm_permgid con el ID del usuario y grupo efectivo respepectivamnete del proceso que lo solicita. Los bits de permiso de shm_perm.mode con los bits indicados en flags (menos significativos). shm_segsz con el valor del tamaño del segmento. shm_lpid, shm_nattch, shm_atime y shm_dtime igual a 0. shm_ctime igual al valor del time actual. Como retorno da un entero no negativo que es el identificador del segmento. Caso contrario retorna un -1 y errno es inicializado con el tipo de errror. Los errores que retorna esta función pueden ser del tipo: EACCES EEXIST EINVAL Escuela de Informática de Sabadell Ramírez Jorge Durán ENOENT ENOMEM ENOSPC semctl() provee una variedad de operaciones de control indicadas por cmd. Los permisos son idicados por tokens análogos a los utilizados en shmctl(). Las operaciones sobre un semáforo identificado por sid, snum son las siguientes: GETVAL: retorna el valor del semáforo semval. SETVAL: inicializa el valor de semval al valor indicado en arg.val. (Cuando este comando es realizado, el valor semadj correspondiente a este semáforo en todos los procesos es borrado). Para ver el significado de estos valores consultar man -s 2 intro en el apartado de semáforos. GETPID: retorna el valor de sempid. GETCNT: retorna el valor de semncnt. GETZCNT: retorna el valor de semzcnt. GETALL: Lee los valores de los semáforos (semvals) en el array apuntado por arg.array SETALL: Inicializa los valores de los semáforos con el array apuntado por arg.array. Esta operación borra la variable semadj correspondiente al semáforo en todo los procesos. IPC_STAT: Inicializa el valor de cada mienbro de la estructura de datos asociada com sid en la estructura apuntada por arg.buf. El contenido de esta estructura está definido en man -s 2 intro. IPC_SET: Inicializa el valor de los datos del semáforo (asociado con sid) a partir de los valores pasados en la estructura arg.buf (sem_perm.uid, sem_perm.gid, sem_perm.mode) (solo puede ser ejecutado por el superusuario, o por el proceso que figura en sem_perm.cuid, sem_perm.uid asociado con sid -quien lo creó-). IPC_RMID: Borra el semáforo especificado por sid del sistema y destruye las estructuras de datos asociadas. Solo puede ser ejecutado por el superusuario, o por el proceso que figura en sem_perm.cuid, sem_perm.uid asociado con sid -quien lo creó- . Valores de retorno: Los siguientes cmd retornan como valor: GETVAL: el valor del semáforo. GETPID: el valor de sempid (int). GETNCNT: el valor de semncnt. GETZCNT: el valor de semzcnt. Todos los otros: 0 cuando todo está bien, -1 cuando existe error. Los errores que retorna esta función pueden ser del tipo: EACCES EFAULT EINVAL EPERM EOVERFLOW ERANGE semget() retorna el id del semáforo asociado con la llave. El semáforo será creado si: key es igual IPC_PRIVATE key aún no tiene un semáforo asociado con esta y (flags&IPC_CREAT) es verdadero. Escuela de Informática de Sabadell Ramírez Jorge Durán Cuando se crea un semáforo, sem_perm.cuid, sem_perm.uid, sem_perm.cgid, sem_perm.gid son inicializados con el ID del usuario efectivo y del grupo que está ejecutando el proceso. sem_perm.mode es inicializado con los bits menos significativos de flags, sem_nsems es inicializado a nsems, sem_otime igual a 0 y sem_ctime igual a la hora actual. La función retorna 0 si todo bien, -1 si existe errores (el valor del error estará en errno). Los errores (errno) que retorna esta función pueden ser del tipo: EACCES EEXIST EINVAL ENOENT ENOSPC semop() es utilizada para realizar operaciones (en forma atómica -indivisible-) sobre todos los semáforos. La llamada int semop(int semid, struct sembuf *sops, size_t nops) utiliza sops que es un puntero a un array de estructuras que contendrán la operaciones para los semáforos en cuestion (recordar que el conjunto de semáforo está identificado por semid). Los miembros de cada una de estas estructuras está definido por: short sem_num /*número del semáforo*/ short sem_op /*operación a realizar*/ short sem_flg /*flags de operación*/ Se debe tener en cuenta que cada semáforo esta especificado por la pareja semid, sem_num. Por lo cual con la semop le podemos indicar en sops el array de punteros a esta estructura para modificar a la vez todo los semáforos de semid. nops es el número de estructuras que existe en el array sops. sem_op puede ser un valor negativo, positivo o 0, y la función realizará diferentes acciones según sea: Sem_op es un entero negativo Si el valor del semáforo (semval) es mayor o igual al valor absoluto de sem_op, el valor abs de sem_op es restado del valor de sem_val. También si (sem_flg&SEM_UNDO) es verdadero el valor abs de sem_op es sumado al valor semadj de los procesos que lo utilizan (ver exit(2)). Si sem val es menor que el valor abs de sem_op y (sem_flg&IPC_NOWAIT) es verdadero, semop() retorna inmediatamente. Si sem val es menor que el valor abs de sem_op y (sem_flg&IPC_NOWAIT) es falso, semop() incrementa el semncnt (proceso bloqueados en el semáforo) asociado con el semáforo y suspende la ejecución del proceso hasta que una de las acciones siguientes ocurra: Semval cambia a un valor mayor o igual que el valor abs de sem_op. Cuando esto ocurre semncnt es decrementado y el valor se sem_op es restado de semval (idem 1er caso). El proceso está esperando por un semáforo -semid- y este es borrado del sistema con semctl(). La llamada semop() retornará con un error y erno será igual a EIDRM. El proceso que espera recibe una señal para terminar (abortar la ejecución), por lo cual el valor de semncnt es decrementado para mantener la coherencia del semáforo. Sem_op es un entero positivo El valor del sem_op es sumado al valor semval y si (sem_flg&SEM_UNDO) es verdadero el valor de semopj es decrementado a semadj de lo procesos que lo utilizan. Escuela de Informática de Sabadell Ramírez Jorge Durán Sem_op es cero Si el valor del semáforo semval es 0 semop() retorna inmediatamente. Si semval no es igual a cero y (sem_flg&IPC_NOWAIT) es verdadero semop() retorna inmediatamente. Si semval no es igual a cero y (sem_flg&IPC_NOWAIT) es falso, semop incrementa el valor de semzcnt asociado con el semáforo y bloque el proceso hasta que una de las siguiente situaciones ocurre: Semval cambia a cero, además el valor de semzcnt es decrementado. El semáforo es borrado del sistema por lo cual la operación retornará con un error EIDRM. El proceso que espera recibe una señal para terminar (abortar la ejecución), por lo cual el valor de semzcnt es decrementado para mantener la coherencia del semáforo. Los errores que retorna esta función pueden ser del tipo: EACCES EAGAIN EFAULT EFBIG EIDRM EINTR EINVAL ENOSPC ERANGE Ejemplo de comunicación entre procesos utilizando Shared Memory y semáforos Consideremos cinco procesos ABCDE que necesitan compartir datos. A crea los procesos-estructuras de datos y pone los datos en SHM, BCDE leen de SHM y realizan algua modificación de los datos, DE escriben en la SHM los datos modificados, y finalmente BC leen de la SHM dichos datos. Para ello utilizaremos dos semáforos: el primero de ellos permitirá a DE escribir en la SHM cuando todos (4 procesos) hayan teminado de leer. El otro semáforo, permitirá que BC posteriormente lean los datos escritos por DE: Proceso A -sólo se muestra el código relevante#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #define LLAVE ((key_t) 13) union semun { /*estructura utilizada por semctl para inicializar los semáforos*/ int val; /*valor inicial del semáforo*/ struct semid_ds *buf; ushort *array; } arg; DefinirEstructuras(int *shmid, int **shmptr, int *semid) { *shmid = shmget(LLAVE, 100*sizeof(int), IPC_CREAT | 0666); /*SHM*/ if (*shmid < 0) { perror("SHMget"); exit(-1)} *shmptr = (int *) shmat(*shmid,0,0); /*Mapea el segmento en cuestión*/ if ((*semid = semget (LLAVE, 2, IPC_CREAT | 0666)) Escuela de Informática de Sabadell Ramírez == -1){ perror("Semget"); exit(-2) } /*Creo dos semáforos*/ arg.val = 4; semctl(*semid, 0, SETVAL, &arg); Inicializo el semáforo 0 a 4 arg.val = 2; semctl(*semid, 1, SETVAL, &arg); Inicializo el semáforo 1 a 2 ... /* aqui el proceso introduce los datos en la SHM */ } CrearProcesos() { char vid[2] /*... declaración de variables varias*/ for (i=1; i<5; i++) /*Creo Procesos*/ { if (fork() == 0 ) { printf ("Proceso %d creado", i); sprintf(vid, "%d", i); execlp("Codigo_de_su_hijo", "Codigo _de_su_hijo", vid, NULL); Ejecuto el código del hijo con arg pasado en vid*/ } for (i=1; i<5; i++) wait(0); /* Me espero que los hijos terminen*/ } Liberar(int shmid, int *shmptr, int semid) { shmdt ((char *)shmptr); shmctl (shmid, IPC_RMIID, 0); semctl (semid, 0, IPC_RMID, 0); semctl (semid, 0, IPC_RMID, 0); } main(int argc, char *argv[]); { int shmid, semid; Identificador de la SHM y conjunto de semáforos*/ int *shmptr; Puntero a la SHM DefinirEstructuras(&shmid, &shmptr, &semid); CrearProcesos(); ImprimirResultado(shmptr); Liberar(shmid, shmptr, semid); } Proceso Xi (BCDE) -sólo se muestra el código relevante#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #define LLAVE ((key_t) 13) struct sembuf ops; main(int arg_n, char *arg[]); { int *ptr; /*puntero a la SHM */ int *mem_local; /*Memoria Local*/ int ident, semid; Jorge Durán Escuela de Informática de Sabadell Ramírez mem_local = (int *)malloc (100*sizeof(int)); /*Obtengo la memoria local*/ ident = shmget (LLAVE, 100*sizeof(int), 0666); /*Obtengo la SHM*/ ptr = (int) shmat (ident, 0, 0); /*Mapeo la SHM en el espacio de usuario - ptr -*/ semid = semget (LLAVE, 2, 0666); /*Obtengo el identificador del semáforo*/ switch (arg[1][0]){ case ´1´: /* los procesos BC (1,2) hacen los mismo*/ case ´2´: Load();/* leo los datos desde ptr y realizo algún trabajo - recordar que este será el B(C) y debe leer, esperar, y leer*/ ops.sem_num = 0; ops.sem_op = -1; /*decremento el semáforo 0 en 1*/ ops.sem_flg = 0; semop (semid, &ops, 1); ops.sem_num = 1; ops.sem_op = 0; /*Con este valor semop esperará hasta que el semáforo en cuestión sea 0 -aquí el sem Nº 1-*/ ops.sem_flg = 0; semop (semid, &ops, 1); Load();/*Ahora el proceso B(C) puede leer por segunda vez desde ptr*/ break; case ´3´: /* los procesos DE (3,4) hacen los mismo*/ case ´4´: Load();/* leo los datos desde ptr y realizo algún trabajo - recordar que este será el D(E) y debe leer, esperar y escribir */ ops.sem_num = 0; ops.sem_op = -1; /*decremento el semáforo 0 en 1*/ ops.sem_flg = 0; semop (semid, &ops, 1); ops.sem_num = 0; ops.sem_op = 0; /*Con este valor semop esperará hasta que el semáforo en cuestión sea 0 -aquí el sem Nº 0-*/ ops.sem_flg = 0; semop (semid, &ops, 1); Store();/*Ahora el proceso D(E) puede guardar la información en ptr porque cuando sem[0]=0 indicará que todos han terminado de leer*/ ops.sem_num = 1; ops.sem_op = -1; /*Decremento el sem[1] que es el que esperará BC para saber que DE han terminado de escribir*/ ops.sem_flg = 0; semop (semid, &ops, 1); break; } shmdt ((char *) ptr); } Jorge Durán