Manual de Prácticas de Sistemas Operativos

Manual de Prácticas de Sistemas Operativos Curso 2004-2005 Índice general 1. Evaluación y Calendario de Prácticas 1 2. Enunciado de las Prácticas 2.1. Práctica 1 . . . . . . . . . . . . . . 2.1.1. Objetivos . . . . . . . . . . 2.1.2. Enunciado . . . . . . . . . . 2.2. Práctica 2 . . . . . . . . . . . . . . 2.2.1. Objetivos . . . . . . . . . . 2.2.2. Enunciado . . . . . . . . . . Proceso santa (“santa.c”) . . Proceso reno (“reno.c”) . . . Proceso duende (“duende.c”) 2.3. Práctica 3 . . . . . . . . . . . . . . 2.3.1. Objetivos . . . . . . . . . . 2.3.2. Enunciado . . . . . . . . . . 2.3.3. Ejemplos de ejecución . . . . . . . . . . . . . . . . 3 3 3 3 5 5 5 6 6 7 8 8 8 9 . . . . . . 11 11 12 13 13 14 15 . . . . . 19 19 19 20 21 23 3. Introducción al Sistema Operativo 3.1. Archivos . . . . . . . . . . . . . . 3.1.1. Directorios . . . . . . . . . 3.1.2. Disquetera . . . . . . . . . 3.2. Compilación . . . . . . . . . . . . 3.3. Depuración . . . . . . . . . . . . 3.4. LLamadas al sistema . . . . . . . 4. Gestión de Procesos 4.1. Definición de Proceso . . . . . 4.2. Estados de un Proceso . . . . 4.3. Identificación de Procesos . . 4.4. Creación de Procesos: fork() . 4.5. Ejecución de Procesos: execl() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5. Utilización de Semáforos en el Laboratorio i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 ii ÍNDICE GENERAL 6. Utilización de monitores en el laboratorio 29 6.1. Monitor ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 6.2. Monitores con paso de parámetros . . . . . . . . . . . . . . . . . . . . . . . 36 Capítulo 1 Evaluación y Calendario de Prácticas Para aprobar la parte práctica de la asignatura, es necesario entregar las prácticas en la fecha indicada, que dichas prácticas funcionen y responder correctamente a algunas preguntas sobre las mismas. La evaluación de las prácticas será individual, y por tanto, deberán haber sido realizadas por los dos miembros de cada grupo. La corrección de las prácticas tendrá lugar en el laboratorio y se hará delante de cada grupo. Sólo los alumnos que superen la parte práctica podrán optar a realizar el examen de la parte de teoría. Una vez que un alumno supera la parte práctica, queda liberado de la misma en las siguientes convocatorias. En las siguientes tablas se indica: el día de inicio de cada práctica, en el que se explicará (al inicio de cada turno) el contenido de la misma; el día límite de entrega (al finalizar el turno correspondiente); el día de corrección (en las horas correspondientes a cada turno). Turno Lunes Martes Miércoles Jueves Turno Lunes Martes Miércoles Jueves Inicio 14 Marzo 15 Marzo 16 Marzo 17 Marzo Práctica 1 Entrega Corrección 4 Abril 11 Abril 29 Marzo 5 Abril 30 Marzo 6 Abril 31 Marzo 7 Abril Inicio 11 Abril 5 Abril 6 Abril 7 Abril Práctica 2 Entrega Corrección 25 Abril 2 Mayo 19 Abril 26 Abril 20 Abril 27 Abril 21 Abril 28 Abril 1 2 CAPÍTULO 1. EVALUACIÓN Y CALENDARIO DE PRÁCTICAS Turno Lunes Martes Miércoles Jueves Inicio 2 Mayo 26 Abril 27 Abril 28 Abril Práctica 3 Entrega Corrección 30 Mayo 6 Junio 24 Mayo 31 Mayo 25 Mayo 1 Junio 26 Mayo 2 Junio Entrega de prácticas: Cada grupo soXX deberá dejar, en el directorio /home/clave/labs/so/practicaN /soXX/ los ficheros fuentes correspondientes a la prácticaN al finalizar el turno correspondiente a dicho grupo, tal y como se indica en las tablas anteriores. Dichos directorios ya están creados (no son subdirectorios dentro de la cuenta de cada usuario), y se bloquearán una vez finalice el plazo de presentación de cada práctica. Si se detectan prácticas copiadas, todos los miembros de las parejas involucradas suspenderán la asignatura. Capítulo 2 Enunciado de las Prácticas 2.1. Práctica 1 2.1.1. Objetivos Entender el funcionamiento de las llamadas al sistema relacionadas con la creación de procesos (fork, execl, wait). Se recomienda leer la parte del manual relativa a estas llamadas y realizar los programas de ejemplo que se incluyen (ver el capítulo 4 del manual). Se recomienda también leer el manual on-line de dichas llamadas al sistema (man fork, man execl, man -a wait (o man 2 wait)). Implementar un grafo de precedencia sincronizando la ejecución concurrente de varios procesos. 2.1.2. Enunciado (a) Implementar el grafo de precedencia de la figura 2.1 utilizando las llamadas al sistema fork y wait (no utilizar waitpid). El grupo de sentencias a ejecutar en cada nodo del grafo se simularán mediante la sentencia printf(“cadena”), donde “cadena” es la cadena de caracteres que contiene cada nodo de la figura. La frase deberá aparecer en una única línea. (b) Repetir el apartado anterior utilizando un proceso auxiliar imprime_pantalla cuyo código será ejecutado por los procesos hijo mediante la llamada al sistema execl. Dicho proceso deberá imprimir en pantalla, mediante la sentencia printf(“cadena”), la(s) cadena(s) de caracteres que reciba como argumento(s). Los resultados obtenidos (implementando correctamente la precedencia del grafo de la figura) en los dos apartados no deben ser los esperados. En el primer apartado se deben obtener palabras repetidas, mientras que en el segundo éstas deben verse en un orden no permitido por el grafo pero sin repetirse. Ambas situaciones son debidas a cómo funcionan las llamadas al sistema fork y execl. El objetivo es detectar la causa de estas dos anomalías y entender su solución. 3 CAPÍTULO 2. ENUNCIADO DE LAS PRÁCTICAS 4 Hola Buenos dias tenga y usted. Hasta luego malos Lucas Figura 2.1: Grafo de Precedencia. 2.2. PRÁCTICA 2 2.2. Práctica 2 2.2.1. Objetivos 5 Utilizar los semáforos como una herramienta de sincronización para acceder en exclusión mutua a un recurso compartido. Para ello, se proporciona una librería que permite la utilización de semáforos de una manera sencilla y similar a la explicada en teoría (ver detalladamente el capítulo 5 del manual). Utilizar semáforos genéricos para controlar el acceso de N procesos a un recurso. Utilizar el sistema de ficheros como un recurso compartido entre varios procesos, entendiendo el funcionamiento de las llamadas al sistema fopen, fprintf, fscanf y fclose. 2.2.2. Enunciado Implementar, mediante la utilización de semáforos el problema clásico de Santa Claus, que se describe como sigue. Santa Claus duerme en su taller del polo norte y sólo puede despertarse en dos circunstancias: Cuando los 4 renos vuelven de sus vacaciones. El último reno en llegar debe despertar a Santa Claus. Mientras, los otros 3 renos deben esperar en un refugio a que llegue el cuarto reno y Santa Claus esté listo para empezar el reparto de juguetes. Cuando algún duende tiene dificultades para hacer juguetes. Para permitir que Santa Claus duerma, los duendes sólo pueden despertarle cuando son tres los que tienen problemas. Además, cuando tres duendes han ido a plantear sus problemas a Santa Claus, cualquier otro duende que desee visitar a Santa Claus debe esperar a que éstos regresen. Si Santa Claus despierta y encuentra tres duendes en la puerta de su taller, junto con el último reno que vuelve de vacaciones, Santa Claus decide que los duendes pueden esperar hasta después de Navidad, y se va a repartir juguetes. En la solución se debe proporcionar el código de los tres tipos de procesos involucrados santa, reno y duende; así como el código de dos dos procesos encargados de crear y destruir los semáforos necesarios para la sincronización: inic_santa y fin_santa. Además, se utilizarán los ficheros “duendes.txt” y “renos.txt” para almacenar la información mínima necesaria para la resolución del problema, teniendo en cuenta que: i) el acceso al fichero “duendes.txt” es en lectura para santa; y en lectura/escritura para los procesos duende (los procesos reno no pueden acceder); ii) el acceso al fichero “renos.txt” es en lectura para santa; y en lectura/escritura para los procesos reno (los procesos duende no pueden acceder). En la resolución de la práctica se supondrá la existencia de un único proceso santa, un número no determinado de procesos duende y exactamente 4 procesos reno. Cada proceso se ejecutará en una ventana diferente. A continuación, se describe el funcionamiento de cada uno de los tres tipos de procesos. 6 CAPÍTULO 2. ENUNCIADO DE LAS PRÁCTICAS Proceso santa (“santa.c”) El proceso santa debe esperar descansando a que los 4 renos hayan regresado de sus vacaciones o 3 duendes tengan problemas para hacer juguetes. A continuación, dependiendo del caso, o bien se va a repartir juguetes o ayuda a los duendes a resolver sus problemas. Se repite este comportamiento hasta que se seleccione una opción para finalizar el proceso santa. A continuación, se muestran dos ejemplos de ejecución del proceso santa, indicando los mensajes que se deben mostrar en pantalla. > santa Durmiendo ..... Voy a repartir juguetes (pulsa ENTER o 1 para finalizar) Durmiendo ..... >santa Durmiendo ..... Ayudo a duende Ayudo a duende Ayudo a duende (pulsa ENTER o 1 para finalizar) Durmiendo ..... Proceso reno (“reno.c”) Los procesos reno deben esperar en el refugio hasta que el último reno haya regresado de sus vacaciones. Cuando Santa Claus decide ir a repartir juguetes se lleva a los renos; una vez finalizado el trabajo, los renos se van otra vez de vacaciones. Se repite este comportamiento hasta que se seleccione una opción para finalizar el proceso reno. A continuación, se muestran dos ejemplos de ejecución de un proceso reno, indicando los mensajes que se deben mostrar en pantalla. > reno Estoy de vacaciones (pulsa ENTER o 1 para finalizar) Espero en el refugio a los demás renos ..... Voy a repartir juguetes .... Estoy de vacaciones (pulsa ENTER o 1 para finalizar) > reno Estoy de vacaciones (pulsa ENTER o 1 para finalizar) Voy a repartir juguetes .... Estoy de vacaciones (pulsa ENTER o 1 para finalizar) 2.2. PRÁCTICA 2 7 Proceso duende (“duende.c”) Los procesos duende hacen continuamente juguetes hasta que tienen un problema. En ese momento se dirigen al taller de Santa Claus a pedir ayuda. Sin embargo, si ya han ido tres duendes a pedir ayuda, el duende con problemas debe esperar a que regresen. Una vez que un duende con problemas ha recibido ayuda, se repite el comportamiento anterior hasta que se seleccione una opción para finalizar el proceso duende. A continuación, se muestran dos ejemplos de ejecución de un proceso duende, indicando los mensajes que se deben mostrar en pantalla. > duende Haciendo juguetes (pulsa ENTER si tienes problemas o 1 para finalizar) Voy en busca de ayuda .... Resuelto el problema Haciendo juguetes (pulsa ENTER si tienes problemas o 1 para finalizar) > duende Haciendo juguetes (pulsa ENTER si tienes problemas o 1 para finalizar) Voy en busca de ayuda .... Soy el tercer duende y despierto a Santa Claus Resuelto el problema Haciendo juguetes (pulsa ENTER si tienes problemas o 1 para finalizar) CAPÍTULO 2. ENUNCIADO DE LAS PRÁCTICAS 8 2.3. Práctica 3 2.3.1. Objetivos Utilizar una herramienta de sincronización de alto nivel como son los monitores condicionales. Para ello, se proporciona una librería que permite la utilización de monitores de una manera sencilla y similar a la explicada en teoría (ver detalladamente el capítulo 6 del manual). Además, en el manual se incluyen dos monitores de ejemplo cuya implementación y prueba se recomienda antes de abordar esta práctica. Entender las diferencias existentes entre los dos tipos de monitores vistos en teoría, en función del proceso elegido para continuar la ejecución cuando se despierta un proceso suspendido en una variable condition. 2.3.2. Enunciado En este problema se deberá simular mediante la utilización de un monitor fabrica la gestión de una fabrica en la que existen empleados de dos categorías: 1. Categoría A: son los empleados encargados de la fabricación de cada uno de los productos. Dado que la fábrica dispone de 2 tipos de productos, un grupo de empleados de la categoría A fabrica productos del tipo 1, y otro fabrica productos del tipo 2. Cada empleado fabrica ejemplares del producto correspondiente de uno en uno. Una vez fabricado un ejemplar lo lleva a un almacén con capacidad para N productos, donde se almacenan todos los productos de la fábrica (tanto los de tipo 1 como los de tipo 2). Los empleados de categoría A acceden al almacen en orden FIFO. Dicho orden FIFO sólo puede romperse para garantizar que no exista interbloqueo. 2. Categoría B: son los empleados encargados de realizar el control de calidad de los productos fabricados que han sido depositados en el almacén. Esta tarea se lleva a cabo sobre un puesto de trabajo individual para cada empleado, donde el control de calidad sólo se puede realizar utilizando simultáneamente un producto de tipo 1 y uno de tipo 2. El resultado de dicho control de calidad puede ser: a) Positivo: en ese caso los dos productos pasan a la red de distribución de ventas. b) Negativo: este resultado se obtiene cuando uno de los dos productos (el de tipo 1 o el de tipo 2) no supera el control de calidad, por lo que dicho producto se desecha. En ese caso, es necesario que dicho empleado retire un nuevo producto del tipo desechado, y realice un nuevo control de calidad. Observe que, en el caso de que ambos productos fuesen defectuosos, el control de calidad sólo detecta uno de ellos, mientras que el otro será detectado en futuros controles —suponga que no es posible que se generen productos defectuosos de manera indefinida. 2.3. PRÁCTICA 3 9 Los empleados de categoría B acceden al almacén en orden FIFO. Dicho orden sólo puede romperse si no es posible realizar ningún control de calidad manteniendo el FIFO, pero sí rompiendo dicho orden FIFO. Es decir, el objetivo es realizar el mayor número de controles de calidad posibles. Además, se tendrán en cuenta las siguientes consideraciones: 1. Se implementará un proceso denominado empA, que se ejecutará con un parámetro en la línea de comandos: empA tipo. El parámetro tipo consistirá en un número indicando el tipo de producto producido (1 ó 2). Dicho proceso, una vez depositado el producto en el almacén, indicará mediante un mensaje por pantalla: i) el número de elementos de cada tipo en el almacén; y ii) el número de empleados empB que están esperando por cada tipo de producto —si hay un empB esperando por los dos tipos de productos, éste se contabilizará como un empB esperando por el tipo 1, y un empB esperando por el tipo 2. 2. Se implementará un proceso denominado empB que, una vez retirados los dos productos necesarios para realizar el control de calidad, mostrará un menú en pantalla con dos opciones: “a” Positivo. “b” Negativo. En el caso de seleccionar la opción “b”, se mostrará un nuevo menú en el que se seleccionará el tipo de producto que no superó el control de calidad: “1” Tipo 1. “2” Tipo 2. Esta operación se repetirá hasta obtener un control positivo. Además, antes de mostrar el menú del control de calidad, indicará mediante un mensaje por pantalla: i) el número de elementos de cada tipo en el almacén; ii) el número total de controles de calidad realizados hasta el momento; y iii) el número de empleados empA que están esperando para introducir cada uno de los dos tipos de productos. 3. Se podrán ejecutar un número indefinido de procesos empA y empB. Cada uno de ellos en una ventana diferente. 4. El número máximo de elementos del almacén N será una constante definida en el monitor. 5. Se implementarán dos procesos encargados de crear y destruir el monitor: crear_fabrica y fin_fabrica. 2.3.3. Ejemplos de ejecución >emp A 1 accediendo al almacen ..... Estado del almacen despues de introducir el producto: Tipo 1: 3 elementos Tipo 2: 0 elementos EmpB esperando tipo 1: 0 EmpB esperando tipo 2: 4 10 CAPÍTULO 2. ENUNCIADO DE LAS PRÁCTICAS >emp A 2 accediendo al almacen ..... Estado del almacen despues de introducir el producto: Tipo 1: 3 elementos Tipo 2: 1 elementos EmpB esperando tipo 1: 0 EmpB esperando tipo 2: 4 >emp A 1 accediendo al almacen ..... Estado del almacen despues de introducir el producto: Tipo 1: 4 elementos Tipo 2: 0 elementos EmpB esperando tipo 1: 1 EmpB esperando tipo 2: 1 >empB accediendo al almacen por los dos productos ..... Estado del almacen despues de retirar los dos productos: Tipo 1: 2 Tipo 2: 0 EmpA esperando tipo 1: 2 EmpA esperando tipo 2: 0 Numero de controles de calidad: 0 Control de Calidad: a. Positivo b. Negativo >b Producto defectuoso: 1. Tipo 1 2. Tipo 2 >1 accediendo al almacen por un producto de tipo 1 ..... Estado del almacen despues de retirar un producto de tipo 1: Tipo 1: 1 Tipo 2: 1 EmpA esperando tipo 1: 0 EmpA esperando tipo 2: 0 Numero de controles de calidad: 3 Control de Calidad: a. Positivo b. Negativo >a Capítulo 3 Introducción al Sistema Operativo Unix El sistema operativo UNIX se desarrolló en los laboratorios Bell desde 1969, y se puede definir como el núcleo (kernel) de un sistema operativo de tiempo compartido, un programa que controla los recursos de una computadora y los asigna entre los usuarios. Permite a los usuarios ejecutar programas, controla los dispositivos periféricos (discos, terminales, impresoras...), y proporciona un sistema de archivos que administra el almacenamiento a largo plazo de información. Por ser un sistema operativo multiusuario, para iniciar una sesión UNIX es necesario identificarse ante el sistema. Cada usuario tiene asignado un nombre de usuario o login y una palabra de paso o passwd. 3.1. Archivos El comando ls muestra los nombres (no el contenido) de los archivos existentes en el directorio actual. > ls file1 file2 > El comando ls, al igual que la mayoría de los comandos, tiene opciones que pueden se utilizadas para alterar su comportamiento. Dichas opciones se teclean a continuación del comando y, en general, están compuestas por una letra precedida del carácter -. Por ejemplo, ls -t permite obtener un listado de los archivos por orden de antigüedad (time), es decir, según la fecha en la que han sido alterados por última vez, empezando por el más reciente. La opción -l (long) permite obtener una información más completa sobre cada uno de los archivos listados (propietario del fichero, protección, etc.) 11 CAPÍTULO 3. INTRODUCCIÓN AL SISTEMA OPERATIVO UNIX 12 El comando cat imprime en pantalla el contenido de todos los archivos referenciados por sus argumentos. El comando cat no es útil para visualizar ficheros largos, ya que, no se detiene si el fichero ocupa más de una pantalla. El comando more, que detiene el listado cada vez que se visualiza una pantalla completa, espera a que el usuario solicite la pantalla siguiente: > more file1 .......... .......... > Muestra la siguiente pantalla. Muestra la siguiente linea. Muestra la pantalla anterior. Aborta el listado. Para cambiar un archivo de nombre se utiliza el comando mv; si lo que se quiere es copiar un archivo, se utiliza el comando cp; y para borrarlo el comando rm. En caso de duda sobre alguno de los comandos o sobre algunas de las funciones en lenguaje C de las librerías del sistema (, , , etc.), se recomienda utilizar el manual (comando man). Por ejemplo, para obtener ayuda acerca del comando cp: > man cp Sobre la ayuda mostrada se avanza con las mismas teclas que en el comando more. Como, en general, puede haber más de una entrada en el manual para un mismo comando o función, se recomienda utilizar la opción -a. De esta manera, se muestran todas las entradas del manual existentes, pasando de una entrada a otra mediante la tecla q: > man -a wait 3.1.1. Directorios El sistema puede distinguir un archivo llamado file1 de otros que tengan el mismo nombre. La distinción se realiza agrupando los archivos en directorios, de manera semejante a como se acomodan los libros en estantes de una biblioteca, por lo que los archivos en distintos directorios pueden tener el mismo nombre sin que se produzca ningún conflicto. Por lo general, cada usuario tiene un directorio personal o directorio de origen, que contiene únicamente los archivos que le pertenecen. Cuando un usuario inicia una sesión UNIX, entra en su directorio de origen. El comando pwd nos indica el directorio actual. Para cambiar de directorio se utiliza el comando cd. Para crear y borrar directorios existen los comandos mkdir y rmdir. 3.2. COMPILACIÓN 3.1.2. 13 Disquetera Para acceder a la disquetera se pueden utilizar las mtools. Para ello, basta con teclear los comandos comunes del MS-DOS con una m delante. Por ejemplo, para listar el contenido de un disquete: mdir. Para copiar todos los archivos a un disquete: mcopy ‘* a:’ . Para cargar todos los archivos de un disquete: mcopy ‘a:*.* .’ 3.2. Compilación El compilador de C de GNU, denominado gcc, es un programa que llama al preprocesador de C, a las diversas pasadas de compilación, y al montaje. Un programa ejemplo.c de un sólo módulo puede compilarse y montarse así: gcc ejemplo.c Lo cual generará un ejecutable a.out. Sin embargo, normalmente se compila con la opción -o ejecutable que puede ir en cualquier orden: gcc ejemplo.c -o ejemplo gcc -o ejemplo ejemplo.c De esta manera, el ejecutable se llamará ejemplo. Si un programa consta de varios módulos, pueden compilarse y montarse todos juntos. Por ejemplo, si ejemplo_bis.c es el módulo principal y lib_1.c y lib_2.c son módulos auxiliares, puede compilarse y montarse así (de nuevo, el orden es irrelevante): gcc lib_1.c lib_2.c ejemplo_bis.c -o ejemplo_bis Sin embargo, a veces interesa compilar por separado los distintos módulos y luego juntar todos los objetos en un ejecutable. El montaje se evita con la opción -c, que genera los ficheros objeto con extensión .o: gcc gcc gcc gcc -c ejemplo_bis.c -c lib_1.c -c lib_2.c lib_1.o lib_2.o ejemplo_bis.o -o ejemplo_bis CAPÍTULO 3. INTRODUCCIÓN AL SISTEMA OPERATIVO UNIX 14 En el caso de que haya que montar bibliotecas ubicadas en lugares normalizados, pero no incluidas automáticamente por el montador, hay que montarlas con la opción -l. Por ejemplo, una compilación y montaje utilizando la librería matemática se realizaría del siguiente modo: gcc -lm ejemplo.c -o ejemplo Finalmente, conviene compilar todos los programas con la opción -Wall para que nos muestre toda la información disponible warnings de la compilación: gcc -Wall ejemplo.c -o ejemplo 3.3. Depuración Para poder depurar un programa hay que indicarle al compilador que almacene información simbólica para el depurador. En el compilador gcc hay que compilar con la opción -g: gcc -g ejemplo.c -o ejemplo El depurador simbólico en tiempo real permite examinar la ejecución de un programa para descubrir las causas de errores observados en las pruebas formales y para hacer pruebas informales. El depurador básico se llama gdb y se invoca de la siguiente manera: gdb programa Para familiarizarse con su uso se dispone de una ayuda que puede obtenerse pulsando el mensaje help y siguiendo las instrucciones. Todas las órdenes del depurador se pueden abreviar hasta el mínimo que no sea ambiguo. La forma más elemental de usar el depurador es la siguiente: 1. Se lanza el depurador como se indicó anteriormente. 2. Si los ficheros fuentes no están en el mismo directorio que el ejecutable, se informa al depurador dónde están con: dir directorio. 3. Se pone un punto de parada en el main con: br main. 4. Se lanza la ejecución del programa con run seguido de los parámetros del programa y las posibles redirecciones de sus entradas y salidas. El programa se parará en el main mostrando sus parámetros. 5. Se ejecuta el programa paso a paso con n o s, dependiendo de si queremos entrar en los procedimientos o no. 3.4. LLAMADAS AL SISTEMA 15 6. Si se pulsa el retorno de carro, se repite la última orden dada. Así no hay que repetir los n o los s para ir paso a paso. 7. Si el método resulta tedioso, se puede poner un punto de parada en otro sitio con br, indicando el nombre de la rutina o el número de línea. Se continúa hasta dicho punto con cont. 8. Puede forzarse a que el programa aborte la ejecución con CTRL-C. 9. Puede saberse en qué punto estamos del programa con where. 10. En todo momento se pueden examinar variables o parte de ellas con p expresión, donde la expresión puede ser cualquier expresión válida en C. También se pueden modificar con set. 11. Podemos movernos por la pila de activaciones de rutinas con up y down. 12. Puede examinarse el texto fuente con l (diez líneas más), l línea o l línea, línea. 13. Puede cambiarse el módulo fuente que estamos examinado con setfile. También puede hacerse referencia a líneas de ficheros distintos del actual anteponiendo el nombre del fichero y “:” al número de línea (por ejemplo, br lib_1.c:21 o l lib_2.c:17). 14. Pulse q para finalizar. Si se desea utilizar un interfaz gráfico para el depurador ejecute /local/bin/gvd. 3.4. LLamadas al sistema Para ejecutar un comando del sistema operativo desde un programa en C se utiliza la función system(): SYNOPSIS #include int system (const char * string); DESCRIPCION system ejecuta el comando que recibe como parámetro como si éste se hubiera tecleado desde el terminal. VALORES RETORNADOS CAPÍTULO 3. INTRODUCCIÓN AL SISTEMA OPERATIVO UNIX 16 Retorna -1 en caso de error. EJEMPLO La siguiente instrucción ejecuta el comando ls y su salida la envía a un fichero llamado “salida_ls”. De esta manera, podríamos abrir dicho fichero y leer desde el programa en C, la salida del comando ejecutado. system(‘‘ls > salida_ls’’); Sin embargo, existe un inconveniente: la creación de ficheros temporales como el fichero “salida_ls”. Para evitar esto se utilizan pipes, que nos permiten establecer un flujo directo de información entre dos procesos, sin tener que pasar previamente por un fichero temporal. SYNOPSIS #include FILE *popen(const char *command, const char *type); int pclose(FILE *stream); DESCRIPCION popen() ejecuta el comando que recibe como parámetro, al igual que lo hacía system, pero además establece un pipe de comunicación entre el comando ejecutado y el proceso que ha ejecutado el popen(). Dicho pipe se establece igual que un fichero, sobre el que se pueden realizar operaciones de lectura y/o escritura. VALORES RETORNADOS Retorna NULL en caso de error, o un descriptor de fichero en caso contrario. EJEMPLO A continuación se muestra un ejemplo en el que se obtiene el PID del proceso llamado init: #include #include int main() { 3.4. LLAMADAS AL SISTEMA FILE *fich = NULL; int pid = -1; if ((fich = popen("ps -ax | grep -v grep | grep \" init \" ", "r")) == NULL) { printf("Error al ejecutar el ps\n"); exit(1); } fscanf(fich, "%d", &pid); pclose(fich); if (pid == -1) printf("No existe el proceso init\n"); else printf("El PID del proceso init es: %d\n", pid); return(0); } 17 18 CAPÍTULO 3. INTRODUCCIÓN AL SISTEMA OPERATIVO UNIX Capítulo 4 Gestión de Procesos 4.1. Definición de Proceso Un programa es una secuencia de instrucciones escrita en un lenguaje de programación. Un proceso es una instancia de ejecución de un programa. Un prog

