Sistemas Operativos Práctica 4 Ing. Andrés Bustamante afbustamanteg@unal.edu.co Ingenierı́a de Sistemas Facultad de Ingenierı́a Universidad de la Amazonia 2009 1. Objetivo El objetivo de la práctica es que el estudiante practique los conceptos aprendidos sobre la comunicación entre procesos (IPC) y los semáforos aplicados en el sistema operativo Linux. 2. Metodologı́a Mediante el uso del lenguaje de programación C, el estudiante aprenderá a establecer y manejar una comunicación entre varios procesos, y creará una pequeña aplicación que permita crear hilos y la implementación de semáforos para resolver el problema del productor/consumidor. Para tal fin se le instruirá en las funciones básicas para dichos manejos y como es costumbre, se apoya en la utilización de las páginas del manual de Linux (man). Es de tener en cuenta que se requieren los conocimientos adquiridos sobre hilos y procesos de la practica 3. 3. Marco teórico 3.1. Programación con hilos y semáforos Los hilos comparten el acceso al espacio de direcciones de un proceso, por lo tanto, se necesitan primitivas de sincronización para controlar el acceso a los datos comunes. La biblioteca de hilos de Linux proporciona varias primitivas de sincronización, entre las que se encuentran los semáforos generales. Un semáforo en POSIX es un tipo variable de sem_t con operaciones atómicas tales como inicialización, wait y signal. Existen semáforos nombrados y no nombrados. Un semáforo no nombrado puede ser usado por solo un proceso o por sus procesos hijos. Un semáforo nombrado puede ser utilizado por cualquier proceso. Nosotros utilizaremos semáforos no nombrados para realizar las prácticas. Las funciones sobre semáforos no nombrados son: int int int int sem_init(sem_t *sem, int pshared, unsigned int value) sem_wait(sem_t *sem) sem_post(sem_t *sem) sem_destroy(sem_t *sem) 1 sem_init crea e inicializa la variable del semáforo (sem); value es el valor inicial del semáforo (no puede ser negativo); pshared indica si el semáforo es nombrado (valor distinto de 0) o no nombrado (valor 0). sem_wait bloquea al hilo si el valor del semáforo es cero, si no, decrementa el valor del semáforo. sem_post incrementa el valor del semáforo. Puede llegar a desbloquear un hilo que ejecutó sem_wait. sem_destroy sirve para eliminar definitivamente un semáforo previamente inicializado. Solo se utiliza en el caso en el que ya no sea necesario dicho semáforo para el funcionamiento del programa. Todas estas funciones devuelven un valor de 0 si ha tenido éxito y -1 si se ha producido cualquier error. Ejemplo: #include <stdio.h> #include <pthread.h> #include <semaphore.h> sem_t sm; sem_t p; int s = 0; int suma = 0; p1() { int n= 0; int i= 0; while (i< 10) { sem_wait(&sm); n++; i++; if ( (n%2) != 0) { s= n; sem_post(&p); } else sem_post(&sm); } } p2() { int i=0; while(i < 5) { sem_wait(&p); i= i++; 2 suma= suma + s; sem_post(&sm); } } main() { int error1, error2; pthread_t tp1; pthread_t tp2; sem_init (&sm, 0, 1); sem_init(&p, 0, 0); error1= pthread_create (&tp1, NULL, (void *)p1, NULL); if (error1) printf("\n", error1); error2= pthread_create (&tp2, NULL, (void *)p2, NULL); if (error2) printf("\n", error2); pthread_join (tp1,NULL); pthread_join(tp2, NULL); printf("%s%d", "La suma es ->", suma); } Para que un programa con múltiples hilos funcione bien se ha de asegurar que dos o más activaciones concurrentes de una misma rutina, compartida por dichos hilos, han de estar correctamente sincronizadas. Por lo tanto, el acceso concurrente a datos compartidos deja a estos en una situación consistente. Si varios hilos pueden llamar simultáneamente a funciones que pertenecen a un mismo módulo, asegurándose la seguridad y corrección en todo momento, se dice que tales funciones son seguras (también se las denomina reentrantes). Por ejemplo, funciones como sin() que acceden a datos globales sólo en lectura, son trivialmente reentrantes. Cualquier código que vaya a ser ejecutado de una manera ası́ncrona por hilos ha de ser reentrante y ha de tener las siguientes caracterı́sticas: No se debe cambiar ningún elemento situado en memoria global No se debe cambiar el estado de ningún archivo o dispositivo Debe hacerse referencia a un elemento situado en memoria global sólo en circunstancias especiales (p.e. asegurando la exclusión mutua en el acceso a datos globales) El código que se ejecuta con mayor frecuencia, de una manera ası́ncrona y compartida, son las bibliotecas del sistema. Por lo tanto, toda biblioteca que se enlace con un programa ha de ser mtsegura o tener una interfaz mt-seguro. Las funciones que se pueden encontrar en bibliotecas, se pueden clasificar en cuatro grupos: Funciones que tienen una interfaz mt-segura ya que siempre han sido mtseguras o que han sido modificadas para que lo sean. Funciones que no son mt-seguras porque su tamaño se hubiera incrementado demasiado. Funciones que tienen un interfaz no mt-seguro. Funciones que son equivalentes a las del tercer grupo, pero que han sido modificadas para hacerlas mt-seguras. Las funciones de este grupo se las identifica por el sufijo _r. 3 Tanto las funciones mt-seguras, como las funciones que tienen interfaces mt-seguros pueden ser utilizadas transparentemente por el programador (ésto es, no tiene que prestarles mayor atención). Para la mayorı́a de las funciones con interfaces no mt-seguros han sido desarrolladas funciones mtseguras con sufijo _r. Si las paginas man no dicen nada acerca de si una función es mt-segura, entonces lo es. Todas las funciones no mt-seguras son identificadas explı́citamente en las páginas del manual. Las funciones para las que no haya garantı́a de que sean reentrantes, se les puede utilizar en programas con múltiples hilos, si las llamadas a dichas funciones se hacen sólo desde el hilo principal (main()). También se puede utilizar con seguridad las funciones no-reentrantes, siempre que se sincronice el acceso de los hilos a dichas funciones. 3.2. Compilación Para compilar y enlazar con éxito un programa que contenga múltiples hilos y semáforos, necesita incluir los archivos de cabecera siguientes: pthread.h semaphore.h Para compilar un programa en C que utiliza la biblioteca de hilos, ponemos igual que en la práctica 3: $ cc -lpthread nombre_archivo.c -o archivo_salida 3.3. Recomendaciones y buenas prácticas Por último y como resumen, he aquı́ una serie de normas que es conveniente observar cuando se programa con hilos: Se ha de saber qué bibliotecas o módulos utilizamos en una aplicación y si éstos son reentrantes. Un programa con hilos no debe utilizar código secuencial sin hilos, de una forma arbitraria. Un código con hilos debe utilizar código no-reentrante sólo en el hilo principal (aquel que contiene a main()). Las bibliotecas suministradas son reentrantes, salvo que se diga lo contrario. El hilo inicial debe asegurar que no hay acceso concurrente a la biblioteca stdio cuando se ejecuta código que no asegure la propiedad de reentrancia. No intente realizar operaciones globales (o acciones con efectos laterales globales) entre varios hilos. Por ejemplo, los hilos no deben cooperar en el acceso a archivos. Cuando se espere un comportamiento del programa en el que han de cooperar varios hilos, utilice las funciones adecuadas. Por ejemplo, si la terminación de main() debe significar sólo la terminación de dicho hilo, entonces el final del código de main() debe ser pthread_exit(). 4. Práctica Implemente el problema del Productor/Consumidor con buffer finito utilizando semáforos. Tanto el productor como el consumidor serán hilos. Recuerde: Productor: produce un dato y lo pone en la siguiente posición libre en el buffer. Si el buffer está lleno, se bloqueará hasta que haya espacio libre. 4 Consumidor: toma un dato del buffer y lo imprime. Si el buffer está vacı́o se bloqueará hasta que haya al menos un dato. A continuación se da una estructura de guı́a para desarrollar la solución: #include <stdio.h> #include <pthread.h> #include <semaphore.h> #define veces 30 #define tamano 10 /* inserte aqui las declaraciones de semaforos y variables o estructuras de datos compartidas, necesarias para resolver el problema */ productor() { int i; /* otras declaraciones locales */ for (i=0; i<veces; i++) { sleep(1); /* retardo */ /* codigo del productor */ } } consumidor () { int i; /* otras declaraciones locales */ for (i=0; i<veces; i++) { /* codigo del proceso consumidor */ sleep(i%5); /* retardo */ } } main () { /* declaracion de los hilos productor y consumidor */ /* inicializacion de los semaforos */ setbuf(stdout,NULL); /*evita que las E/S se hagan con buffering*/ /* creacion de hilos y deteccion de errores */ printf("FIN\n"); } 5. Entregables 1. Se debe entregar informe únicamente en formato PDF con la descripción del proceso realizado de manera detallada. Se recomienda realizar capturas de pantalla describiendo los resultados 5 obtenidos. También se deben incluir conclusiones del aprendizaje de la práctica y bibliografı́a consultada (si es el caso). 2. También se debe entregar el archivo con el código fuente del programa elaborado en lenguaje C. Se recomienda realizar comentarios en el código para documentar el programa y colocar el nombre de los integrantes del grupo al principio del archivo de código fuente (como comentario). Los entregables se deben enviar vı́a correo electrónico a la dirección electrónica del profesor. 6