MÓDULO II: PROGRAMACIÓN CONCURRENTE COMPILACIÓN Y EJECUCIÓN DE PROGRAMAS C. LLAMADAS DEL SISTEMA RELACIONADAS CON LOS PROCESOS. 1.- Compilación y ejecución de programas C. Un programa en C se escribe mediante un editor, dándole al fichero que lo contiene un nombre con la extensión .c. Para compilarlo se usa la orden gcc que es el compilador de C. La sintaxis es: gcc nombre_fichero dando como resultado un fichero denominado a.out que será el ejecutable. Si se quiere que el ejecutable tenga otro nombre distinto se usará la opción -o del compilador de la siguiente forma: gcc -o nombre_fichero_ejecutable nombre_fichero_compilar Este compilador no se detiene al encontrar el primer error, sino que continúa mientras puede. 2.- Llamadas del sistema relacionadas con los procesos. 2.1.- Creación de procesos: fork. La llamada al sistema fork () es la única que permite crear un nuevo proceso. El proceso que invoca a fork () se llama proceso padre y el proceso creado es el proceso hijo. El contenido del proceso hijo es identico al contenido del proceso padre. El nuevo proceso hereda algunas de las características del antiguo proceso, tales como el identificador de usuario y de grupo, la prioridad, etc, excepto el identificador del proceso que será distinto para el proceso hijo, puesto que se le asigna uno nuevo. El proceso hijo comienza su ejecución mientras que el proceso padre continua la suya, por la siguiente instrucción después del fork (). Sólo se puede hacer una llamada fork(), que devuelve dos valores, uno por proceso. Fork () devuelve un cero al proceso hijo y un valor distinto de cero (el identificador del proceso hijo) al proceso padre. Si la llamada a fork () falla, devolverá el valor -1 al proceso padre. El siguiente ejemplo muestra el uso de está llamada al sistema. #include <stdio.h> main () { int pid; if ((pid=fork ())==-1) { printf (“Error en la llamada a fork”); } else if (pid==0) { /* Código que va a ejecutar el proceso hijo. */ © E.G.R. 1 printf ("Soy el proceso hijo"); } else { /* Código que va a ejecutar el proceso padre. */ printf ("Soy el proceso padre"); } } 2.2.- Terminación de procesos: exit y wait. Una situación muy típica es que cuando un proceso crea a otro, el proceso padre se quede esperando a que termine el hijo antes de continuar su ejecución. Un ejemplo de esta situación es la forma de operar de los intérpretes de órdenes. Cuando se escribe una orden, el shell arranca un proceso para ejecutar dicha orden y no devuelve el control hasta que no se ha ejecutado completamente. Para poder sincronizar los procesos padre e hijo, se emplean las llamadas exit y wait. La llamada al sistema exit (estado) finaliza la ejecución de un proceso y devuelve el valor de estado al sistema. Si el proceso padre del que ejecuta la llamada a exit (estado) esta ejecutando una llamada a wait (estado), se le notifica la terminación de su proceso hijo y se le envían los 8 bits menos significativos de estado. Exit (estado) es una de las pocas llamadas que no devuelve ningún valor. Es lógico, ya que el proceso que la llama deja de existir después de haberla ejecutado. La llamada al sistema wait (estado) suspende la ejecución del proceso que la invoca hasta que su proceso hijo haya terminado. Estado es la variable donde se va a almacenar el valor que el proceso hijo le envía al proceso padre mediante la llamada a exit (estado) y que da idea de la condición de finalización del proceso hijo. Si se desea ignorar este valor, se puede pasar a wait (estado) un puntero NULL. Si durante la llamada a wait (estado) se produce algún error, la función devuelve -1. Exit (estado) y wait (estado) se suelen usar para conseguir que el proceso padre espere a la terminación de su proceso hijo. Una secuencia de código para realizar esta operación puede ser: #include <stdio.h> main () { int estado; if ((pid=fork ())==-1) { printf (“Se ha producido un error en la creación del proceso hijo”) } else if (pid==0) } printf ("Soy el proceso hijo"); © E.G.R. 2 exit (10); } else { wait (&estado); printf ("Soy el proceso padre"); printf ("El proceso hijo devuelve", estado); } } Se puede observar en el ejemplo que el proceso padre espera hasta que el proceso hijo termine su ejecución y luego continua. Esta llamada al sistema es la que usa el proceso del Shell que se genera cuando el usuario se conecta con el entorno. 2.3.- Información sobre procesos. Existen un conjunto de llamadas al sistema necesarias para conocer y fijar algunos de los parámetros de un proceso, las cuales describen cómo se relaciona el proceso con el resto del sistema. 2.3.1.- Identificadores de proceso. Todo proceso tiene asociados dos números desde el momento de su creación: el identificador de proceso y el identificador del proceso padre. El identificador de proceso (PID) es un número entero positivo que actúa a modo de nombre del proceso. El identificador del proceso padre (PPID) es el PID del proceso que ha creado al actual. El PID de un proceso no cambia durante el tiempo de vida de éste; sin embargo, su PPID sí puede variar. Esta situación se da cuando el proceso padre muere, pasando el PPID del proceso hijo a tomar el valor 1. Para conocer los valores de PID y PPID se utilizan las llamadas getpid () y getppid (). Es decir, son llamadas al sistema que devuelven al proceso que las solicita el identificador de él mismo y el identificador del proceso padre respectivamente. Estas llamadas nunca van a fallar. El objetivo de conocer el identificador de un proceso o el de su padre, estriba en la necesidad que tienen los procesos de conectarse entre sí mediante tuberías, semáforos, o simplemente mandándose señales. Tarea para la cual es imprescindible conocer ambos valores. En Unix, los procesos van a estar agrupados en conjuntos de procesos que tienen alguna característica común (por ejemplo, tener un mismo proceso padre). A estos conjuntos se les conoce como grupos de procesos y desde el sistema son controlados a través de un identificador de grupo de procesos. Para conocer a qué grupo pertenece un proceso, se utiliza la llamada getpgrp (). El identificador de grupo de procesos es heredado por los procesos hijo después de una llamada a fork (), pero también puede cambiarse creando un nuevo grupo de procesos. Esto se consigue con la llamada setpgrp (). Esta llamada hace que el proceso © E.G.R. 3 actual se convierta en el líder de un grupo de procesos. El identificador de este grupo va a coincidir con el PID del proceso que realiza la llamada y es el valor devuelto por ella. Si la llamada falla, devolverá el valor -1. El ejemplo siguiente muestra un programa que crea un proceso hijo. Los procesos padre e hijo van a mostrar su PID, PPID y su identificador de grupo de procesos. Pasados 5 segundos, el proceso padre va a morir, y el hijo va a mostrar sus nuevos PID, PPID e identificador de grupo de procesos. Pasados 10 segundos, el proceso hijo se va a convertir en el líder de un nuevo grupo de procesos y va a mostrar sus identificadores. #include <stdio.h> main () { if (fork ()==0) { /* Código del proceso hijo. */ printf ("Soy el proceso hijo mi PID es %d, mi PPID es %d, y el ID de grupo es %d\n", getpid (), getppid (), getpgrp()); sleep (10); printf ("Soy el proceso hijo y transcurrido 10 segundos, mi PID es %d, mi PPID es %d, y el ID de grupo es %d\n", getpid (), getppid (), getpgrp()); setpgrp(); printf ("Soy el proceso hijo y líder de un grupo de procesos. Ahora mi PID es %d, mi PPID es %d, y el ID de grupo es %d\n", getpid (), getppid (), getpgrp()); exit (0); } /* Código que va a ejecutar el proceso padre. */ sleep (5); printf ("Soy el proceso padre y transcurrido 5 segundos mi PID es %d, mi PPID es %d, y el ID de grupo es %d\n", getpid (), getppid (), getpgrp()); exit (0); } 2.3.2.- Identificadores de usuario y de grupo. Cada proceso tiene asociado un identificador de usuario UID y un identificador de grupo GID. El UID identifica al usuario que es responsable de la ejecución del proceso y el GID al grupo al cual pertenece el usuario. Se pueden conocer los valores que toman estos identificadores mediante las llamadas getuid () que devuelve el identificador de usuario y getgid () que devuelve el identificador del grupo. Los procesos hijos heredan estos valores del proceso padre. © E.G.R. 4