39 downloads 68 Views 192KB Size

Story Transcript

Manual de Prácticas de Sistemas Operativos

Curso 2004-2005

Índice general 1. Evaluación y Calendario de Prácticas

1

2. Enunciado de las Prácticas 2.1. Práctica 1 . . . . . . . . . . . . . . 2.1.1. Objetivos . . . . . . . . . . 2.1.2. Enunciado . . . . . . . . . . 2.2. Práctica 2 . . . . . . . . . . . . . . 2.2.1. Objetivos . . . . . . . . . . 2.2.2. Enunciado . . . . . . . . . . Proceso santa (“santa.c”) . . Proceso reno (“reno.c”) . . . Proceso duende (“duende.c”) 2.3. Práctica 3 . . . . . . . . . . . . . . 2.3.1. Objetivos . . . . . . . . . . 2.3.2. Enunciado . . . . . . . . . . 2.3.3. Ejemplos de ejecución . . .

. . . . . . . . . . . . .

3 3 3 3 5 5 5 6 6 7 8 8 8 9

. . . . . .

11 11 12 13 13 14 15

. . . . .

19 19 19 20 21 23

3. Introducción al Sistema Operativo 3.1. Archivos . . . . . . . . . . . . . . 3.1.1. Directorios . . . . . . . . . 3.1.2. Disquetera . . . . . . . . . 3.2. Compilación . . . . . . . . . . . . 3.3. Depuración . . . . . . . . . . . . 3.4. LLamadas al sistema . . . . . . . 4. Gestión de Procesos 4.1. Definición de Proceso . . . . . 4.2. Estados de un Proceso . . . . 4.3. Identificación de Procesos . . 4.4. Creación de Procesos: fork() . 4.5. Ejecución de Procesos: execl()

. . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

Unix . . . . . . . . . . . . . . . . . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

5. Utilización de Semáforos en el Laboratorio i

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

. . . . . . . . . . . . .

. . . . . .

. . . . .

25

ii

ÍNDICE GENERAL

6. Utilización de monitores en el laboratorio 29 6.1. Monitor ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 6.2. Monitores con paso de parámetros . . . . . . . . . . . . . . . . . . . . . . . 36

Capítulo 1 Evaluación y Calendario de Prácticas Para aprobar la parte práctica de la asignatura, es necesario entregar las prácticas en la fecha indicada, que dichas prácticas funcionen y responder correctamente a algunas preguntas sobre las mismas. La evaluación de las prácticas será individual, y por tanto, deberán haber sido realizadas por los dos miembros de cada grupo. La corrección de las prácticas tendrá lugar en el laboratorio y se hará delante de cada grupo. Sólo los alumnos que superen la parte práctica podrán optar a realizar el examen de la parte de teoría. Una vez que un alumno supera la parte práctica, queda liberado de la misma en las siguientes convocatorias. En las siguientes tablas se indica: el día de inicio de cada práctica, en el que se explicará (al inicio de cada turno) el contenido de la misma; el día límite de entrega (al finalizar el turno correspondiente); el día de corrección (en las horas correspondientes a cada turno).

