Tema 6. Gestión de Procesos e Hilos.

1. EJECUCIÓN DE PROGRAMAS MEDIANTE EXEC

Existen una serie de funciones para ejecutar programas, las cuales cargan un programa en la zona de la memoria del proceso que ejecuta la llamada, es decir un programa viejo es sustituido por otro nuevo y nunca volveremos a el para proseguir su ejecución ya que es el programa nuevo el que pasa a ejecutarse. La declaración de exec es la siguiente:

Int execl (char *path, char *arg0,... char *argn, (char *)0);
Int execv (char *path, char *argv[ ]);
Int execle(char *path, char *arg0,... char *argn, (char *)0, char *envp[ ]);
Int execve(char *path, char *argv[ ], char *envp[ ]);
Int execlp(char *file, char *argv[ ], char *argn, (char *)0);
Int execvp(char *file, char *argv[ ]);
Path: ruta del fichero ordinario ejecutable.
File: nombre del fichero ejecutable.
Arg0 y argn: son punteros a cadenas de caracteres que constituyen una lista de argumentos que se le pasa al nuevo programa.
Argv: array de punteros a cadenas de caracteres.
Envp: array de punteros a cadenas de caracteres que constituye el entorno en el que se ejecutara el nuevo programa.
Si exec no se ejecuta bien nos devolverá -1 y en errno estará el código del tipo de error que se ha producido.

La estructura de un fichero ejecutable consta de:

  1. La cabecera principal: contiene el total de secciones del programa, la dirección de inicio de ejecución y el numero mágico del programa que identifica que tipo de fichero es.
  2. Cabeceras de sección: contienen el tamaño de la sección, el total de direcciones virtuales que ocupara durante su ejecución y otra información.
  3. Secciones con el código del programa y las variables globales.
  4. Secciones con tablas de símbolos, información para el depurador, etc.

2. CREACIÓN DE PROCESOS (FORK)

El proceso que invoca a fork se le llama proceso padre mientras que al proceso creado se le llama proceso hijo. La declaración es la siguiente:

#include <sys/types.h>
pid_t fork ();

La llamada a fork hace que el proceso actual se duplique. A la salida de fork, los dos procesos tienen una copia idéntica del contexto de nivel de usuario excepto el valor de pid, que para el proceso padre toma el valor del PID del proceso hijo, mientras que para el proceso hijo toma el valor 0. El único proceso que no se crea con la llamada a fork es el proceso 0 creado por el núcleo del sistema. Si fork falla nos devolverá -1.

Cuando realizamos una llamada a fork, el núcleo del sistema realiza las siguientes operaciones: Buscara una entrada libre en la tabla de procesos y la reserva para el proceso hijo.

Asigna un PID al proceso hijo, el cual es invariable y único durante toda la vida del proceso y además constituirá la clave para poder controlarlo desde otros procesos. Realiza una copia del contexto del nivel de usuario del proceso padre para el proceso hijo.

También se copiaran las tablas de control de ficheros locales del proceso padre al proceso hijo. Vuelve al proceso padre el PID del proceso hijo y el proceso hijo le devuelve el valor 0.

3. TERMINACIÓN DE PROCESOS(EXIT Y WAIT)

La declaración de exit es la siguiente:

#include <stdlib.h>
void exit (int status);

Esta llamada termina la ejecución de un proceso y le devuelve el valor de status al sistema. Para consultarlo podemos utilizar la variable entorno?. Si efectuamos el retorno sin devolver ningún valor en concreto, el resultado devuelto al sistema estará indefinido.

Las consecuencias de exit son:

  • Las funciones registradas por atexit son invocadas en orden inverso a como fueron registradas.
  • El contexto del proceso es descargado de memoria por lo que todo relacionado con el proceso quedara cerrado.
  • Si el proceso padre esta ejecutando una llamada a wait se produce un aviso de finalización del proceso hijo y se le envían los 8 bits menos significativos de status.
  • Si se produce lo contrario al paso anterior, el proceso hijo se convierte en un proceso zombi ocupando una entrada en la tabla de procesos del sistema y su contexto es descargado de la memoria.

La declaración de wait es la siguiente:

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int *stat_loc);

Esta llamada suspende la ejecución del proceso que la invoca hasta que alguno de los procesos hijos termina. Puede ocurrir que algún proceso hijo termine de forma anormal, exigiendo unas macros que muestran la terminación del proceso:

  • WIFEXITED, devuelve verdad a cualquier valor distinto de 0 cuando el proceso termina con una llamada a exit o wait.
  • WIXTSTATUS, devuelve los 8 bits menos significativos que exit le pasa al proceso padre.
  • WIFSIGNALED, termina debido a alguna señal (signal).
  • WTERMSIG, numero de la señal que ha caducado la terminación del proceso.
  • WSTOPSIG, fichero con volcado de la señal.
  • WIFSTOPPED, proceso parado.
  • WSTOPSIG, numero de la señal que ha caducado la parada del proceso.

Si durante la llamada a wait se produce algún error nos devolverá -1.

4. INFORMACIÓN SOBRE PROCESOS

IDENTIFICADORES DE PROCESO

Todos los procesos tienen dos números, el identificador de proceso que es el PID y el identificador del proceso padre que es el PPID que a diferencia del PID este puede variar. Esto ocurre cuando el proceso padre muere y el PPID pasa al proceso hijo poniéndose el valor a 1. Para saber los valores de los procesos utilizaremos:

#include <types.h>
pid_t getpid ();
pid_t getppid ();

Para determinar a que grupo pertenece un proceso utilizaremos:
#include <sys/types.h>
pid_t getpgrp();

Para cambiar el identificador de grupo de procesos utilizaremos: #include <sys/types.h>
pid_t setpgrp ();

IDENTIFICADORES DE USUARIO Y DE GRUPO

El núcleo asocia a cada proceso dos identificadores de usuario, UID(identificador real) y EUID(identificador del usuario efectivo) y dos identificadores para el grupo, GID(identificador del grupo real) y EGID(identificador del grupo efectivo).

El UID identifica al usuario que es responsable de la ejecución del proceso y el GID al grupo al cual pertenece el usuario.

El EUID se usa para determinar el propietario de los ficheros recién creados, comprobar la mascara de permisos de acceso a ficheros y los permisos para enviar señales a otros procesos. El UID y EUID coinciden, pero si un proceso ejecuta un programa que pertenece a otro usuario y que tiene activo el bit S_ISUID el proceso cambia su EUID que toma el valor del UID del nuevo usuario.

Con respecto al identificador de grupo efectivo pasa exactamente lo mismo.

Bajate esta documentación en un archivo Acrobat Reader

Imagen que hace referencia a un archivo para Acrobat Reader