Semáforos - Bucomsec - Comunicaciones Y Seguridad

   EMBED

Share

Preview only show first 6 pages with water mark for full document please download

Transcript

Sistemas Operativos I Práctica 4 Sincronización entre procesos. Semáforos. L os sistemas operativos, además de proporcionar diferentes mecanismos para comunicar procesos, pueden ofrecer herramientas que permiten sincronizar varios procesos. El paquete denominado IPC (InterProcess Comunication) del sistema operativo UNIX proporciona un conjunto de componentes para la comunicación y sincronización de procesos, entre los que destaca la gestión de semáforos y la gestión de memoria analizada en la práctica anterior. A lo largo de esta práctica estudiaremos las llamadas al sistema que proporciona el sistema operativo para gestionar los semáforos. SEMÁFOROS Los semáforos que emplea el sistema operativos UNIX es muy similar a la definición clásica de Dijkstra, en el sentido de que es una variable entera con operaciones atómicas de inicialización, incremento y decremento con bloqueo. UNIX define tres operaciones fundamentales sobre semáforos: ƒ semget. Crea o toma el control de un semáforo ƒ semctl. Operaciones de lectura y escritura del estado del semáforo. Destrucción del semáforo ƒ semop. Operaciones de incremento o decremento con bloqueo UNIX no ofrece las operaciones clásicas P y V, sino que dispone de la llamada al sistema semop que permite realizar varias operaciones que incluyen las P y V. Previamente a utilizar estas operaciones será necesario crear el semáforo e inicializarlo. A continuación se describen las tres llamadas al sistema que permiten gestionar los semáforos en UNIX. LLAMADA SEMGET int semget ( key_t key, int nsems, int semflg ) La llamada semget se emplea para solicitar al sistema operativo que cree un conjunto de semáforos (un vector de semáforos). El valor devuelto (si no ha habido error) es el identificador del vector de semáforos y permite referirse a él en sucesivas llamadas al sistema. Semget tiene los siguientes argumentos: ƒ key es la clave con la que se crea el semáforo, de modo que siempre que se utilice la misma clave se podrá acceder al mismo semáforo. Puede ser IPC_PRIVATE (obliga a semget a crear un nuevo y único identificador, nunca devuelto por anteriores llamadas a semget hasta que sea liberado con semctl) o bien obtenerse de la función ftok que devuelve una clave (un número) a partir de un nombre de archivo existente y accesible y un número que lo identifica (ver ejemplo de uso y consultar man). ƒ nsems es el número de semáforos que se van a crear (el tamaño del vector de semáforos). ƒ semflg es un flag que permite establecer los permisos del semáforo y en caso de que ya esté creado obtener su identificador. 2 Si la ejecución se realiza con éxito, entonces devolverá un valor no negativo identificando el conjunto de semáforos. En caso contrario, devolverá -1 y la variable global errno tomará en código del error producido. LLAMADA SEMCTL int semctl ( int semid, int semnun, int cmd, union semun arg ) La llamada semctl se emplea para realizar operaciones de control sobre los semáforos. Las operaciones que más se emplean son: ƒ SETVAL permite establecer el valor de los elementos del vector de semáforos. ƒ IPC_RMID elimina el vector de semáforos del sistema y debe ser llamada antes de que el proceso finalice, pues los semáforos no son eliminados del sistema cuando el proceso finaliza. ƒ GETVAL devuelve el valor actual del semáforo. Los argumentos de la llamada semctl son los siguientes: ƒ semid es el identificador del vector de semáforos (obtenido de semget). ƒ semnun es el índice del vector de semáforos, es decir, el semáforo a utilizar en esta operación. ƒ cmd representa el tipo de operación de control a realizar (por ejemplo: SETVAL). ƒ El cuarto parámetro está relacionado con el tercero, se pasará un tipo de parámetro u otro dependiendo de lo que se haya escogido en el tercer parámetro. Si ocurre un error la llamada devolverá -1 y la variable global errno tomará el código del error producido. LLAMADA SEMOP int semop ( int semid, struct sembuf *sops, unsigned nsops) Esta llamada permite realizar modificar el contador del semáforo. En UNIX es posible añadir cualquier valor al semáforo y no solamente 1 y -1 (funciones P() y V()). A semop se le pasa como argumento: ƒ semid que indica el identificador del semáforo ƒ struct sembuf representa el vector de operaciones sobre semáforos. ƒ nsops es el número de operaciones a realizar 3 La estructura struct sembuf está formada por: ƒ sem_num. Índice del elemento del vector de semáforos sobre el que se desea realizar la operación. ƒ sem_op. Cantidad a añadir al valor del semáforo (puede ser negativa, en nuestro caso ±1) ƒ sem_flg. Modificadores para describir cómo se desea que se realice la operación (en nuestro caso no es necesario ningún modificador, por lo que el valor de este campo será 0 para todas las operaciones). Si el campo sem_op de una operación es positivo, se incrementa el valor del semáforo. Asimismo, si sem_op es negativo, se decrementa el valor del semáforo si el resultado no es negativo. En caso contrario el proceso espera a que se dé esa circunstancia. Es decir, sem_op==1 produce una operación V y sem_op==-1, una operación P. La llamada semop devolverá el valor 0 si se ejecuta con éxito, o -1 si se produce un error, tomando además la variable global errno el valor del código del error producido. EJEMPLO DE USO Como ejemplo vamos a crear una serie de funciones base para construir las operaciones P y V de los semáforos. Lo primero hay que definir las variables y declarar los prototipos de las funciones a utilizar. /* Definimos las constantes utilizadas para identificar las operaciones P y V */ #define OP_P ((short) -1) #define OP_V ((short) 1) /* variables globales */ int semid; int max; /* Prototipos de las funciones que podemos utilizar sobre los semaforos */ int creaSEM(int cuantosSem, char *queClave); void iniSEM(int semID, ushort queSemaforo, int queValor); void P(int semID, ushort queSemaforo); void V(int semID, ushort queSemaforo); void destruyeSEM(int semID); Para utilizar las llamadas de semáforos hay que incluir los archivos de cabecera: #include #include #include 4 Con la función creaSEM creamos el semáforo basándolo en la llave generada con la cadena queClave y con la letra 'k'. Indicamos que lo cree con permisos de lectura y escritura para el usuario y que si ya existía, simplemente devuelva el identificador. El número de semáforos a crear lo indicamos con cuantosSEM. int creaSEM(int cuantosSEM, char *queClave) { int idSEM; key_t llave; llave = ftok(queClave, 'k'); if ((idSEM=semget(llave, cuantosSEM, IPC_CREAT|0600))==-1) { perror("semget"); exit(-1); } } return idSEM; Con la función iniSEM inicializamos el valor del contador de semáforo queSemaforo perteneciente al grupo de semáforos semID. void iniSEM(int semID, ushort queSemaforo, int queValor) { semctl(semID, queSemaforo, SETVAL, queValor); } Ya podemos realizar las operaciones P y V del semáforo queSemaforo perteneciente al grupo de semáforos semID. void P(int semID, ushort queSemaforo) { struct sembuf operacion; operacion.sem_num = queSemaforo; /* semáforo sobre el que trabajaremos */ operacion.sem_op = OP_P; /* operación a realizar: P */ operacion.sem_flg = 0; /* Siempre 0 */ semop(semID, &operacion, 1); /* Finalmente, ejecutamos la operación */ } void V(int semID, ushort queSemaforo) { struct sembuf operacion; operacion.sem_num = queSemaforo; /* semáforo sobre el que trabajaremos */ operacion.sem_op = OP_V; /* operación a realizar: V */ operacion.sem_flg = 0; /* Siempre 0 */ semop(semID, &operacion, 1); /* Finalmente, ejecutamos la operación */ } Por último con la función destruyeSEM liberamos el semáforo de la memoria. 5 void destruyeSEM(int semID) { semctl(semID, 0, IPC_RMID, 0); } PROBLEMA DE LECTORES/ESCRITORES Un problema clásico en el estudio de los Sistemas Operativos es aquél que implica la existencia de procesos que han de acceder de forma concurrente a un recurso compartido, sobre el cual se realizan dos operaciones distintas. Cada proceso se clasifica dependiendo del tipo de operación que realiza: ƒ Un proceso Lector se caracteriza porque accede al recurso de modo que no altera el contenido del mismo ƒ Un proceso Escritor accede al recuso crítico con la posibilidad de modificar su contenido. En el sistema puede haber varios procesos Lectores accediendo concurrentemente al recuso sin importar que se superpongan, puesto que nunca comprometerán su consistencia. Sin embargo, cada Escritor ha de acceder de forma exclusiva al recurso, y no debe nunca entrelazarse con otro proceso. El orden de atención de la lista de procesos dependerá de del tipo de gestión: prioridad lectores, prioridad escritores y prioridad FIFO. REFERENCIAS BIBLIOGRAFÍA ƒ F. M. Márquez: UNIX. Programación avanzada, Rama, 1996. ƒ K. A. Robbins y S. Robbins: UNIX. Programación práctica, Prentice may, 1997. ƒ F. Maciá Pérez y A. Soriano Payá: El Shell Korn. Manual de Usuario y Programador, Servicio de Publicaciones de la Universidad de Alicante, 1999. ƒ J. Tackett y D. Gunter: Linux 4ª Edición, Prentice may, 2000. WEB Existe una gran cantidad de manuales y tutoriales de Unix y Linux en Internet que puedes localizar a partir de cualquier buscador (ej. www.google.com). 6 AUTOEVALUACIÓN 1. El programa realizado en la práctica anterior incrementaba una variable compartida por dos procesos sin ninguna restricción (qué proceso comienza, dependiente del núcleo, etc.). Teniendo en cuenta que los semáforos permiten sincronizar procesos, realizar un programa llamado sumaalt.c que cree un proceso hijo. Tanto el padre como el hijo alternadamente tienen que ir sumando un valor a una variable compartida hasta el máximo especificado. Además, el proceso que empieza se indicará en la línea de comandos. (2.5 puntos) sumaalt inicio_variable incr_padre incr_hijo maximo_variable padre|hijo $ sumaalt 0 1 2 15 padre Padre(345): variable=1 Hijo(349): variable=3 Padre(345): variable=4 Hijo(349): variable=6 Padre(345): variable=7 Hijo(349): variable=9 Padre(345): variable=10 Hijo(349): variable=12 Padre(345): variable=13 Hijo(349): variable=15 $ 2. Realizar un programa que resuelva el problema de lectores/escritores con priopidad FIFO (lefifo.c) que tenga como argumento un nombre de archivo en el que figuren los datos relativos a cada proceso en cuanto a tipo de proceso, tiempo de inicio y duración en la sección crítica. El programa leerá el archivo y creará los procesos. Los procesos irán mostrando en pantalla una traza de su ejecución, es decir, cada vez que realicen una P(), una V(), entren o salgan de la sección crítica mostrarán un mensaje con el tiempo, su identificador y su acción. El tipo de proceso L indicará que en un lector mientras que el tipo E indicará que es un escritor. Cada línea del archivo contiene el tipo de proceso, el retardo de comienzo y el tiempo dentro de la sección crítica. (2.5 puntos) Ejemplo: $ lefifo traza.txt Tiempo Tipo Acción 20:21:01 L P(MUTEX) 20:21:01 L Entra_Sección 20:21:06 L Sale_Sección 20:21:07 L V(MUTEX) .............. .............. $ cat traza.txt L 1 5 L 8 6 E 12 3 L 13 2 $ 7 3. Realizar un programa que resuelva el problema de lectores/escritores con prioridad lectores (lelect.c) teniendo en cuenta las consideraciones anteriores. (2.5 puntos) 4. Realizar un programa que resuelva el problema de lectores/escritores con prioridad escritores (leescr.c) teniendo en cuenta las consideraciones anteriores. (2.5 puntos) FECHA DE ENTREGA: Semana del 30 de mayo en la sesión de prácticas 8