Turno Lunes Martes Miércoles Jueves

Turno Lunes Martes Miércoles Jueves

Inicio 14 Marzo 15 Marzo 16 Marzo 17 Marzo

Práctica 1 Entrega Corrección 4 Abril 11 Abril 29 Marzo 5 Abril 30 Marzo 6 Abril 31 Marzo 7 Abril

Inicio 11 Abril 5 Abril 6 Abril 7 Abril

Práctica 2 Entrega Corrección 25 Abril 2 Mayo 19 Abril 26 Abril 20 Abril 27 Abril 21 Abril 28 Abril 1

2

CAPÍTULO 1. EVALUACIÓN Y CALENDARIO DE PRÁCTICAS Turno Lunes Martes Miércoles Jueves

Inicio 2 Mayo 26 Abril 27 Abril 28 Abril

Práctica 3 Entrega Corrección 30 Mayo 6 Junio 24 Mayo 31 Mayo 25 Mayo 1 Junio 26 Mayo 2 Junio

Entrega de prácticas: Cada grupo soXX deberá dejar, en el directorio /home/clave/labs/so/practicaN /soXX/ los ficheros fuentes correspondientes a la prácticaN al finalizar el turno correspondiente a dicho grupo, tal y como se indica en las tablas anteriores. Dichos directorios ya están creados (no son subdirectorios dentro de la cuenta de cada usuario), y se bloquearán una vez finalice el plazo de presentación de cada práctica. Si se detectan prácticas copiadas, todos los miembros de las parejas involucradas suspenderán la asignatura.

Capítulo 2 Enunciado de las Prácticas 2.1.

Práctica 1

2.1.1.

Objetivos

Entender el funcionamiento de las llamadas al sistema relacionadas con la creación de procesos (fork, execl, wait). Se recomienda leer la parte del manual relativa a estas llamadas y realizar los programas de ejemplo que se incluyen (ver el capítulo 4 del manual). Se recomienda también leer el manual on-line de dichas llamadas al sistema (man fork, man execl, man -a wait (o man 2 wait)). Implementar un grafo de precedencia sincronizando la ejecución concurrente de varios procesos.

2.1.2.

Enunciado

(a) Implementar el grafo de precedencia de la figura 2.1 utilizando las llamadas al sistema fork y wait (no utilizar waitpid). El grupo de sentencias a ejecutar en cada nodo del grafo se simularán mediante la sentencia printf(“cadena”), donde “cadena” es la cadena de caracteres que contiene cada nodo de la figura. La frase deberá aparecer en una única línea. (b) Repetir el apartado anterior utilizando un proceso auxiliar imprime_pantalla cuyo código será ejecutado por los procesos hijo mediante la llamada al sistema execl. Dicho proceso deberá imprimir en pantalla, mediante la sentencia printf(“cadena”), la(s) cadena(s) de caracteres que reciba como argumento(s). Los resultados obtenidos (implementando correctamente la precedencia del grafo de la figura) en los dos apartados no deben ser los esperados. En el primer apartado se deben obtener palabras repetidas, mientras que en el segundo éstas deben verse en un orden no permitido por el grafo pero sin repetirse. Ambas situaciones son debidas a cómo funcionan las llamadas al sistema fork y execl. El objetivo es detectar la causa de estas dos anomalías y entender su solución.

3

CAPÍTULO 2. ENUNCIADO DE LAS PRÁCTICAS

4

Hola

Buenos

dias

tenga

y

usted.

Hasta

luego

malos

Lucas Figura 2.1: Grafo de Precedencia.

2.2. PRÁCTICA 2

2.2.

Práctica 2

2.2.1.

Objetivos

5

Utilizar los semáforos como una herramienta de sincronización para acceder en exclusión mutua a un recurso compartido. Para ello, se proporciona una librería que permite la utilización de semáforos de una manera sencilla y similar a la explicada en teoría (ver detalladamente el capítulo 5 del manual). Utilizar semáforos genéricos para controlar el acceso de N procesos a un recurso. Utilizar el sistema de ficheros como un recurso compartido entre varios procesos, entendiendo el funcionamiento de las llamadas al sistema fopen, fprintf, fscanf y fclose.

2.2.2.

Enunciado

Implementar, mediante la utilización de semáforos el problema clásico de Santa Claus, que se describe como sigue. Santa Claus duerme en su taller del polo norte y sólo puede despertarse en dos circunstancias: Cuando los 4 renos vuelven de sus vacaciones. El último reno en llegar debe despertar a Santa Claus. Mientras, los otros 3 renos deben esperar en un refugio a que llegue el cuarto reno y Santa Claus esté listo para empezar el reparto de juguetes. Cuando algún duende tiene dificultades para hacer juguetes. Para permitir que Santa Claus duerma, los duendes sólo pueden despertarle cuando son tres los que tienen problemas. Además, cuando tres duendes han ido a plantear sus problemas a Santa Claus, cualquier otro duende que desee visitar a Santa Claus debe esperar a que éstos regresen. Si Santa Claus despierta y encuentra tres duendes en la puerta de su taller, junto con el último reno que vuelve de vacaciones, Santa Claus decide que los duendes pueden esperar hasta después de Navidad, y se va a repartir juguetes. En la solución se debe proporcionar el código de los tres tipos de procesos involucrados santa, reno y duende; así como el código de dos dos procesos encargados de crear y destruir los semáforos necesarios para la sincronización: inic_santa y fin_santa. Además, se utilizarán los ficheros “duendes.txt” y “renos.txt” para almacenar la información mínima necesaria para la resolución del problema, teniendo en cuenta que: i) el acceso al fichero “duendes.txt” es en lectura para santa; y en lectura/escritura para los procesos duende (los procesos reno no pueden acceder); ii) el acceso al fichero “renos.txt” es en lectura para santa; y en lectura/escritura para los procesos reno (los procesos duende no pueden acceder). En la resolución de la práctica se supondrá la existencia de un único proceso santa, un número no determinado de procesos duende y exactamente 4 procesos reno. Cada proceso se ejecutará en una ventana diferente. A continuación, se describe el funcionamiento de cada uno de los tres tipos de procesos.

6

CAPÍTULO 2. ENUNCIADO DE LAS PRÁCTICAS

Proceso santa (“santa.c”) El proceso santa debe esperar descansando a que los 4 renos hayan regresado de sus vacaciones o 3 duendes tengan problemas para hacer juguetes. A continuación, dependiendo del caso, o bien se va a repartir juguetes o ayuda a los duendes a resolver sus problemas. Se repite este comportamiento hasta que se seleccione una opción para finalizar el proceso santa. A continuación, se muestran dos ejemplos de ejecución del proceso santa, indicando los mensajes que se deben mostrar en pantalla. > santa Durmiendo ..... Voy a repartir juguetes (pulsa ENTER o 1 para finalizar) Durmiendo ..... >santa Durmiendo ..... Ayudo a duende Ayudo a duende Ayudo a duende (pulsa ENTER o 1 para finalizar) Durmiendo ..... Proceso reno (“reno.c”) Los procesos reno deben esperar en el refugio hasta que el último reno haya regresado de sus vacaciones. Cuando Santa Claus decide ir a repartir juguetes se lleva a los renos; una vez finalizado el trabajo, los renos se van otra vez de vacaciones. Se repite este comportamiento hasta que se seleccione una opción para finalizar el proceso reno. A continuación, se muestran dos ejemplos de ejecución de un proceso reno, indicando los mensajes que se deben mostrar en pantalla. > reno Estoy de vacaciones (pulsa ENTER o 1 para finalizar) Espero en el refugio a los demás renos ..... Voy a repartir juguetes .... Estoy de vacaciones (pulsa ENTER o 1 para finalizar) > reno Estoy de vacaciones (pulsa ENTER o 1 para finalizar) Voy a repartir juguetes .... Estoy de vacaciones (pulsa ENTER o 1 para finalizar)

2.2. PRÁCTICA 2

7

Proceso duende (“duende.c”) Los procesos duende hacen continuamente juguetes hasta que tienen un problema. En ese momento se dirigen al taller de Santa Claus a pedir ayuda. Sin embargo, si ya han ido tres duendes a pedir ayuda, el duende con problemas debe esperar a que regresen. Una vez que un duende con problemas ha recibido ayuda, se repite el comportamiento anterior hasta que se seleccione una opción para finalizar el proceso duende. A continuación, se muestran dos ejemplos de ejecución de un proceso duende, indicando los mensajes que se deben mostrar en pantalla. > duende Haciendo juguetes (pulsa ENTER si tienes problemas o 1 para finalizar) Voy en busca de ayuda .... Resuelto el problema Haciendo juguetes (pulsa ENTER si tienes problemas o 1 para finalizar) > duende Haciendo juguetes (pulsa ENTER si tienes problemas o 1 para finalizar) Voy en busca de ayuda .... Soy el tercer duende y despierto a Santa Claus Resuelto el problema Haciendo juguetes (pulsa ENTER si tienes problemas o 1 para finalizar)

