Sistemas Operativos I Grupos Lunes Martes Miércoles Viernes Realización 22 de Febrero, 1, 8, 15 de Marzo 23 de Febrero, 2, 9 y 16 de Marzo 24 de Febrero, 3, 10 y 17 de Marzo 19 y 26 de Febrero, 5 y 12 de Marzo Práctica 1: Procesos y señales Entrega/Evaluación 22 de Marzo 23 de Marzo 24 de Marzo 19 de Marzo (Entrega electrónica) Teoría Procesos Los procesos tienen un tiempo de vida definido creándose mediante una llamada a fork o vfork. Finalizan mediante un exit o un return en el main o cuando reciben una señal de terminación. A su vez los procesos hijos creados pueden crear nuevos hijos o ejecutar otros programas mediante llamadas a las funciones de la familia exec. Todos los procesos tienen un proceso padre y pueden ser padres de varios procesos hijos. Esto forma una estructura jerárquica en forma de árbol siendo el proceso init el tronco. Si un proceso termina antes que sus hijos estos pasan a ser hijos de dicho proceso. Para diferenciar unos procesos de otros dentro de este árbol se asigna a cada proceso un identificador único llamado identificador de proceso, PID. Además el sistema maneja la llamada process table que consiste en un array de estructuras tipo proc que contienen la información sobre los procesos que hay en el sistema, aunque no estén en CPU. Los procesos en System V R2 pueden estar en los siguientes estados: •inicial (idle): El proceso está siendo creado pero todavía no está listo para ejecución. •listo (runnable, ready to run) y en espera (blocked, asleep): El proceso está listo para ser ejecutado. Puede estar en la memoria principal o en el intercambio (swapped). •ejecución modo usuario (user running) •ejecución modo kernel (kernel running) •zombie: El proceso ha terminado pero su proceso padre no ha hecho wait(). Por esto el proceso permanece en tabla de procesos y para el sistema el proceso sigue existiendo. •parada (a partir de 4.2BSD): El proceso está parado. Esto se debe a que el proceso ha recibido alguna señal de parada. Para salir de este estado debe recibir la señal de continuación (SIGCONT). En esta primera práctica de Sistemas Operativos I vamos a familiarizarnos con el uso de la función de creación de procesos fork, de espera de hijos wait y de obtención de los pid de proceso con getpid y del proceso padre con getppid. Es necesario, por tanto, leer las páginas del manual de dichas funciones antes de empezar los ejercicios. © Escuela Politécnica Superior Universidad Autónoma de Madrid 1 Sistemas Operativos I Práctica 1: Procesos y señales Señales Las señales son usadas por el kernel para notificar a los procesos de sucesos asíncronos. Por ejemplo, si se pulsa Ctrl-C el kernel envía SIGINT. Además los procesos pueden enviarse señales mediante la función kill. Se responde a las señales al volver a modo usuario. Está respuesta puede ser terminar el proceso, ignorar la señal o realizar una acción definida por el usuario. En cualquier caso esta acción no se realiza de manera inmediata. El kernel pone un bit en la estructura proc del proceso indicando que la señal ha llegado y si el proceso está en una espera interruptible lo saca de esta. En este caso la llamada al sistema devolverá -1 y errno será EINTR. En esta segunda parte vamos a familiarizarnos con el uso de las funciones de envío y captura de señales kill y signal, así como de las funciones de espera pause y sigsuspend. Es necesario, por tanto, leer las páginas del manual de dichas funciones antes de empezar los ejercicios. Ejercicios de procesos 1. Implementa un programa en C que use fork para crear el siguiente árbol de procesos. Ten en cuenta que no es necesario generar ningún tipo de representación gráfica, más allá de una frase indicando cuándo se ha creado un proceso, su pid y el pid de su padre. Observa también que cada nodo es un proceso, y que el PID mostrado es únicamente orientativo. ¿Por qué crees que algunos nodos aparecen repetidos en el diagrama? (3 puntos) 1978 1978 fork fork 1ª generación (21 = 2) 1979 fork 1978 1980 1979 1981 2 2ª generación (2 = 4) 2. ¿El orden de la salida al ejecutar el programa 1 es siempre el mismo? Justifica tu respuesta. (1 punto) 3. Modifica el programa anterior, escribiendo un programa2.c que en lugar de generar 4 procesos, genere 32 procesos. Justifica la modificación realizada al código. (2 puntos) © Escuela Politécnica Superior Universidad Autónoma de Madrid 2 Sistemas Operativos I Práctica 1: Procesos y señales Escribe el siguiente código y guárdalo como programa3.c // programa3.c #include <unistd.h> #include <stdlib.h> #include <stdio.h> int main (int argc, char *argv [], char *env []) { int i, a=1; } for (i = 0; i < 10; i++) { switch (fork ()) { case 0: a=3; printf(“a= %d\n”,a); break; } printf(“a=%d\n”,a); } exit (0); 4. ¿Por qué puede fallar fork? ¿hay alguna forma de controlar el error de fork? Modifica el código de programa3.c para controlarlo. (2 puntos) 5. ¿Qué otro problema tiene el código del programa3.c? Soluciónalo escribiendo el código correcto de programa3.c como programa4.c (2 puntos) 6. ¿Cuánto vale la variable a para el código padre del programa4.c? ¿y el para el hijo? ¿por qué no valen lo mismo? ¿en algún momento valen lo mismo? (1 punto) 7. Modifica el código de programa4.c como un nuevo programa5.c en el que el hijo devuelve el valor de la variable a para que el printf que hace el padre muestre el mismo valor que el printf que hace el proceso hijo. (2 puntos) 8. Explica las macros de wait en un ejemplo de programa padre (programa6.c) que cree un proceso, y lo espere con wait, ¿cómo afecta a wait el uso de las macros? (2 puntos) 9. Modifica programa6.c como programa7.c para que en lugar de usar wait, espere al proceso hijo usando waitpid. ¿Qué diferencias encuentras entre el uso de wait/waitpid? (2 puntos) 10. Escribe el código de un programa8.c que reserve en el proceso padre memoria dinámica para una cadena de 20 caracteres de longitud y un proceso hijo. Si en el proceso hijo se pide al usuario que introduzca un nombre para guardar en la cadena. ¿El proceso padre tiene acceso a ese valor? ¿qué ocurre si el usuario no teclea nada? ¿dónde hay que liberar la memoria reservada y por qué? (5 puntos) © Escuela Politécnica Superior Universidad Autónoma de Madrid 3 Sistemas Operativos I Práctica 1: Procesos y señales Ejercicios de señales Escribe el siguiente código y guárdalo como programa9.c // programa9.c #include <signal.h> #include <stdio.h> #include <stdlib.h> void (*signal (int sig, void (*func)(int))) (int); void captura (int sennal) { printf ("Capturada la señal %d\n", sennal); fflush (NULL); return; } int main (int argc, char *argv [], char *env []) { if (signal (SIGINT, captura) == SIG_ERR) { puts ("Error en la captura"); exit (1); } while (1); exit (0); } 11. Ejecuta el programa9.c, ¿la llamada a signal supone que se ejecute la función captura? ¿cuándo aparece el printf en pantalla? ¿por qué no aparece antes? (1 punto) 12. ¿Qué ocurre por defecto cuando un programa recibe una señal y no la tiene capturada? Demuestra tu respuesta modificando el código de programa9.c y guardando el código resultante como programa10.c (2 puntos) 13. Escribe el código de un programa11.c que ignore la pulsación de Ctrl+C en el teclado. ¿Cómo podrías demostrar que realmente la señal ha sido ignorada? (2 puntos) 14. ¿Todas las señales tienen asociada una semántica previa? Si no es así, pon un ejemplo en un programa12.c de algún tipo de señal definida por el usuario (2 puntos) 15. Escribe el código de un programa13.c que intente capturar SIGKILL, y que escriba en la función manejadora “He conseguido capturar SIGKILL”. ¿Por qué nunca sale por pantalla “He conseguido capturar SIGKILL”? (3 puntos) 16. ¿Por qué se permite el uso de variables globales para compartir datos entre la función manejadora y el resto del programa que tiene capturada una señal? (1 punto) 17. ¿Qué diferencia hay entre usar pause o sigsuspend? Demuestra tu respuesta con dos ejemplos de código (programa14.c y programa15.c) (2 puntos) 18. Escribe un programa16.c que capture todas las señales capturables. (2 puntos) 19. Mientras se ejecuta el código de la función manejadora del programa16.c asociada a SIGUSR1, envíale SIGUSR2 desde otra terminal, ¿qué sucede? ¿por qué? (1 punto) © Escuela Politécnica Superior Universidad Autónoma de Madrid 4 Sistemas Operativos I Práctica 1: Procesos y señales 20. Escribe el código de un programa17.c que establezca una alarma inicial de 20 segundos en los que el programa debe mostrar por pantalla una secuencia numérica creciente separada por comas. Cuando salte la alarma, la última línea mostrada en la pantalla debe ser: “Estos son los números que me ha dado tiempo a contar en 20 segundos”. (5 puntos) © Escuela Politécnica Superior Universidad Autónoma de Madrid 5 Sistemas Operativos I Práctica 1: Procesos y señales Ejercicio Final de la Práctica 1 Escribe el código de un programa18.c que consista en un sistema básico de c a l c u l o d e n ú m e r o s p r i m o s . En particular, se pide que el programa consista en un bucle que: 1. Pide al usuario dos números enteros (de los que se tomará únicamente su valor absoluto). Por ejemplo: 4 y 13. 2. Crea tres procesos hijos: - Un hijo se encarga de calcular todos los primos entre ambos números. En este caso 5, 7, 11, 13 y mostrarlos por pantalla. - Otro hijo decidirá si el número menor de los dos es primo e informará mediante exit al padre. - Otro hijo decidirá si el número mayor es primo e informará al padre mediante el exit. Só lo el proceso hijo e n c a rg a do d e l cá l c u lo d e lo s p r i mo s mostrará su resultado por pantalla. L o s d e m á s procesos hijos deben enviar el resultado de su operación al proceso padre en el retorno de exit. 3. El proceso padre debe realizar una espera no activa para recoger los resultados de los procesos hijos y los debe mostrar por pantalla respetando el siguiente orde: - Primero la lista con los números primos: 5, 7, 11, 13 - despues el resultado sobre el número menor: El número 4 no es primo - y por último el mensaje del número mayor: En número 13 es primo. - Si se produjese cualquier error durante el proceso se infomaría al usuario por pantalla . El programa debe terminar en cualquier momento cuando el usuario pulse Ctrl+C, liberando todos los recursos solicitados y sin dejar procesos zombie. También debe terminar si el usuario tarda más de 1 minuto en introducir los valores a y b. Para evitar que cada hijo recalcule los primos será solo uno de ellos el que lo haga e informe a los demás. Para ello, una vez calculados los número primos, enviará una señal a cada uno de los otros hijos para que estos la procesen e informen en consecuencia al padre de si su número es primo o no. Para que este mecanismo de comunicación funcione es imprescindible controlar el orden en que se lanzan los hijos y la sincronización entre ellos. Todas las esperas han de ser no activas y, si es posible, no bloqueantes. (20 puntos) © Escuela Politécnica Superior Universidad Autónoma de Madrid 6 Sistemas Operativos I Práctica 1: Procesos y señales Ejemplo de Ejecución del Ejercicio Final En cursiva se indica los valores introducidos por el usuario. Introduce el valor del primer operando: 3 Introduce el valor del segundo operando: 15 La lista es: 3, 5, 7, 11, 13 El número 3 es primo El número 15 no es primo Introduce el valor del primer operando: -2 Introduce el valor del segundo operando: 4 La lista es: 2, 3 El número -2 no es primo El número 4 no es primo Recibido Ctrl-C. Fin del programa. Criterios de evaluación 1. Todos los ejercicios son obligatorios. La puntuación se obtiene aplicando una regla de tres directa, en la que 63 puntos equivalen a obtener un 10 en esta práctica. 2. Aquellos ejercicios que no contienen una respuesta con código, se evaluarán atendiendo a la explicación realizada por el alumno/a. Para que el razonamiento sea considerado válido debe ser suficientemente detallado y se puede apoyar en documentación externa. En ningún caso, se considerará una respuesta válida una captura de pantalla sin explicación, o un código sin explicación. 3. Los ejercicios que solicitan la realización de un código, deben ser también explicados, tanto a nivel de comentarios en el propio código (que se debe entregar junto con la memoria, inserto en los ejercicios o en apéndices), como en texto como respuesta al ejercicio. Esto es, especialmente relevante en el caso del ejercicio final que debe ir acompañado de una descripción textual del diseño realizado y la solución propuesta en el código. 4. No se tendrá en cuenta código que no compila. Recibiendo una fuerte penalización programas que no realizan una correcta liberación de los recursos solicitados al Sistema Operativo (por ej. dejar procesos zombie), o que no tienen un correcto control de errores (con especial interés en el control de errores de las llamadas del sistema operativo tratadas en esta práctica). 5. Por último, se valorará la correcta aplicación de los principios de la programación estructurada (esto es, organizar el código en funciones en la medida de lo posible, separar las definiciones y estructuras en un archivo .h, no usar “números mágicos”, etc.) © Escuela Politécnica Superior Universidad Autónoma de Madrid 7