Sistemas Operativos I Práctica 2 Continuación de la implementación de msh: Uso de la memoria compartida y de semáforos Diana Rosario Pérez Marín Marzo 2007 EPS-UAM 1 Conceptos teóricos • Para comunicar a varios procesos en ejecución, el sistema operativo Linux nos ofrece varias alternativas: usar el valor devuelto por exit, ficheros, pipes o memoria compartida. Nosotros nos vamos a centrar en esta última forma de comunicación, que consiste en crear una zona de memoria común a la que todos los procesos se enganchan y de esta forma, lo que un proceso escribe en ella, lo pueden leer todos los demás. • Por ejemplo : Proceso 1 Escribe : valor = 9 Memoria compartida Valor = 9 Proceso 2 Lee : valor = 9 2 Varias instancias de msh compartiendo información común • En esta práctica continuamos con el desarrollo de nuestro intérprete de comandos. De forma que todos los requisitos exigidos en la práctica anterior se mantienen en esta práctica. • Con la novedad de que ahora ya no nos vamos a centrar únicamente en una ejecución de msh, sino que vamos a ejecutar varios mshs de forma concurrente. Todos ellos serán procesos padre que vayan lanzando hijos para que vayan ejecutando los comandos que se les pida de forma interactiva o por fichero. • Habrá un único fichero .msprofile al que todos tendrán acceso y la información de los procesos y las variables la guardarán en un espacio de memoria común (la memoria compartida) de forma que todas las instancias de msh, tengan el mismo valor para todas las variables de entorno. Se definen nuevas variables: – MAX_NUM_MSHS: número máximo de mshs que se pueden ejecutar en paralelo. – ID_MEMORIA: clave que se usará para identificar la memoria. 3 Modificaciones a realizar en los comandos propios de msh (I) • exit: Sigue siendo el comando que se encarga de cerrar un intérprete de comandos (aunque pueden quedar otros ejecutándose). Debe liberar la memoria prestada, esperar a todos los hijos, desengancharse de la memoria compartida y si es ya la última instancia de msh que queda, liberar la memoria compartida. • msps: Si la memoria compartida de procesos tiene permisos de lectura, lista todos los procesos que estén en ejecución en todas las mshs activas. Mostrando una línea por proceso que sigue el formato: pid ppid status t_ejec comando idMsh • eVar, rVar y Entorno: Su sintaxis no varía respecto a la práctica anterior. Tan solo hay que tener cuidado de que ahora sólo se puede ejecutar si se tiene permiso de lectura de la memoria compartida de variables. 4 Modificaciones a realizar en los comandos propios de msh (II) • aVar: Si la msh desde donde se ejecuta tiene permisos de escritura en memoria compartida de variables entonces funciona igual que en la práctica anterior, en caso contrario debe dar error. • bVar: La msh desde donde se ejecuta debe tener permisos de lectura y escritura. En caso contrario, debe dar error. • eId: A cada uno de los msh que tenemos en ejecución le vamos a asignar un identificador que puede ser su número de pid o bien el valor de un contador que empieza en uno y se va incrementando cada vez que se crea un nuevo msh. El comando eId nos debe mostrar por pantalla el identificador del msh desde donde se ejecuta • eNum: Muestra por pantalla el número de mshs que se están ejecutando concurrentemente. 5 Modificaciones a realizar en los comandos propios de msh (III) • dPermVar: Concede permisos para acceder a la memoria compartida de variables (dPermVar r, concede el permiso de lectura; dPermVar w: concede el permiso de escritura; y, dPermVar: concede ambos permisos). • dPermProc: Igual que dPermVar pero pero para la memoria de procesos. • ePermVar: Muestra qué permisos tiene la memoria compartida de variables (ePermVar r, muestra si tiene permiso de lectura; ePermVar w: muestra si tiene el permiso de escritura; y, ePermVar: ambos). • ePermProc: Igual que ePermVar pero para la memoria de procesos • bPermVar: deniega permisos para acceder a la memoria compartida de variables (bPermVar r, deniega el permiso de lectura; bPermVar w: deniega el permiso de escritura; y, bPermVar: ambos). • bPermProc: Igual que bPermVar pero para la memoria compartida de procesos. 6 Pseudocódigo de msh (I) • Comprobar argumentos para saber si entra en modo fichero o en modo interactivo o bien si algún error en los argumentos salir. • Leer el fichero de configuración .msprofile y entonces: – Si es el primer msh que se ejecuta, crear la memoria compartida reservando espacio para una estructura con información sobre : • • • • • El número real de procesos que se han creado. El número máximo de procesos que se pueden guardar. El número real de variables de entorno que se están gestionando. El número máximo de variables que se pueden guardar. Un array con los identificadores de las instancias de msh de tamaño el máximo número de mshs que se puedan ejecutar concurremente. • Un array de igual tamaño que el anterior que indique el modo de cada msh: FICHERO (F) o INTERACTIVO (I). • Un array de tamaño el número de mshs que guarde para cada msh un contador del número de hijos, que se incremente en cada fork y se decremente en cada wait/waitpid. De forma que cuando su valor llegue a cero esto indique que ya no queda ningún proceso en estado zombi. 7 Pseudocódigo de msh (II) • • • • • Un array de estructuras variables de tamaño el número máximo de variables. Un array de estructuras procesos de tamaño el número máximo de procesos. Un array de permisos de lectura de tamaño el máximo número de mshs. Un array de permisos de escritura de tamaño el máximo número de mshs. Un contador del número real de mshs en ejecución (opcional). – Engancharse a la memoria compartida. Tanto si es la primera instancia de msh como las siguientes, todos los procesos que quieran tener acceso a la memoria compartida, necesitan obtener un puntero a ella. – Si es la primera instancia de msh, inicializar los campos de la estructura de la memoria con valores por defecto e ir rellenando los campos de las variables con la información leída del fichero .msprofile. Si no es la primera, actualizar únicamente los campos adecuados (como colocar su valor en los arrays e incrementar los contadores pertinentes). Tener en cuenta que no tener permiso de escritura no debe impedir la escritura de las variables leídas de .msprofile. – Ejecutar los posibles comandos de inicio que hubiera en el fichero .msprofile. 8 Pseudocódigo de msh ( III ) – Mientras que siga habiendo comandos a ejecutar: • Analizar la línea de comandos. La sintaxis es la misma que en la práctica anterior. No es necesario guardar los comandos en memoria compartida, pero sí los procesos. • Identificar el comando. Si el comando es alguno de los comentados en las diapositivas anteriores entonces es el propio padre quien lo ejecuta, en caso contrario crear un hijo para que lo ejecute llamando a execvp (¡el hijo debe desvincularse de la memoria compartida antes de llamar a execvp!). • Mientras el hijo está ejecutando el comando, el padre guarda la información sobre el proceso en memoria compartida. • Si es el comando no debe ser ejecutado en background, entonces hacer espera bloqueante con waitpid hasta que termine el proceso. – Esperar a que todos los hijos terminen. Incluídos los que estén en background y comprobar que el contador de los hijos llega a 0 para que no se queden procesos zombi. – Liberar todos los recursos prestados. 9 Vista global • Ver en el enunciado ejemplo de .msprofile y ficheroScript. consola1 ./msh Creando mc..OK Lect. .msprofile..OK archivo1 msh% eNum Hay 1 msh. en ejec. msh% exit consola2 Consola3 ./msh Enganche a mc..OK Lect. .msprofile..OK archivo1 msh% dPermVar w Permiso concedido. msh% exit ./msh ficheroScript Enganche a mc..OK Lect. .msprofile..OK archivo1 msh% exit 10 Qué es un semáforo ( I ) • Además, en esta práctica nos vamos asegurar que no haya ningún problema de concurrencia en la ejecución de las mshs. • Para ello vamos a usar semáforos. • Un semáforo no es más que un número entero positivo y es así como hay que imaginarlo, pero siguiendo la metáfora de un semáforo que estará en rojo cuando el valor del entero sea 0 y estará en verde cuando tenga un valor positivo. • Cuando un proceso toma el semáforo, lo primero que se hace es comprobar el valor del semáforo. Si es 0, entonces se le deja dormido, y sólo cuando otro proceso lo libera e incrementa su valor, se le deja pasar. Lo primero que hace es decrementar el valor para que cuando llegue a 0 se le cierre el paso a todos los demás procesos que vengan por detrás. Por lo tanto para proteger una sección crítica con semáforos se hará : Down(semaforo) Sección crítica Up(semaforo) 11 Qué es un semáforo ( II ) • De esta forma se asegura : – La exclusión mutua, esto es, que sólo va a haber un proceso en la sección crítica. – Un proceso que no está en su sección crítica no puede bloquear a otros procesos. – El proceso que está en la sección crítica no bloqueará para siempre al resto, ya que cuando termine de escribir en la memoria compartida entonces liberará el semáforo y el primer proceso de la cola de los que estaban dormidos a la espera de que el semáforo despertara será el que haya ganado el acceso a la sección crítica. Así cuando termine de manipular la memoria compartida liberará el semáforo para que pase al siguiente. Hasta que finalmente pasen todos por esta zona protegida del código. 12 Tipos de semáforo • Hay dos tipos de semáforo : – Binarios : Sólo pueden tomar 2 valores, 1 ó 0. De forma que cuando están a 1 permiten acceder a la zona crítica y cuando están a 0 ya no permiten acceder a ella. Son muy útiles como semáforos mutex para controlar escritura de variables en memoria compartida que sólo permite que 1 proceso esté en la sección crítica. – N-arios : Se inicializan al número de procesos que se permite que estén en la sección crítica, de forma que cada uno de ellos lo va decrementando al hacer el down y luego cuando llega a 0 ya no permite que pasen ninguno más, hasta que se va liberando. Son muy útiles para operaciones de consulta de la memoria compartida en las que se permite que varios procesos lean de ella siempre que no haya ningún otro proceso escribiendo lo que se controlaría con un semáforo binario. 13 Operaciones con semáforos • Down, p, wait o toma_semaforo : Consiste en la petición del semáforo por parte de un proceso que quiere entrar en la sección crítica. Lo que se hace es comprobar si el valor del semáforo es 0 para en ese caso quedarse dormido porque el semáforo está en rojo y habrá otro proceso en la sección crítica. Mientras que si está en un valor positivo entonces el semáforo está en verde y de forma atómica (esto es el planificador no puede dar ahora la ejecución a otro proceso) decrementa el valor del semáforo. • Up, v, signal o libera_semaforo : Consiste en la liberación del semáforo por parte del proceso que ya ha terminado de trabajar en la sección crítica y por lo tanto de forma atómica también incrementar el valor del semáforo. Por lo que si fuera binario estaría despertando a otro proceso que estuviera esperando en down (al pasar el valor de 0 a 1), si fuera N-ario simplemente incrementa una unidad y si estaba en 0 también significará que lo pone en verde y permitiendo el acceso a la sección crítica al otro proceso. 14 Llamadas al sistema operativo para trabajar con memoria compartida • Veamos ahora de qué funciones disponemos : – – – – shmget : Crear la zona de memoria compartida shmat : Engancharse a la zona de memoria compartida shmdt : Desengancharse de la zona de memoria compartida shmctl : Operaciones de control e información sobre la memoria compartida • Todas ellas por razones de portabilidad a otros sistemas operativos se deben encapsular en un fichero aparte ( por ej. memoria.c ) con su correspondiente archivo de cabecera ( por ej. memoria.h ) que no deben contener información referente a esta práctica. • En las siguientes transparencias estudiaremos sus prototipos y las librerías que hay que incluir para poderlas usar. 15 shmget • Hay que incluir las librerías : – #include <sys/ipc.h> – #include <sys/shm.h> • Sirve para crear una zona de memoria compartida, y su prototipo es : – int shmget ( key_t key, int size, int shmflg ) ; – El valor de retorno es el identificador de la zona de memoria compartida si todo va bien, -1 si error y errno coge el número de error encontrado. Importante distinguir el valor EEXIST de errno puesto que no se debe considerar error como tal, ya que lo que indica es que la memoria compartida ya existía y no se está creando nueva. – Los argumentos son : • key_t key : Clave para crear la memoria compartida ( MEMID ), la misma para todos los procesos que quieren acceder a esta zona común. • int size : Tamaño a reservar ( sizeof de la estructura ). • int shmflg : Opciones de configuración. Se hace un OR entre ellas. Ejemplos : – IPC_CREAT sirve para crear un nuevo segmento – IPC_EXCL para asegurarse que no existía anteriormente – Permisos : SHM_R para leer y SHM_W para escribir (para todas las mshs*) . * los permisos los gestionamos nosotros con los arrays para la lectura y la escritura 16 shmat • Hay que incluir las librerías : – #include <sys/ipc.h> – #include <sys/shm.h> – #include <sys/types.h> • Sirve para engancharse a una zona de memoria compartida. Prototipo : – char * shmat ( int shmid, char * shmaddr, int shmflg ) ; – El valor de retorno es el puntero a la zona de memoria compartida si todo va bien, NULL si error y errno coge el número de error encontrado. – Los argumentos son : • int shmid : Identificador devuelto por shmget. • char * shmaddr : Como realloc se le podría indicar un puntero a una zona de memoria ya existente. Por defecto, este argumento se pasa a NULL. • int shmflg : Opciones de configuración. Se hace un OR entre ellas. Ejemplos : – SHM_R permiso para leer. condicionado a shmget – SHM_W permiso para escribir. 17 shmdt • Hay que incluir las librerías : – #include <sys/ipc.h> – #include <sys/shm.h> – #include <sys/types.h> • Sirve para desengancharse de una zona de memoria compartida. Prototipo : – int shmdt ( char * shmaddr ) ; – El valor de retorno es 0 si todo va bien, -1 si error y errno coge el número de error encontrado. – El argumento es char * shmaddr : Puntero a la zona de memoria compartida de la que nos queremos desenganchar. Se suele pasar con un casting a ( char * ) para compatibilizar argumentos. 18 shmctl • Hay que incluir las librerías : – #include <sys/ipc.h> – #include <sys/shm.h> • Sirve entre otras funciones de control sobre la memoria compartida, para eliminarla definitivamente ( siempre y cuando todos los procesos se hayan desenganchado de ella). Su prototipo es : – int shmctl ( int shmid, int comando, struct shmid_ds * buf ) ; – El valor de retorno es 0 si todo va bien, -1 si error y errno coge el número de error. – Los argumentos son : • int shmid : Identificador devuelto por shmget. • int comando : IPC_RMID para eliminarla. • struct shmid_ds * buf : Estructura con información de control. Por defecto se pasa a NULL cuando no se quiere trabajar con ella. 19 Ejemplo básico de creación de memoria compartida ( I ) #include #include #include #include #include <stdio.h> <sys/ipc.h> <sys/shm.h> <sys/types.h> <errno.h> int main () { int shm ; /* identificador de la zona de memoriacompartida */ int * dir ; /* direccion de enlace de la memoria con los datos */ int * numero ; /* numero que guardamos en la memoria compartida */ printf(“Estoy creando la memoria compartida\n") ; 20 Ejemplo ( II ) /* creo la zona de memoria compartida */ shm = shmget(3000, sizeof(int), IPC_CREAT|IPC_EXCL|SHM_R|SHM_W) ; if ( shm == -1 && errno == EEXIST) shm = shmget(3000, sizeof(int), SHM_R|SHM_W) ; printf ( “Cree la zona de memoria %d\n", shm) ; /* lo enlazo a un proceso */ dir = (int *) shmat ( shm, NULL, 0) ; if ( dir == (int *)-1) { perror("error de enlace\n") ; exit(-1) ; } /* utilizo la informacion */ numero = (int *) dir ; *numero = 0 ; printf("%d", *numero) ; 21 Ejemplo ( III ) /* libero la memoria */ if ( shmdt(dir) == -1) { perror("error de liberacion\n") ; exit(-1) ; } else { if (shmctl(shm, IPC_RMID, 0) == -1) { perror("error de borrado fisico\n") ; exit(-1) ; } } exit(0) ; } 22 Funciones de semáforos • Veamos ahora cómo se implementan los semáforos en Linux. Para usarlos lo primero que hay que hacer es incluir las librerías <sys/types.h>, <sys/ipc.h> y <sys/sem.h>, las funciones que vamos a usar son semget, semop y semctl. Que como en la práctica anterior no usaremos directamente para que el código sea portable a otros sistemas operativos sino que las encapsularemos en otras funciones en un archivo semaforo.c y su correspondiente archivo de cabecera semaforo.h. En semaforo.h además de incluir las librerías y los prototipos de las funciones que encapsularan las llamadas al sistema operativo, tenemos que escribir: #if defined ( __GNU_LIBRARY__) && !defined ( _SEM_SEMUN_UNDEFINED ) #else union semun { int valor ; /* valor para SETVAL */ struct semid_ds * buf ; /* buffer para IPC_STAT, IPC_SET */ unsigned short int * array ; /* array para GETALL, SETALL */ struct seminfo * __buf ; /* buffer para IPC_INFO */ }; #endif 23 semget ( I ) • Sirve para crear el array de semáforos. Siempre se crea un array de semáforos aunque sólo queramos usar 1, se creará un array de un solo elemento y para referirte a él será el semáforo número 0. • Igual que con shmget, se debe distinguir entre el primer msh que es el que debe crear el array de semáforos usando las opciones IPC_CREAT|IPC_EXCL|SHM_R|SHM_W, y el resto de mshs que sólo la llaman para obtener el identificador del array de semáforos con los permisos de lectura y escritura ( SHM_R|SHM_W ). • Su prototipo es : int semget ( key_t key, int nsems, int flags ) • Donde el int de retorno es el identificador del array de semáforos creados si no ha habido ningún error, -1 si lo ha habido con errno indicando qué es lo que ha ocurrido ( distinguir EEXIST ). 24 semget ( II ) • Los argumentos que recibe son : – key_t key : La clave que todos los procesos deben pasar a la función. Debe estar definido en el .h por ejemplo como CLAVE_SEMAFORO. – int nsems : El número de semáforos que se quieren crear – int flags : Opciones de creación, OR entre IPC_CREAT, IPC_EXCL, SHM_R y SHM_W • Cuidado que la creación de un semáforo es independiente de su inicialización y usar un semáforo sin inicializar puede tener resultados catastróficos, por lo que hay que asegurarse de inicializar los semáforos inmediatamente después de crearlos. • Veamos ahora un ejemplo de código que usa semget : 25 semget ( III ) #define CLAVE_SEMAFORO 2000 int semid ; semid = semget( CLAVE_SEMAFORO, 1, IPC_CREAT|IPC_EXCL|SHM_R|SHM_W); if ( semid == -1 ) { if ( errno == EEXIST ) semid = semget( CLAVE_SEMAFORO, 1, SHM_R|SHM_W ) ; else { perror(“semget”); exit(errno); } } else printf(“Soy el primero que crea el semáforo con identificador %d\n”, semid); 26 semop ( I ) • Implementa las operaciones de down y up. Su prototipo es : int semop ( int semid, struct sembuf * sops, unsigned nsops ) • Donde el int del retorno es para control de errores. • Los argumentos son : – int semid : Es el identificador devuelto por semget. – unsigned nsops : El número de operaciones a realizar sobre el semáforo – struct sembuf * sops : Es una estructura con 3 campos : • sem_num : El número de semáforo con el que queremos trabajar. • sem_op : La operación a realizar, que se indica con un número – Positivo : Si se quiere liberar al semáforo ( up ), ya que lo que hace es incrementar su valor. – Negativo : Si se quiere tomar el semáforo ( down ) ya que lo que hace es decrementar su valor. – 0 : Si lo que se quiere el bloquear el semáforo hasta que tenga un valor 0. 27 semop ( II ) • sem_flag : Flags para configurar la ejecución de la operación – SEM_UNDO : Para que cuando el proceso haga exit se nivelen los semáforos y no haya problemas de que deje bloqueados a otros procesos. – IPC_NOWAIT : Para que no se quede bloqueado y vuelva del down aunque el semáforo sea 0. • Ejemplo de pseudocódigo de lo que hace semop internamente : coge_semaforo ( int sem ) { if ( sem es 0 ) duerme hasta que sem != 0 ; else sem-- ; } libera_semaforo ( int sem ) { if ( hay procesos durmiendo ) despierta al primero sem++ ; } 28 semop ( III ). Ejemplo de código de down struct sembuf pbuf ; pbuf.sem_num = 0 ; pbuf.sem_op = -1 ; pbuf.sem_flg = SEM_UNDO ; if ( semop(semid, &pbuf, 1 ) == -1 ) { perror(“semop:”); return ERROR ; } 29 semop ( IV ). Ejemplo de código de up struct sembuf vbuf ; vbuf.sem_num = 0 ; vbuf.sem_op = 1 ; vbuf.sem_flg = SEM_UNDO ; if ( semop(semid, &vbuf, 1 ) == -1 ) { perror(“semop:”); return ERROR ; } 30 semctl ( I ) • Sirve para inicializar, obtener información o eliminar el array de semáforos. Su prototipo es : int semctl ( int semid, int semnum, int comando, union semun arg ) • Donde el valor de retorno es para control de errores o el retorno de determinados comandos como GETVAL o GETNCNT. • Los argumentos son : – int semid : Valor devuelto por semget. – int comando : Puede ser GETNCNT ( para saber cuántos procesos están esperando en el semáforo ), GETALL ( para obtener los valores de todos los semáforos del array ), GETVAL ( para uno ), SETALL ( para inicializar todos los semáforos del array ), SETVAL ( para uno ), IPC_RMID ( para borrar el array ). – int semnum : El número del semáforo sobre el que queremos trabajar. Se ignora si se usa GETALL/SETALL que es para todos. 31 semctl ( II ) – union semun arg : Estructura con los campos • val : El valor para SETVAL • struct semid_ds * buf : Información para IPC_STAT e IPC_SET • unsigned short * array : Array de valores para GETALL y SETALL. • Ejemplos de código donde se usa semctl : – Para inicializar el array : union semun carg ; carg.array = array ; // en array los valores a los que queremos inicializar los sem if ( semctl( semid, 0, SETALL, carg ) == -1 ) ERROR else OK – Para saber si hay procesos esperando en un semáforo : numero = semctl ( semid, 1, GETNCNT, carg ) – Para eliminar el array de semáforos : semctl ( semid, 0, IPC_RMID, NULL ) 32 ipcs / icrpm • De gran utilidad para esta práctica son estos dos comandos : – ipcs : Se teclea ipcs y nos muestra los segmentos de memoria compartida y semáforos que están reservados. – ipcrm : Nos permite eliminar manualmente cualquier segmento de memoria compartida ( shm ), cola de mensajes ( msg ) o array de semáforos ( sem ) que por un fallo del programa no se haya eliminado automáticamente. Para memoria compartida se ejecuta : ipcrm shm idMemoria y para semáforos: ipcrm sem idSemaforo • Un ejemplo de uso sería : – ipcs ( y así averiguamos el identificador de la memoria compartida ) – ipcrm shm id ( y sí ya se han terminado todos los procesos enganchados a la memoria compartida desaparece ) 33 Pasos orientativos para resolver la práctica • 1°. Familiarizarse con las funciones de creación, modificación y liberación de la memoria compartida y de semáforos. Codificar memoria.c, memoria.h, semaforo.c y semaforo.h. Modificar el makefile para tener en cuenta estos nuevos ficheros. • 2º. Copiar los ficheros de la práctica anterior en un nuevo directorio para asegurarnos que seguimos teniendo la versión anterior. • 3º. Modificar main.c para que se tenga en cuenta que ahora se trabaja con memoria compartida y con semáforos. • 4º. Modificar func.c para tener en cuenta los nuevos comandos y la gestión de la memoria compartida. • ¡Importante! Asegurarnos de eliminar posibles memorias compartidas que se hayan quedado sin borrar antes de la siguiente ejecución. 34 Control de errores y mejoras • Control de errores: – Controlar los retornos de todas las funciones de manejo de la memoria compartida y los semáforos. – En general, robustecer el código frente a cualquier posible error que ocurra y asegurar que se dejará la máquina cómo estaba antes de que se ejecutase alguna instancia de msh. • Mejoras: – Cualquiera de las mejoras de la práctica anterior, son aplicables aquí. En general, se deja abierta la posibilidad de mejorar el código para que se parezca lo máximo posible a un intérprete de comandos Linux. – IPC_NOWAIT para que en el caso de que el código se vaya a quedar bloqueado en un punto nos muestra antes un mensaje informándonos. – Usar memoria dinámica compartida. – Usar algoritmos como lectores/escritores o mejores para no hacer todos los acceso bloqueantes, sólo cuando 1 escribe, que los demás no lean, pero permitir que hay lecturas concurrentes. 35 Problema lector/escritor. Teoría ( I ) • Vamos a ver el problema del lector/escritor en el que tenemos procesos con una base de datos común y unos quieren leer de ella y otros escribir. Al igual que en la práctica no se permite que mientras que están leyendo otro escriba, aunque si leer puesto que esto no supone ninguna modificación de la memoria compartida, y tampoco se permite que varios estén escribiendo. • Para resolverlo se usan 2 semáforos binarios : – mutex : Que controla la escritura de la variable que indica el número de lectores ( numLectores ). De forma que si hay algún lector ya no puede haber escritor y cuando ya no haya lectores ya puede entrar el escritor. – db : Que controla el acceso a la base de datos por parte tanto de los lectores como los escritores. Si está a 1 entonces es que no hay nadie escribiendo y pueden entrar tantos lectores como quieran a consultar la base de datos. Si es 0 para a los lectores porque el escritor está modificando la base de datos. 36 Problema lector/escritor. Pseudocódigo ( II ) semaforo mutex = 1 semaforo db = 1 int numLectores = 0 Escritor while ( 1 ) { down(db) ; escribir up(db) ; } Lector while ( 1 ){ down( mutex ) ; numLectores++ ; if ( numLectores == 1 ) down(db) ; up(mutex); leer down(mutex); numLectores-- ; if ( numLectores == 0 ) up(db); up(mutex); } 37