CAPÍTULO 2. ENUNCIADO DE LAS PRÁCTICAS

8

2.3.

Práctica 3

2.3.1.

Objetivos

Utilizar una herramienta de sincronización de alto nivel como son los monitores condicionales. Para ello, se proporciona una librería que permite la utilización de monitores de una manera sencilla y similar a la explicada en teoría (ver detalladamente el capítulo 6 del manual). Además, en el manual se incluyen dos monitores de ejemplo cuya implementación y prueba se recomienda antes de abordar esta práctica. Entender las diferencias existentes entre los dos tipos de monitores vistos en teoría, en función del proceso elegido para continuar la ejecución cuando se despierta un proceso suspendido en una variable condition.

2.3.2.

Enunciado

En este problema se deberá simular mediante la utilización de un monitor fabrica la gestión de una fabrica en la que existen empleados de dos categorías: 1. Categoría A: son los empleados encargados de la fabricación de cada uno de los productos. Dado que la fábrica dispone de 2 tipos de productos, un grupo de empleados de la categoría A fabrica productos del tipo 1, y otro fabrica productos del tipo 2. Cada empleado fabrica ejemplares del producto correspondiente de uno en uno. Una vez fabricado un ejemplar lo lleva a un almacén con capacidad para N productos, donde se almacenan todos los productos de la fábrica (tanto los de tipo 1 como los de tipo 2). Los empleados de categoría A acceden al almacen en orden FIFO. Dicho orden FIFO sólo puede romperse para garantizar que no exista interbloqueo. 2. Categoría B: son los empleados encargados de realizar el control de calidad de los productos fabricados que han sido depositados en el almacén. Esta tarea se lleva a cabo sobre un puesto de trabajo individual para cada empleado, donde el control de calidad sólo se puede realizar utilizando simultáneamente un producto de tipo 1 y uno de tipo 2. El resultado de dicho control de calidad puede ser: a) Positivo: en ese caso los dos productos pasan a la red de distribución de ventas. b) Negativo: este resultado se obtiene cuando uno de los dos productos (el de tipo 1 o el de tipo 2) no supera el control de calidad, por lo que dicho producto se desecha. En ese caso, es necesario que dicho empleado retire un nuevo producto del tipo desechado, y realice un nuevo control de calidad. Observe que, en el caso de que ambos productos fuesen defectuosos, el control de calidad sólo detecta uno de ellos, mientras que el otro será detectado en futuros controles —suponga que no es posible que se generen productos defectuosos de manera indefinida.

2.3. PRÁCTICA 3

9

Los empleados de categoría B acceden al almacén en orden FIFO. Dicho orden sólo puede romperse si no es posible realizar ningún control de calidad manteniendo el FIFO, pero sí rompiendo dicho orden FIFO. Es decir, el objetivo es realizar el mayor número de controles de calidad posibles. Además, se tendrán en cuenta las siguientes consideraciones: 1. Se implementará un proceso denominado empA, que se ejecutará con un parámetro en la línea de comandos: empA tipo. El parámetro tipo consistirá en un número indicando el tipo de producto producido (1 ó 2). Dicho proceso, una vez depositado el producto en el almacén, indicará mediante un mensaje por pantalla: i) el número de elementos de cada tipo en el almacén; y ii) el número de empleados empB que están esperando por cada tipo de producto —si hay un empB esperando por los dos tipos de productos, éste se contabilizará como un empB esperando por el tipo 1, y un empB esperando por el tipo 2. 2. Se implementará un proceso denominado empB que, una vez retirados los dos productos necesarios para realizar el control de calidad, mostrará un menú en pantalla con dos opciones: “a” Positivo. “b” Negativo. En el caso de seleccionar la opción “b”, se mostrará un nuevo menú en el que se seleccionará el tipo de producto que no superó el control de calidad: “1” Tipo 1. “2” Tipo 2. Esta operación se repetirá hasta obtener un control positivo. Además, antes de mostrar el menú del control de calidad, indicará mediante un mensaje por pantalla: i) el número de elementos de cada tipo en el almacén; ii) el número total de controles de calidad realizados hasta el momento; y iii) el número de empleados empA que están esperando para introducir cada uno de los dos tipos de productos. 3. Se podrán ejecutar un número indefinido de procesos empA y empB. Cada uno de ellos en una ventana diferente. 4. El número máximo de elementos del almacén N será una constante definida en el monitor. 5. Se implementarán dos procesos encargados de crear y destruir el monitor: crear_fabrica y fin_fabrica.

2.3.3.

Ejemplos de ejecución

>emp A 1 accediendo al almacen ..... Estado del almacen despues de introducir el producto: Tipo 1: 3 elementos Tipo 2: 0 elementos EmpB esperando tipo 1: 0 EmpB esperando tipo 2: 4

10

CAPÍTULO 2. ENUNCIADO DE LAS PRÁCTICAS

>emp A 2 accediendo al almacen ..... Estado del almacen despues de introducir el producto: Tipo 1: 3 elementos Tipo 2: 1 elementos EmpB esperando tipo 1: 0 EmpB esperando tipo 2: 4 >emp A 1 accediendo al almacen ..... Estado del almacen despues de introducir el producto: Tipo 1: 4 elementos Tipo 2: 0 elementos EmpB esperando tipo 1: 1 EmpB esperando tipo 2: 1 >empB accediendo al almacen por los dos productos ..... Estado del almacen despues de retirar los dos productos: Tipo 1: 2 Tipo 2: 0 EmpA esperando tipo 1: 2 EmpA esperando tipo 2: 0 Numero de controles de calidad: 0 Control de Calidad: a. Positivo b. Negativo >b Producto defectuoso: 1. Tipo 1 2. Tipo 2 >1 accediendo al almacen por un producto de tipo 1 ..... Estado del almacen despues de retirar un producto de tipo 1: Tipo 1: 1 Tipo 2: 1 EmpA esperando tipo 1: 0 EmpA esperando tipo 2: 0 Numero de controles de calidad: 3 Control de Calidad: a. Positivo b. Negativo >a

Capítulo 3 Introducción al Sistema Operativo Unix El sistema operativo UNIX se desarrolló en los laboratorios Bell desde 1969, y se puede definir como el núcleo (kernel) de un sistema operativo de tiempo compartido, un programa que controla los recursos de una computadora y los asigna entre los usuarios. Permite a los usuarios ejecutar programas, controla los dispositivos periféricos (discos, terminales, impresoras...), y proporciona un sistema de archivos que administra el almacenamiento a largo plazo de información. Por ser un sistema operativo multiusuario, para iniciar una sesión UNIX es necesario identificarse ante el sistema. Cada usuario tiene asignado un nombre de usuario o login y una palabra de paso o passwd.

3.1.

Archivos

El comando ls muestra los nombres (no el contenido) de los archivos existentes en el directorio actual.

> ls file1 file2 > El comando ls, al igual que la mayoría de los comandos, tiene opciones que pueden se utilizadas para alterar su comportamiento. Dichas opciones se teclean a continuación del comando y, en general, están compuestas por una letra precedida del carácter -. Por ejemplo, ls -t permite obtener un listado de los archivos por orden de antigüedad (time), es decir, según la fecha en la que han sido alterados por última vez, empezando por el más reciente. La opción -l (long) permite obtener una información más completa sobre cada uno de los archivos listados (propietario del fichero, protección, etc.) 11

CAPÍTULO 3. INTRODUCCIÓN AL SISTEMA OPERATIVO UNIX

12

El comando cat imprime en pantalla el contenido de todos los archivos referenciados por sus argumentos. El comando cat no es útil para visualizar ficheros largos, ya que, no se detiene si el fichero ocupa más de una pantalla. El comando more, que detiene el listado cada vez que se visualiza una pantalla completa, espera a que el usuario solicite la pantalla siguiente: > more file1 .......... .......... >

Muestra la siguiente pantalla. Muestra la siguiente linea. Muestra la pantalla anterior. Aborta el listado.

Para cambiar un archivo de nombre se utiliza el comando mv; si lo que se quiere es copiar un archivo, se utiliza el comando cp; y para borrarlo el comando rm. En caso de duda sobre alguno de los comandos o sobre algunas de las funciones en lenguaje C de las librerías del sistema (, , , etc.), se recomienda utilizar el manual (comando man). Por ejemplo, para obtener ayuda acerca del comando cp: > man cp Sobre la ayuda mostrada se avanza con las mismas teclas que en el comando more. Como, en general, puede haber más de una entrada en el manual para un mismo comando o función, se recomienda utilizar la opción -a. De esta manera, se muestran todas las entradas del manual existentes, pasando de una entrada a otra mediante la tecla q: > man -a wait

3.1.1.

Directorios

El sistema puede distinguir un archivo llamado file1 de otros que tengan el mismo nombre. La distinción se realiza agrupando los archivos en directorios, de manera semejante a como se acomodan los libros en estantes de una biblioteca, por lo que los archivos en distintos directorios pueden tener el mismo nombre sin que se produzca ningún conflicto. Por lo general, cada usuario tiene un directorio personal o directorio de origen, que contiene únicamente los archivos que le pertenecen. Cuando un usuario inicia una sesión UNIX, entra en su directorio de origen. El comando pwd nos indica el directorio actual. Para cambiar de directorio se utiliza el comando cd. Para crear y borrar directorios existen los comandos mkdir y rmdir.

3.2. COMPILACIÓN

3.1.2.

13

Disquetera

Para acceder a la disquetera se pueden utilizar las mtools. Para ello, basta con teclear los comandos comunes del MS-DOS con una m delante. Por ejemplo, para listar el contenido de un disquete: mdir. Para copiar todos los archivos a un disquete: mcopy ‘* a:’ . Para cargar todos los archivos de un disquete: mcopy ‘a:*.* .’

