Está en la página 1de 7
Práctica 6: Terminación de Procesos. Ejecución de Programas. Página 1 Práctica 6. Terminación de Procesos (exit y wait). Ejecución de Programas (exec). 1. Objetivos. • Utilizar llamadas al sistema Linux para terminar procesos y para conocer el estado en el que finalizan otros procesos. • Ejecutar programas diferentes desde un programa escrito en lenguaje C. 2. Terminación de Procesos: exit y wait. Un caso típico de programación tiene lugar cuando un proceso permanece a la espera de que termine otro proceso hijo suyo antes de continuar ejecutándose. Un ejemplo de esta situación se tiene cuando se escribe una orden en la línea de comandos y se pulsa [intro]: el proceso shell crea un proceso hijo suyo que se encarga de ejecutar la orden solicitada y no muestra el cursor (señal de espera por una nueva orden) hasta que no se ha ejecutado completamente. Como es lógico, esto no ocurre así cuando la orden se ejecuta en background. Para poder sincronizar los procesos padre e hijo, se emplean las llamadas exit y wait, cuyas declaraciones son las siguientes: #include #include void exit (int estado); #include pid_t wait(int *estado); exit termina la ejecución de un proceso y devuelve al sistema el valor estado. Si el proceso padre del que ejecuta la llamada a exit está ejecutando una llamada a wait, se le notifica la terminación de su proceso hijo y se le envían los ocho bits menos significativos de estado. Con esta información, el proceso padre puede saber en qué condiciones ha terminado el proceso hijo. wait suspende la ejecución del proceso que la invoca hasta que alguno de sus procesos hijo finaliza. La forma de invocar a wait es: pid_t pid; int estado,edad; pid=wait(&estado); Práctica 6: Terminación de Procesos. Ejecución de Programas. Página 2 Se obtiene en pid el valor del identificador de proceso de alguno de los hijos zombies (un proceso zombie es el que acaba de finalizar). En la variable estado se almacena el valor que el proceso hijo le envía al padre mediante la llamada a exit y que da idea de la condición de finalización del proceso hijo. Si se quiere ignorar este valor, se puede pasar como parámetro un puntero NULL (pid=wait(NULL)). En el archivo de cabecera se definen diferentes macros que permiten analizar el valor de estado para determinar la causa de terminación del proceso. En particular, el proceso padre puede obtener el valor de los ocho bits menos significativos del parámetro que recibe desde la llamada a exit por parte del hijo utilizando la macro WEXITSTATUS(estado). Si durante la llamada a wait se produce algún error, la función devuelve –1 y en errno quedará el código del tipo de error producido. Como es lógico, si el proceso que invoca a wait no tiene ningún proceso hijo vivo, se produce un error. Por ejemplo, sea el siguiente código correspondiente a un proceso cuyo pid es igual a 10 y que tiene un hijo con pid igual a 20: #include #include #include ... int main (void){ pid_t pid; int estado,edad,edadHijo; ... pid=wait(&estado); edad=WEXITSTATUS(estado); printf("Mi hijo %d ha fallecido a los %d años.\n",pid,edad); pid=wait(&estado); printf("pid=%d\n",pid); ... } Práctica 6: Terminación de Procesos. Ejecución de Programas. Página 3 Si el código del proceso hijo termina con las siguientes instrucciones: ... edadHijo=140; exit(edadHijo); } Cuando el proceso padre llega a la instrucción wait suspende su ejecución y permanece a la espera de que finalice el proceso hijo. Éste finaliza cuando alcanza la instrucción exit que tiene como parámetro la variable edadHijo, cuyo valor es 140 en el proceso hijo. Es ahora cuando el proceso padre se desbloquea y la instrucción wait devuelve como resultado el valor del pid del proceso hijo que ha terminado (en este caso, el valor 20) y lo almacena en la variable pid. En los ocho bits menos significativos de la variable estado queda almacenado el valor de los ocho bits menos significativos de la variable edadHijo (parámetro de la instrucción exit del proceso hijo). Como es posible que el resto de bits de la variable estado tengan valores no deseados, se utiliza la macro WEXITSTATUS para obtener el valor correcto del parámetro enviado por el proceso hijo. Así, tras finalizar el proceso hijo, el proceso padre mostraría en pantalla el siguiente mensaje: Mi hijo 20 ha fallecido a los 140 años. Posteriormente, el padre pretende esperar a que termine otro proceso hijo (wait). Puesto que ya no hay ningún hijo vivo, la llamada a wait produce un error y devuelve el valor –1. Entonces se vuelca en pantalla el mensaje: pid=-1 3. Ejecución de programas: exec. Existe toda una familia de funciones exec útiles para lanzar programas ejecutables desde un programa escrito en lenguaje C. Dentro de esta familia cada función tiene su sintaxis pero todas tienen aspectos comunes y obedecen al mismo tipo de funcionamiento: se carga un programa en la zona de memoria del proceso que ejecuta la llamada sobreescribiendo los segmentos del programa antiguo con los del nuevo. Es decir, el programa viejo es SUSTITUIDO por el nuevo y NUNCA se volverá a él para proseguir su ejecución, pues es el programa nuevo el que pasa a ejecutarse. Práctica 6: Terminación de Procesos. Ejecución de Programas. Página 4 La declaración de la familia de funciones exec es la siguiente: int execl (char *ruta, char *arg0, char * arg1,..., char *argn, (char *)0); int execv (char *ruta, char *argv[]); int execle (char *ruta, char *arg0, char * arg1,..., char *argn, (char *)0, char *envp[]); int execve (char *ruta, char *argv[], char *envp[]); int execlp (char *fichero, char *arg0, char * arg1,..., char *argn, (char *)0); int execvp (char *fichero, char *argv[]); Donde: ruta es una cadena con el path (absoluto o relativo) de un archivo ejecutable. fichero es el nombre de un fichero ejecutable. arg0, arg1,..., argn son cadenas de caracteres que constituyen la lista de parámetros que se le pasa al nuevo programa. Por convenio, al menos arg0 está presente siempre y coincide con ruta o con el último componente de ruta. Hay que destacar que tras argn se pasa un puntero NULL para indicar el final de los argumentos. argv es un array de cadenas de caracteres que constituye la lista de argumentos que va a recibir el nuevo programa. Por convenio, argv debe tener al menos un elemento, que coincide con ruta o con el último componente de ruta. El final de argv se indica colocando un puntero NULL detrás del último parámetro. envp es un array de punteros a cadenas de caracteres que constituyen el entorno en el que se va a ejecutar el nuevo programa. envp también termina con un puntero NULL. Un programa escrito en C recibe estos parámetros a través de la función main, que se puede declarar de la siguiente forma: void main (int argc, char *argv[], char *envp[]); Así, por ejemplo, se podría obtener un listado completo de los archivos del directorio actual cuyo nombre termine con la cadena “.c” utilizando la instucción execl: execl("/bin/ls","ls","-l","*.c", (char *)0); La instrucción anterior ejecuta la orden ls de igual manera que si se escribiera en la línea de comandos la siguiente orden: ls –l *.c Práctica 6: Terminación de Procesos. Ejecución de Programas. Página 5 Si hubiera más instrucciones posteriores a execl no se ejecutarían porque el programa ls sustituye al que lo llama. En el caso de que se deseara ejecutar más instrucciones, sería necesario crear un proceso hijo que se encargara de llamar a la orden execl mientras el padre podría continuar realizando otras tareas. Práctica 6: Terminación de Procesos. Ejecución de Programas. Página 6 4. Enunciado de la práctica. Escribir un programa en lenguaje C que reciba un parámetro desde la línea de órdenes y se encargue de: - Comprobar que la sintaxis empleada ha sido adecuada y, en caso contrario, mostrar por pantalla cuál es la correcta: La sintaxis correcta es: dormilones número_hijos - Crear un número de procesos hijo dado por el parámetro de la línea de órdenes. - Cada uno de los procesos hijo generará un número aleatorio entre 1 y 10, mostrará un mensaje indicando su pid y el tiempo que va a dormir. A continuación dormirá ese número de segundos. Una vez transcurrido este tiempo, el proceso hijo termina con una llamada a exit tal que el proceso padre pueda conocer el tiempo que dicho hijo ha estado durmiendo. - El proceso padre, por su parte, esperará (wait) a que terminen todos sus procesos hijo. A medida que vayan finalizando irá mostrando el valor del pid de cada hijo junto con el tiempo que ha dormido. - Cuando todos los hijos hayan terminado, el proceso padre debe dormir durante 2 segundos y, posteriormente, ejecutar ( execl ) el siguiente programa: ps –lc. ¿Por qué no se visualiza ningún mensaje si se coloca despúes de la orden execl la instrucción siguiente? printf("PADRE: ejecución terminada\n"); Generación de números aleatorios en lenguaje C. Para generar números aleatorios, el lenguaje C dispone de las funciones srand y rand, que aparecen en el archivo de cabecera stdlib.h. void srand (unsigned semilla); Inicializa el generador de números aleatorios. La secuencia generada depende del valor de semilla y es siempre la misma para la misma semilla. Práctica 6: Terminación de Procesos. Ejecución de Programas. Página 7 int rand (void); Permite obtener un número aleatorio. Para generar un entero aleatorio entre 1 y 10 se puede utilizar el siguiente código dentro de cada proceso hijo: srand(getpid()); tiempo=1+(int)(10.0*rand()/(RAND_MAX+1.0)); Nota: Normalmente, el generador de números aleatorios se inicializa con el valor del reloj del sistema para evitar que se repita la misma secuencia cada vez que se ejecuta el programa.