domingo, 13 de noviembre de 2011

Procesos Linux - exec y fork

En esta entrada me gustaría hablar acerca de la creación de procesos con C cuando trabajamos con Linux. En mi caso trabajo con Debian. Debo dejar claro que hablamos de PROCESOS y no de hilos. Acerca de los hilos hablaré en entradas posteriores. En esta entrada hablaré acerca de como se crean nuevos procesos mediante código en C. De las dos posibilidades que existen. No me pararé en exceso a explicar en detalle qué es un proceso, cambios de contexto de procesos, la tabla de procesos, imagen de memoria, o el ciclo de vida de un proceso. Estos temas cabe mencionarlos, pero como ya he dicho no haré una explicación detallada acerca de ellos.

Para comenzar me gustaría hacer una explicación acerca de los procesos. Comenzando por la definición de un proceso: "Un proceso es un programa en ejecución". El proceso esta formado por el código del programa y el conjunto de datos asociados a la ejecución del programa. El proceso además posee una imagen de memoria, esto es el espacio de memoria en el que esta autorizado. La imagen de memoria puede estar referida en memoria virtual o memoria física. Además en cuanto al ciclo de vida hablaré acerca del que se tiene en cuenta en la planificación a corto plazo (ciclo de vida simple). Esta planificación es en la que se decide el siguiente proceso a ejecutar (FIFO, Round Robin, SJF). Este ciclo de vida posee 4 estados: Listo para ejecutarse, en ejecución, bloqueado y fin.
 Imagen: Basada en diapositivas Sistemas Operativos UC3M. TEMA 2
Podemos tener en ejecución tantos procesos como procesadores tenga nuestro equipo. El fin de tiempo en ejecución lo decide el algoritmo de planificación que utilice nuestro sistema operativo. Este expulsión del procesador provoca un cambio de contexto. Ya vimos en una entrada anterior que nuestro código en ensamblador tiene mas instrucciones, por tanto un cambio de contexto se puede producir en mitad de una instrucción (por ejemplo una suma).

Una  vez visto esto compredemos algo mejor el funcionamiento de los procesos en nuestro sistema. Aunque esta sea una imagen demasiado general y alejada siempre es conveniente tener una mínima idea acerca de esto (el tema del cambio de contexto es importante en la concurrencia). En esta entrada estamos hablando de procesos pesados (programa con un solo hilo de ejecución), pues en procesos ligeros la cosa cambia.
Los nuevos procesos obtienen los recursos directamente del sistema operativo o el proceso padre debe compartir recursos. Acerca de los nuevos procesos en Linux, debemos diferenciar entre: crear un nuevo proceso y ejecutar nuevos programas.

La llamada al sistema que empleamos para crear un nuevo proceso se denomina fork(). La llamada fork() crea una copia casi identica del proceso padre (se copia todo el código) y continúan ejecutandose en paralelo. El proceso padre recibe de fork() el pid del hijo, mientras que el hijo recibe un 0. El  hijo además hereda recursos del padre (ficheros, abiertos, estado de las variables, etc...) , sin embargo hay otros recursos que no se heredan como por ejemplo las señales pendientes, devuelve -1 en caso de error. La función esta declarada tal que así: size_t fork(void);
Imagen: diapositivas de Sistemas Operativos UC3M. TEMA 2
Por otra parte tenemos la función exec(). Esta función cambia la imagen del proceso actual. Lo que realiza es sustituir la imagen de memoria del programa por la de un programa diferente. Esta función normalmente la invocaremos en un proceso hijo previamente generado por fork(). Existen diferentes funciones para exec, sus declaraciones son:
  • int execl(const char *path, const char *arg, ...); 
  • int execv(const char* path, char* const argv[]); 
  • int execve(const char* path, char* const argv[], char* const envp[]); 
  • int execvp(const char *file, char *const argv[]);
En path debemos pasar la ruta del ejecutable, file: Busca el archivo ejecutable en todos los directorios especificados por PATH. Esta función retorna -1 en caso de error, en caso contrario no retorna. No retorna debido a que hemos sustituido la imagen del programa actual por la de un nuevo programa. Debemos pasar los argumentos para el nuevo programa a ejecutarse en *arg. Además se heredan los descriptores de ficheros abiertos y todas las señales pasaran a la acción por defecto.
Imagen: diapositivas de Sistemas Operativos UC3M TEMA 2
Cabe mencionar que lo que se debe hacer es dedicar al padre a crear hijos y estos que realizan trabajo por él. Además el padre puede crear mas hijos o esperar a que termine a que termine el hijo. Esta esperar se realiza con la función wait(). Esta funciona pasa al padre al estado bloqueado hasta que acabe el hijo. Recomiendo que en nuestro terminal realicemos "man" de todas las funciones para comprenderlas mejor si aún no lo entendemos.

El modelo de la función fork() posee ciertas ineficiencias:
  • Se copia una gran cantidad de datos que podrían compartirse.
  • Además si lo empleamos para cargar otra imagen es todavía peor. Todo se desecha.
En muchos sistemas de UNIX se mejora usando COW (Copy-On-Write). Esto nos ayuda de la siguiente forma:
  • Retrasa la copia de datos.
  • Se copian los datos si se intentan modificar.
  • Se copia la tabla de páginas del padre (no su contenido).
Por último expondré un ejemplo simple de estas funciones:

Este ejemplo es bastante sencillo. El proceso padre genera un hijo y espera a que termine. El proceso hijo crea una nueva imagen de programa llamando al comando "ls -l". El switch nos sirve para saber donde nos encontramos. Hay que recordar que el hijo en pid tiene 0 y el padre el pid del hijo.

Espero que esto haya servido de ayuda.
La entrada ha sido realizada con ayuda de diapositivas de Sistema Operativos de la UC3M. TEMA 2

12 comentarios:

  1. Hola, tu articulo me ha ayudado a entender el exec, gracias, está muy bien explicado. Pero tengo que decirte que tu código tiene un error. el parámetro NULL del final del array de strings no debe estar entre comillas, debe ser un NULL a secas.

    ResponderEliminar
  2. Muchas gracias, me ayudó mucho tu artículo.

    ResponderEliminar
  3. hola una pregunta necesito que mi proceso hijo ejecute unas funciones, es decir q realice la tarea q yo desee.. como hago?

    ResponderEliminar
  4. Aquí tambien encontre información util sobre creación de procesos programados en C http://isyskernel.blogspot.com/2014/02/Programacion-de-Procesos-en-GNU-Linux.html

    ResponderEliminar
  5. Buen aporte, lo minimo que se puede hacer es agradecer, Saludos.

    ResponderEliminar
  6. hola muy buen aporte muchas gracias; tengo una duda como puedo detener un proceso desde el mismo programa donde los cree por ejemplo si creo 2 y quiero deteneer uno el primero como puedo realizar esa accion
    graccias

    ResponderEliminar
    Respuestas
    1. Hola, si no me equivoco en C existe la llamada al sistema kill que recibe el id de un proceso como parámetro y lo detiene. En el padre has obtenido el id del hijo, almacenado en el ejemplo en la variable pid, por lo que podrías "matar" al proceso hijo.

      Eliminar
  7. Hola buenas noches; muy buen aporte me gusto mucho.
    Disculpa como puedo inhabilitar un proceso de 4 creados , por el ejemplo el primer proceso que sale despues de realizar su accion; porque yo los mantengo vivos con un while infinito pero estrictamente quiero que solo uno de esos 4 se detenga que me recomiendas
    GRACIAS

    ResponderEliminar