3.2.

Compilación

El compilador de C de GNU, denominado gcc, es un programa que llama al preprocesador de C, a las diversas pasadas de compilación, y al montaje. Un programa ejemplo.c de un sólo módulo puede compilarse y montarse así: gcc ejemplo.c Lo cual generará un ejecutable a.out. Sin embargo, normalmente se compila con la opción -o ejecutable que puede ir en cualquier orden: gcc ejemplo.c -o ejemplo gcc -o ejemplo ejemplo.c De esta manera, el ejecutable se llamará ejemplo. Si un programa consta de varios módulos, pueden compilarse y montarse todos juntos. Por ejemplo, si ejemplo_bis.c es el módulo principal y lib_1.c y lib_2.c son módulos auxiliares, puede compilarse y montarse así (de nuevo, el orden es irrelevante): gcc lib_1.c lib_2.c ejemplo_bis.c -o ejemplo_bis Sin embargo, a veces interesa compilar por separado los distintos módulos y luego juntar todos los objetos en un ejecutable. El montaje se evita con la opción -c, que genera los ficheros objeto con extensión .o: gcc gcc gcc gcc

-c ejemplo_bis.c -c lib_1.c -c lib_2.c lib_1.o lib_2.o ejemplo_bis.o -o ejemplo_bis

CAPÍTULO 3. INTRODUCCIÓN AL SISTEMA OPERATIVO UNIX

14

En el caso de que haya que montar bibliotecas ubicadas en lugares normalizados, pero no incluidas automáticamente por el montador, hay que montarlas con la opción -l. Por ejemplo, una compilación y montaje utilizando la librería matemática se realizaría del siguiente modo: gcc -lm ejemplo.c -o ejemplo Finalmente, conviene compilar todos los programas con la opción -Wall para que nos muestre toda la información disponible warnings de la compilación: gcc -Wall ejemplo.c -o ejemplo

3.3.

Depuración

Para poder depurar un programa hay que indicarle al compilador que almacene información simbólica para el depurador. En el compilador gcc hay que compilar con la opción -g: gcc -g ejemplo.c -o ejemplo El depurador simbólico en tiempo real permite examinar la ejecución de un programa para descubrir las causas de errores observados en las pruebas formales y para hacer pruebas informales. El depurador básico se llama gdb y se invoca de la siguiente manera: gdb programa Para familiarizarse con su uso se dispone de una ayuda que puede obtenerse pulsando el mensaje help y siguiendo las instrucciones. Todas las órdenes del depurador se pueden abreviar hasta el mínimo que no sea ambiguo. La forma más elemental de usar el depurador es la siguiente: 1. Se lanza el depurador como se indicó anteriormente. 2. Si los ficheros fuentes no están en el mismo directorio que el ejecutable, se informa al depurador dónde están con: dir directorio. 3. Se pone un punto de parada en el main con: br main. 4. Se lanza la ejecución del programa con run seguido de los parámetros del programa y las posibles redirecciones de sus entradas y salidas. El programa se parará en el main mostrando sus parámetros. 5. Se ejecuta el programa paso a paso con n o s, dependiendo de si queremos entrar en los procedimientos o no.

3.4. LLAMADAS AL SISTEMA

15

6. Si se pulsa el retorno de carro, se repite la última orden dada. Así no hay que repetir los n o los s para ir paso a paso. 7. Si el método resulta tedioso, se puede poner un punto de parada en otro sitio con br, indicando el nombre de la rutina o el número de línea. Se continúa hasta dicho punto con cont. 8. Puede forzarse a que el programa aborte la ejecución con CTRL-C. 9. Puede saberse en qué punto estamos del programa con where. 10. En todo momento se pueden examinar variables o parte de ellas con p expresión, donde la expresión puede ser cualquier expresión válida en C. También se pueden modificar con set. 11. Podemos movernos por la pila de activaciones de rutinas con up y down. 12. Puede examinarse el texto fuente con l (diez líneas más), l línea o l línea, línea. 13. Puede cambiarse el módulo fuente que estamos examinado con setfile. También puede hacerse referencia a líneas de ficheros distintos del actual anteponiendo el nombre del fichero y “:” al número de línea (por ejemplo, br lib_1.c:21 o l lib_2.c:17). 14. Pulse q para finalizar. Si se desea utilizar un interfaz gráfico para el depurador ejecute /local/bin/gvd.

3.4.

LLamadas al sistema

Para ejecutar un comando del sistema operativo desde un programa en C se utiliza la función system():

SYNOPSIS #include int system (const char * string); DESCRIPCION system ejecuta el comando que recibe como parámetro como si éste se hubiera tecleado desde el terminal.

VALORES RETORNADOS

CAPÍTULO 3. INTRODUCCIÓN AL SISTEMA OPERATIVO UNIX

16

Retorna -1 en caso de error. EJEMPLO La siguiente instrucción ejecuta el comando ls y su salida la envía a un fichero llamado “salida_ls”. De esta manera, podríamos abrir dicho fichero y leer desde el programa en C, la salida del comando ejecutado. system(‘‘ls > salida_ls’’); Sin embargo, existe un inconveniente: la creación de ficheros temporales como el fichero “salida_ls”. Para evitar esto se utilizan pipes, que nos permiten establecer un flujo directo de información entre dos procesos, sin tener que pasar previamente por un fichero temporal. SYNOPSIS #include FILE *popen(const char *command, const char *type); int pclose(FILE *stream); DESCRIPCION popen() ejecuta el comando que recibe como parámetro, al igual que lo hacía system, pero además establece un pipe de comunicación entre el comando ejecutado y el proceso que ha ejecutado el popen(). Dicho pipe se establece igual que un fichero, sobre el que se pueden realizar operaciones de lectura y/o escritura. VALORES RETORNADOS Retorna NULL en caso de error, o un descriptor de fichero en caso contrario. EJEMPLO A continuación se muestra un ejemplo en el que se obtiene el PID del proceso llamado init: #include #include int main() {

3.4. LLAMADAS AL SISTEMA FILE *fich = NULL; int pid = -1; if ((fich = popen("ps -ax | grep -v grep | grep \" init \" ", "r")) == NULL) { printf("Error al ejecutar el ps\n"); exit(1); } fscanf(fich, "%d", &pid); pclose(fich); if (pid == -1) printf("No existe el proceso init\n"); else printf("El PID del proceso init es: %d\n", pid); return(0); }

17

18

CAPÍTULO 3. INTRODUCCIÓN AL SISTEMA OPERATIVO UNIX

Capítulo 4 Gestión de Procesos 4.1.

Definición de Proceso

Un programa es una secuencia de instrucciones escrita en un lenguaje de programación. Un proceso es una instancia de ejecución de un programa. Un programa es un concepto estático, mientras que un proceso es un concepto dinámico. En un entorno multiusuario, es posible que varios usuarios ejecuten el mismo programa, obteniendo un proceso distinto por cada ejecución.

4.2.

Estados de un Proceso

En un entorno multitarea coexisten varios procesos que se ejecutan de manera entrelazada (un único procesador). Sin embargo, algunos procesos se encuentran esperando eventos, por ejemplo esperando a que el usuario pulse una tecla, dicha espera se debe realizar de manera que se perjudique lo menos posible al resto de procesos, con los que se comparte la CPU. Si se realiza espera activa, cada vez que el sistema operativo le asigne la CPU a dicho proceso, éste “mirará” el teclado a ver si se ha pulsado una tecla, y esto lo repetirá hasta que dicho evento se produzca. Durante todo ese tiempo, el proceso está consumiendo innecesariamente CPU. Para evitar la espera activa, los procesos se suspenden a espera de eventos, de manera que dichos procesos no entren en el reparto de la CPU. A continuación se enumeran los distintos estados en los que se puede encontrar un proceso: Preparados (R): Conjunto de procesos que pueden ejecutarse en este momento, es decir, disponen de todos los recursos necesarios para poder ejecutarse, salvo la CPU. El sistema operativo les irá asignando la CPU a cada uno de ellos. Ejecutando (O): El proceso ocupa actualmente la CPU: Sólo uno de los procesos preparados se estará ejecutando en cada momento (monoprocesador). 19

CAPÍTULO 4. GESTIÓN DE PROCESOS

20

Suspendidos (S): A dichos procesos les falta, además de la CPU, algún recurso para poder ejecutarse, entendiéndose por recurso un dispositivo, un dato, etc. Los procesos suspendidos están esperando a que ocurra algún evento para poder acceder al recurso que necesitan. Estos procesos no entran en el reparto de la CPU, evitando así la espera activa. Cuando se produce el evento esperado, dicho proceso pasará a estar preparado. Parados (T): Son procesos que tampoco entran en el reparto de la CPU, pero que no están suspendidos a la espera de eventos, sino que han sido parados en su ejecución. Para salir de dicho estado hay que mandarles continuar, volviendo así a estar preparados. Zombies (Z): Cuando un proceso finaliza, se lo comunica a su proceso padre (el proceso que lo creó). Si dicho proceso no captura el aviso de su proceso hijo, éste queda en un estado “zombies”. En dicho estado, el proceso no consume CPU pero sigue ocupando recursos en la tabla de procesos (donde se guarda información de cada uno de los procesos existentes en el sistema). Un proceso permanece “zombi” hasta que su proceso padre captura su aviso.

PARADO

COLA DE PROCESOS PREPARADOS

EJECUTANDO

ZOMBI

SUSPENDIDO

Figura 4.1: Estados de un Proceso

4.3.

Identificación de Procesos

Cada proceso se identifica mediante su PID (identificador de proceso), que es un número entero (mayor que cero) que el sistema va asignando a cada proceso cuando éste se crea. El sistema operativo unix proporciona un comando que nos da información relativa a cada uno de los procesos que existen en el sistema. Para obtener todos los procesos

4.4. CREACIÓN DE PROCESOS: FORK()

21

