Compilación y ejecución de programas C. Llamadas del sistema

Anuncio
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
Descargar