correspondientes a un usuario usr1: ps -aux | grep usr1. Se recomienda ver el manual (man ps). USER: El propietario del proceso. PID: El identificador del proceso. % CPU: Porcentaje de CPU consumida. % MEM: Porcentaje de memoria consumida. SIZE: Tamaño total del proceso (Kilobytes). RSS: Kilobytes del programa en memoria. (El resto estará en disco (swapp)). TTY: Identificador del terminal desde donde se lanzó el proceso. STAT: Estado del proceso. START: Hora en la que empezó o la que se lanzó el proceso. TIME: Tiempo de CPU consumido. COMMAND: Nombre del proceso.

4.4.

Creación de Procesos: fork()

Los procesos pueden tener una estructura jerárquica, de manera que un proceso (proceso padre) puede crear un nuevo proceso (proceso hijo) y así sucesivamente. Para la realización de aplicaciones con varios procesos, el sistema operativo unix proporciona la llamada al sistema1 fork().

SYNOPSIS #include #include int fork(void); DESCRIPCION fork() crea un nuevo proceso exactamente igual (mismo código) al proceso que invoca la función. Ambos procesos continúan su ejecución tras la llamada al fork(). 1 Funciones que se pueden invocar desde un programa en C, y que realizan una llamada al sistema operativo.

CAPÍTULO 4. GESTIÓN DE PROCESOS

22

VALORES RETORNADOS En caso de error retorna -1 y no se crea el proceso hijo. En otro caso, retorna valores diferentes al proceso padre (el que lo invocó) y al proceso hijo (el proceso creado): Proceso Padre: Retorna el PID del proceso hijo. Proceso Hijo: Retorna 0. EJEMPLO En el ejemplo que se muestra a continuación, se crea un proceso hijo que imprime en pantalla el PID de su proceso padre, mientras que el proceso padre imprime en pantalla su propio PID y el del proceso hijo que ha creado. Para ello, se utilizan las llamadas al sistema getpid() y getppid(). El proceso padre, antes de finalizar se suspende hasta que el hijo muere, para evitar que éste se quede zombi. Para ello, utiliza la llamada al sistema wait(), que recibe en la variable status el estado en que el proceso hijo finalizó. #include #include #include #include #include



int main() { int pid = 0, status = 0, pid_hijo_finalizado = 0; if ((pid = fork()) == -1) { printf(‘‘Error al crear proceso hijo\n’’); exit(1); } if (pid == 0) { /* Proceso Hijo */ printf(‘‘El PID de mi proceso padre es %d\n’’, getppid()); exit(1); } else { /* Proceso Padre */ printf(‘‘Mi PID es el %d y he creado un proceso hijo cuyo pid es %d\n’’, getpid(), pid); pid_hijo_finalizado = wait(&status); printf(‘‘\nEl proceso hijo [pid] = %d, finalizo con el estado %d\n’’, pid_hijo_finalizado, status); } return(0); }

4.5. EJECUCIÓN DE PROCESOS: EXECL()

4.5.

23

Ejecución de Procesos: execl()

Una llamada al sistema que se utiliza normalmente en combinación con el fork() es execl(), la cual nos permite sustituir la imagen de un proceso por otro.

SYNOPSIS #include int execl( const char *path, const char *arg, ...); DESCRIPCION execl() sustituye la imagen del proceso actual por la del proceso cuyo código se indica como parámetro. Comprobar que las sentencias posteriores a un execl() no se ejecutan si dicha función ha tenido éxito (ya que se ha sustituido la imagen del proceso). Es decir, después de una llamada a execl() sólo tiene sentido comprobar si se ha producido algún error en la llamada.

PARAMETROS path: Camino completo del programa a ejecutar. arg: Lista de argumentos a dicho programa. VALORES RETORNADOS Retorna -1 si se produce algun error. EJEMPLO En el ejemplo que se muestra a continuación, se crea un proceso hijo que ejecuta el programa cuyo código se encuentra en un fichero llamado “esclavo”. Dicho programa se ejecuta con dos parámetros de entrada, el primero se corresponde al nombre que tomará en la ejecución (argv[0]), en este caso “nombre”; y el segundo es el parámetro “-a”. La lista de argumentos termina con NULL. El proceso padre, simplemente espera a que el proceso hijo finalice.

#include #include #include #include



CAPÍTULO 4. GESTIÓN DE PROCESOS

24 #include int main() { int pid = 0, status = 0;

if ((pid = fork()) == -1) { printf(‘‘Error al crear proceso hijo\n’’); exit(1); } if (pid == 0) { /* Proceso Hijo */ if (execl(‘‘esclavo’’, ‘‘nombre’’, ‘‘-a’’, NULL) == -1) { printf(‘‘Error al ejecutar execl\n’’); return; } /* Nunca llega aqui, se ha cambiado la imagen del proceso */ } else { /* Proceso Padre */ pid_hijo_finalizado = wait(&status); printf(‘‘\nEl proceso hijo [pid] = %d, finalizo con el estado %d\n’’, pid_hijo_finalizado, status); } return(0); } A continuación se muestra un posible código del proceso “esclavo”, que simplemente imprime en pantalla la lista de argumentos recibidos:

#include int main(int argc, char *argv[]) { int i = 0; for (i = 0; i < argc; i++) printf(‘‘\nArgumento [%d]: %s’’, i, argv[i]); return(0); }

Capítulo 5 Utilización de Semáforos en el Laboratorio El sistema operativo UNIX proporciona unos mecanismos de comunicación entre procesos llamados ipcs. Dichos mecanismos son: memoria compartida, semáforos y paso de mensajes. Para ofrecer una interfaz de acceso a los semáforos similar a la explicada en las clases teóricas, se proporciona una librería de funciones en C que encapsula el manejo de los ipcs del sistema operativo. Dicha librería de funciones se encuentra en el fichero /opt/ipcms/lib/libipcms.a, y su fichero de cabecera en: /opt/ipcms/include/cipcms.h. El fichero cipcms.h contiene la declaración de las funciones de manejo de semáforos. Se recomienda leer detenidamente la declaración de las funciones: sem_crear(), sem_capturar(), sem_wait(), sem_signal y sem_destruir() (la función sem_ver() sólo se podrá utilizar, en tareas de depuración, para mostrar el valor interno de la variable asociada al semáforo). Cada semáforo se crea con una clave1 , de manera que sólo los procesos que conozcan dicha clave puedan acceder al semáforo. Existe, por tanto, un proceso que crea el semáforo y que puede acceder a él a partir del identificador del semáforo retornado, tal y como se muestra a continuación:

#include int main() { semaforo_t s; s = sem_crear(1234, 1, 0); /* crea un semaforo binario de paso */ /* con la clave de acceso 1234 y retorna */ /* el descriptor del semaforo creado */ ......................... sem_wait(s); ......................... sem_signal(s); 1

Las claves utilizadas en el laboratorio serán enteros mayores que 1000.

25

CAPÍTULO 5. UTILIZACIÓN DE SEMÁFOROS EN EL LABORATORIO

26

......................... sem_destruir(s); return(0); } Si algún otro proceso quiere utilizar el semáforo s, deberá obtener su descriptor. A partir de ese momento, puede interaccionar con el semáforo exactamente igual que el proceso que lo creó. Una vez destruido el semáforo, ningún proceso podrá acceder a él.

#include int main() { semaforo_t s; s = sem_capturar(1234); /* retorna el descriptor del semaforo */ /* que se creo con la clave 1234 */ ......................... sem_wait(s); ......................... sem_signal(s); ......................... return(0); } Para compilar un programa que utilice la librería libipcms.a, es necesario indicarle al compilador la localización de dicha librería y la de su fichero de cabecera2 : gcc -Wall -I /opt/ipcms/include/ - L /opt/ipcms/lib/ programa.c -lipcms -o programa

Además, es necesario ejecutar un proceso encargado de gestionar los mecanismos de comunicación de bajo nivel que utiliza dicha librería. Para ello basta con ejecutar el programa: /opt/ipcms/bin/gestoripcms. Dicho proceso debe ser ejecutado una sola vez al iniciarse la sesión en el laboratorio. Al finalizar dicha sesión, debe finalizarse su ejecución. Para ello se proporciona el programa: /opt/ipcms/bin/matagestoripcms. Si se produce algún error en la ejecución de los programas desarrollados en el laboratorio, y estos se abortan anormalmente sin destruir antes los semáforos creados, es necesario abortar también el proceso gestoripcms y ejecutarlo de nuevo. En ese caso, y dependiendo de cómo se abortó el programa que utiliza la librería, puede ser necesario abortar la ejecución del proceso gestoripcms mediante el comando kill pid. En ese caso, es necesario eliminar también las ipcs que estaba utilizando el proceso gestor. Para ello, existen los comandos: ipcs (similar a ps), e ipcrm (similar a kill) (se recomienda ver el manual (man) 2

Ver la sección 3.2.

27 de cada uno de estos comandos. A partir del comando ipcs se obtienen los identificadores de los ipcs activos, y con el comando ipcrm [sem id] [shm id] [msg id] se eliminan los ipcs correspondientes a semáforos (sem), memoria compartida (shm) y colas de mensajes (msg). Finalmente, se recomienda comprobar todos los posibles errores producidos en las llamadas a las funciones de manejo de semáforos. En la mayoría de los casos, éstas retornan un valor negativo en caso de error. (Ver fichero /opt/ipcms/include/cipcms.h). Nota Importante: Al abandonar el laboratorio es imprescindible que se eliminen todos los procesos e ipcs creados. En caso contrario, el siguiente grupo no podrá ejecutar el proceso gestoripcms, debiendo reiniciar el ordenador para poder empezar a trabajar. Para evitar esta situación, antes de abandonar el laboratorio se deberán seguir los siguientes pasos: 1. Ejecutar el proceso matagestoripcms. 2. Comprobar que no quedan procesos gestores en ejecución: >ps -aux | grep gestor 3. Si quedase algún proceso gestor, debe ser eliminado con el comando Kill pid. 4. Comprobar que no quedan ipcs activas: >ipcs 5. Si quedase alguna ipc, debe ser eliminada con el comando ipcrm.

28

CAPÍTULO 5. UTILIZACIÓN DE SEMÁFOROS EN EL LABORATORIO

Capítulo 6 Utilización de monitores en el laboratorio El sistema operativo unix no proporciona mecanismos de sincronización de alto nivel como monitores. En el laboratorio se dispone de una implementación software de monitores en la librería libcipcms. Para crear el código correspondiente a un monitor genérico denominado monitor_ejemplo, se deberán seguir los siguientes pasos (sin añadir líneas en blanco ni comentarios): 1. Crear un fichero monitor_ejemplo.mon que contenga la declaración del tipo de monitor, las variables condition, los procedimientos ENTRY, un procedimiento de inicio y un procedimiento de finalización, según la siguiente sintaxis: tipo: 1 (continúa el proceso que señala), ó 2 (continúa el proceso señalizado). condicionales: nombre de las variables de tipo condition, separadas por comas, sin ningún carácter al final de la línea. Si la declaración de dichas variables ocupa más de una línea, no deben separarse éstas con un retorno de carro, sino que se escribirán todas seguidas. publicos: nombre de los procedimientos de entrada al monitor. Siguen las mismas normas que el caso anterior inicio: nombre de la función que se ejecuta al iniciar el monitor. fin: nombre de la función que se ejecuta al finalizar el monitor. Es necesario que exista, aunque no haga nada. A partir de esta cabecera, se continua como si fuera un programa C normal. En este caso, se escribirá el código C de cada uno de los procedimientos del monitor: tipo: 1 condicionales: lleno, vacio publicos: procedimiento_1, procedimiento_3, procedimiento_4, procedim iento_5, inicio: inicio fin: fin 29

30

CAPÍTULO 6. UTILIZACIÓN DE MONITORES EN EL LABORATORIO

#include int procedimiento_1() { } void procedimiento_2() /* no es ENTRY */ { } int procedimiento_3() { } int procedimiento_4() { } int procedimiento_5() { } void inicio() { } void fin() { } En la implementación de la librería que se va a utilizar en el laboratorio, todos los procedimientos de tipo ENTRY deben retornar un valor entero, y no pueden recibir ningún parámetro. 2. A partir de dicho fichero, crear un fichero en C mediante el programa: /opt/ipcms/bin/mongenint.pl monitor_ejemplo.mon > monitor_ejemplo.c. 3. Compile dicho programa con la librería lipcmsint y la librería del sistema lpthread: gcc -Wall -I /opt/ipcms/include/ -L /opt/ipcms/lib/ monitor_ejemplo.c -lipcmsint -lpthread -o monitor_ejemplo El ejecutable creado (monitor_ejemplo) no debe ejecutarse. 4. Crear un programa que cree el monitor: #include #include

31

int main () { if (mon_crear(2000, "monitor_ejemplo") < (monitor_t) 0) printf("No se puedo lanzar el monitor.\n"); else printf("Monitor lanzado.\n"); return(0); } 5. Crear un programa para finalizar su ejecución: #include #include int main() { if (mon_destruir(mon_capturar(2000)) < 0) printf("error al destruir\n"); else printf("monitor destruido\n"); return(0); } 6. Para utilizar dicho monitor desde un programa en C, declarar una variable de tipo monitor_t, capturar su descriptor a partir de la clave con la que se creó, e invocar a los procedimientos del monitor con la función. int monitorint(monitor_t, char *) #include #include int main() { monitor_t mi_monitor; int n = 0; mi_monitor = mon_capturar(2000); ........ ........ n = monitorint(mi_monitor, "procedimiento_1"); return(0); } Estos programa se compilan con la librería ipcms, al igual que los programas que utilizan semáforos y necesitan la ejecución previa del proceso gestoripcms.

CAPÍTULO 6. UTILIZACIÓN DE MONITORES EN EL LABORATORIO

32

7. Para utilizar las variables de tipo condition declaradas en la cabecera existen las funciones: condm_wait(variable), condm_signal(variable) y condm_empty(variable).

6.1.

Monitor ejemplo

A continuación, se muestra un monitor de ejemplo que implementa la funcionalidad de un semáforo entero. El ejemplo consta de 4 ficheros: ej_mon.mon correspondiente al monitor, un proceso ej_proc que utiliza dicho monitor y dos procesos para iniciar y finalizar el monitor: ej_inic.c y ej_fin.c. :::::::::::::: ej_mon.mon :::::::::::::: tipo: 1 condicionales: cola_sem publicos:mi_sem_wait, mi_sem_signal inicio: mi_inic_sem fin: mi_fin_sem /*************************** FIN CABECERA ******************************/ #define N 4 /* valor maximo del semaforo */ int n; /* variable interna del semaforo */ int mi_sem_wait() { if (n == 0) condm_wait(cola_sem); else n--; return(0); } int mi_sem_signal() { if (! condm_empty(cola_sem)) condm_signal(cola_sem); else { if (n == N) return(1); /* error */ n++; } return(0); }

6.1. MONITOR EJEMPLO

void mi_inic_sem() { n = N; /* inicialmente el semaforo esta abierto */ } void mi_fin_sem() { return; } :::::::::::::: ej_inic.c :::::::::::::: #include #include int main() { if (mon_crear(2004, "ej_mon") < ((monitor_t) 0)) printf("No se pudo lanzar el monitor\n"); else printf("Monitor lanzado correctamente\n"); return(0); }

:::::::::::::: ej_fin.c :::::::::::::: #include #include int main() { if (mon_destruir(mon_capturar(2004)) < 0) printf("No se pudo destruir el monitor\n"); else printf("Monitor finalizado\n"); return(0); }

33

34

CAPÍTULO 6. UTILIZACIÓN DE MONITORES EN EL LABORATORIO

:::::::::::::: ej_proc.c :::::::::::::: #include #include int main() { int resultado = 0; monitor_t mon_sem; if ((mon_sem = mon_capturar(2004)) < (monitor_t) 0) { printf("No se pudo capturar el monitor\n"); exit(-1); } printf("\nElija una opcion\n1. Wait\n2. Signal\n3.Fin del proceso\n> "); do { switch (getchar()) { case ’1’: printf("Intentando acceder al recurso...\n"); resultado = monitorint(mon_sem, "mi_sem_wait"); if (resultado < 0) { printf("Error interno en el monitor\n"); return(-1); } else printf("Recurso accedido\n"); printf("\nElija una opcion\n1. Wait\n2. Signal\n3.Fin del proceso\n> "); break; case ’2’: resultado = monitorint(mon_sem, "mi_sem_signal"); if (resultado < 0) { printf("Error interno en el monitor\n"); return(-1); } else { if (resultado == 1) printf("Demasiados signals.\n"); else printf("Fin acceso recurso\n"); } printf("\nElija una opcion\n1. Wait\n2. Signal\n3.Fin del proceso\n> "); break; case ’3’: return (0); }

6.1. MONITOR EJEMPLO } while (1); return(0); }

35

CAPÍTULO 6. UTILIZACIÓN DE MONITORES EN EL LABORATORIO

36

6.2.

Monitores con paso de parámetros

Para utilizar monitores cuyas funciones EN T RY puedan recibir parámetros, es necesario definir dichos parámetros como punteros a void. A continuación, se describe un ejemplo: 1. Fichero buffer.mon: tipo: 1 condicionales: vacio, lleno publicos: producir, consumir inicio: iniciar fin: finalizar #include #include void void void void

*producir(void *); *consumir(void *); iniciar(void); finalizar(void);

#define N 4 int buffer[N], ptr_prod, ptr_cons, num_datos; void iniciar(void) { ptr_prod = 0; ptr_cons = 0; num_datos = 0; buffer[0]=0; buffer[1]=0; buffer[2]=0; buffer[3]=0; } void *producir(void *dato) { if (num_datos == N) condm_wait(lleno); buffer[ptr_prod] = *((int *) dato); ptr_prod = (ptr_prod + 1) % N; num_datos++; condm_signal(vacio); return(NULL); }

6.2. MONITORES CON PASO DE PARÁMETROS

37

void *consumir(void *nada) { int *mi_dato = NULL; if ((mi_dato = malloc(sizeof(int))) == NULL) { printf(‘‘Error en malloc\n’’); exit(-1); } if (num_datos == 0) condm_wait(vacio); *mi_dato = buffer[ptr_cons]; ptr_cons = (ptr_cons + 1) % N; num_datos--; condm_signal(lleno); return((void *) mi_dato); } void finalizar(void) { return; } 2. A partir de dicho fichero, crear un fichero en C mediante el programa: /opt/ipcms/bin/mongen.pl buffer.mon > buffer.c. 3. Compile dicho programa con la librería lipcms y la librería del sistema lpthread: gcc -Wall -I /opt/ipcms/include/ -L /opt/ipcms/lib/ buffer.c -lipcms -lpthread -o buffer El ejecutable creado (buffer) no debe ejecutarse. 4. Crear un programa que cree el monitor: #include #include int main () { if (mon_crear(1234, "buffer") < (monitor_t) 0) printf("No se puedo lanzar el monitor.\n"); else printf("Monitor lanzado.\n"); return(0);

CAPÍTULO 6. UTILIZACIÓN DE MONITORES EN EL LABORATORIO

38 }

5. Crear un programa para finalizar su ejecución: #include #include int main() { if (mon_destruir(mon_capturar(1234)) < 0) printf("error al destruir\n"); else printf("monitor destruido\n"); return(0); } 6. Para utilizar dicho monitor desde un programa en C, declarar una variable de tipo monitor_t, capturar su descriptor a partir de la clave con la que se creó, e invocar a los procedimientos del monitor con la función. int monitor(monitor_t, char *, size_t, size_t, void*) *********** productor.c *********** #include #include #include int main() { monitor_t m; int dato=3; if ((m = mon_capturar(1234))

Get in touch

Social

© Copyright 2013 - 2024 MYDOKUMENT.COM - All rights reserved.