txipi:blog
  • About
  • Not Found? lee esto

Curso de programación en C para GNU/Linux (V)

03Nov06

En la pasada entrega estuvimos enredando con los directorios y con los modos de acceso a los ficheros. Vamos a tratar ahora la gestión de múltiples procesos y pronto entraremos en la comunicación entre ellos ;-)

 

Creación y duplicación de procesos

Una situación muy habitual dentro de un programa es la de crear un nuevo proceso que se encargue de una tarea concreta, descargando al proceso principal de tareas secundarias que pueden realizarse asíncronamente o en paralelo. Linux ofrece varias funciones para realizar esto: system(), fork() y exec().

Con system() nuestro programa consigue detener su ejecución para llamar a un comando de la shell (“/bin/sh” típicamente) y retornar cuando éste haya acabado. Si la shell no está disponible, retorna el valor 127, o –1 si se produce un error de otro tipo. Si todo ha ido bien, system() devuelve el valor de retorno del comando ejecutado. Su prototipo es el siguiente:

int system(const char *string);

Donde “string” es la cadena que contiene el comando que queremos ejecutar, por ejemplo:

system(“clear”);

Esta llamada limpiaría de caracteres la terminal, llamando al comando “clear”. Este tipo de llamadas a system() son muy peligrosas, ya que si no indicamos el PATH completo (“/usr/bin/clear”), alguien que conozca nuestra llamada (bien porque analiza el comportamiento del programa, bien por usar el comando strings, bien porque es muy muy muy sagaz), podría modificar el PATH para que apunte a su comando clear y no al del sistema (imaginemos que el programa en cuestión tiene privilegios de root y ese clear se cambia por una copia de /bin/sh: el intruso conseguiría una shell de root).

La función system() bloquea el programa hasta que retorna, y además tiene problemas de seguridad implícitos, por lo que desaconsejo su uso más allá de programas simples y sin importancia.

La segunda manera de crear nuevos procesos es mediante fork(). Esta función crea un proceso nuevo o “proceso hijo” que es exactamente igual que el “proceso padre”. Si fork() se ejecuta con éxito devuelve:

  • Al padre: el PID del proceso hijo creado.
  • Al hijo: el valor 0.

Para entendernos, fork() clona los procesos (bueno, realmente es clone() quien clona los procesos, pero fork() hace algo bastante similar). Es como una máquina para replicar personas: en una de las dos cabinas de nuestra máquina entra una persona con una pizarra en la mano. Se activa la máquina y esa persona es clonada. En la cabina contigua hay una persona idéntica a la primera, con sus mismos recuerdos, misma edad, mismo aspecto, etc. pero al salir de la máquina, las dos copias miran sus pizarras y en la de la persona original está el número de copia de la persona copiada y en la de la “persona copia” hay un cero:

cursoc03.gif

Duplicación de procesos mediante fork().

En la anterior figura vemos como nuestro incauto voluntario entra en la máquina replicadora con la pizarra en blanco. Cuando la activamos, tras una descarga de neutrinos capaz de provocarle anginas a Radiactivoman, obtenemos una copia exacta en la otra cabina, sólo que en cada una de las pizarras la máquina ha impreso valores diferentes: “123”, es decir, el identificativo de la copia, en la pizarra del original, y un “0” en la pizarra de la copia. No hace falta decir que suele ser bastante traumático salir de una máquina como esta y comprobar que tu pizarra tiene un “0”, darte cuenta que no eres más que una vulgar copia en este mundo. Por suerte, los procesos no se deprimen y siguen funcionando correctamente.

Veamos el uso de fork() con un sencillo ejemplo:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
	pid_t pid;

	if ( (pid=fork()) == 0 )
	{ /* hijo */
		printf("Soy el hijo (%d, hijo de %d)\n", getpid(),
        getppid());
	}
	else
	{ /* padre */
		printf("Soy el padre (%d, hijo de %d)\n", getpid(),
        getppid());
	}

	return 0;
}

Guardamos en la variable “pid” el resultado de fork(). Si es 0, resulta que estamos en el proceso hijo, por lo que haremos lo que tenga que hacer el hijo. Si es distinto de cero, estamos dentro del proceso padre, por lo tanto todo el código que vaya en la parte “else” de esa condicional sólo se ejecutará en el proceso padre. La salida de la ejecución de este programa es la siguiente:

txipi@neon:~$ gcc fork.c –o fork
txipi@neon:~$ ./fork
Soy el padre (569, hijo de 314)
Soy el hijo (570, hijo de 569)
txipi@neon:~$ pgrep bash
314

La salida de las dos llamadas a printf(), la del padre y la del hijo, son asíncronas, es decir, podría haber salido primero la del hijo, ya que está corriendo en un proceso separado, que puede ejecutarse antes en un entorno multiprogramado. El hijo, 570, afirma ser hijo de 569, y su padre, 569, es a su vez hijo de la shell en la que nos encontramos, 314. Si quisiéramos que el padre esperara a alguno de sus hijos deberemos dotar de sincronismo a este programa, utilizando las siguientes funciones:

pid_t wait(int *status)
pid_t waitpid(pid_t pid, int *status, int options);

La primera de ellas espera a cualquiera de los hijos y devuelve en la variable entera “status” el estado de salida del hijo (si el hijo ha acabado su ejecución sin error, lo normal es que haya devuelto cero). La segunda función, waitpid(), espera a un hijo en concreto, el que especifiquemos en “pid”. Ese PID o identificativo de proceso lo obtendremos al hacer la llamada a fork() para ese hijo en concreto, por lo que conviene guardar el valor devuelto por fork(). En el siguiente ejemplo combinaremos la llamada a waitpid() con la creación de un árbol de procesos más complejo, con un padre y dos hijos:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
	pid_t pid1, pid2;
	int status1, status2;

	if ( (pid1=fork()) == 0 )
	{ /* hijo */
		printf("Soy el primer hijo (%d, hijo de %d)\n",  getpid(), getppid());
	}
	else
 	{ /*  padre */
 		if ( (pid2=fork()) == 0 )
 		{ /* segundo hijo  */
 			printf("Soy el segundo hijo (%d, hijo de %d)\n",  getpid(), getppid());
		}
		else
		{ /* padre */
/* Esperamos al primer hijo */
			waitpid(pid1, &status1, 0);
/* Esperamos al segundo hijo */
			waitpid(pid2, &status2, 0);
			printf("Soy el padre (%d, hijo de %d)\n", getpid(), getppid());
 		}
	}

	return 0;
}

El resultado de la ejecución de este programa es este:

txipi@neon:~$ gcc doshijos.c –o doshijos
txipi@neon:~$ ./ doshijos
Soy el primer hijo (15503, hijo de 15502)
Soy el segundo hijo (15504, hijo de 15502)
Soy el padre (15502, hijo de 15471)
txipi@neon:~$ pgrep bash
15471

Con waitpid() aseguramos que el padre va a esperar a sus dos hijos antes de continuar, por lo que el mensaje de “Soy el padre…” siempre saldrá el último.

Se pueden crear árboles de procesos más complejos, veamos un ejemplo de un proceso hijo que tiene a su vez otro hijo, es decir, de un proceso abuelo, otro padre y otro hijo:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
	pid_t pid1, pid2;
	int status1, status2;

	if ( (pid1=fork()) == 0 )
	{ /* hijo (1a generacion) = padre */
		if ( (pid2=fork()) == 0 )
		{ /* hijo (2a generacion)  = nieto */
			printf("Soy el nieto (%d, hijo de %d)\n",
getpid(), getppid());
		}
		else
		{ /* padre (2a generacion) = padre */
			wait(&status2);
			printf("Soy el padre (%d, hijo de %d)\n",
getpid(), getppid());
		}
	}
	else
	{ /* padre (1a generacion) = abuelo */
		wait(&status1);
		printf("Soy el abuelo (%d, hijo de %d)\n", getpid(),
getppid());
	}

	return 0;
}

Y el resultado de su ejecución sería:

txipi@neon:~$ gcc hijopadrenieto.c -o hijopadrenieto
txipi@neon:~$ ./hijopadrenieto
Soy el nieto (15565, hijo de 15564)
Soy el padre (15564, hijo de 15563)
Soy el abuelo (15563, hijo de 15471)
txipi@neon:~$ pgrep bash
15471

Tal y como hemos dispuesto las llamadas a wait(), paradójicamente el abuelo esperará a que se muera su hijo (es decir, el padre), para terminar, y el padre a que se muera su hijo (es decir, el nieto), por lo que la salida de este programa siempre tendrá el orden: nieto, padre, abuelo. Se pueden hacer árboles de procesos mucho más complejos, pero una vez visto cómo hacer múltiples hijos y cómo hacer múltiples generaciones, el resto es bastante trivial.

Otra manera de crear nuevos procesos, bueno, más bien de modificar los existentes, es mediante el uso de las funciones exec(). Con estas funciones lo que conseguimos es reemplazar la imagen del proceso actual por la de un comando o programa que invoquemos, de manera similar a como lo hacíamos al llamar a system(). En función de cómo queramos realizar esa llamada, elegiremos una de las siguientes funciones:

int execl( const char *path, const char *arg, ...);
int execlp( const char *file, const char *arg, ...);
int execle( const char * path, const  char  *arg  ,  ..., char * const envp[]);
int execv( const char * path, char *const argv[]);
int execvp( const char *file, char *const argv[]);
int  execve(const  char  *filename, char *const argv [], char *const envp[]);

El primer argumento es el fichero ejecutable que queremos llamar. Las funciones que contienen puntos suspensivos en su declaración indican que los parámetros del ejecutable se incluirán ahí, en argumentos separados. Las funciones terminadas en “e” ( execle() y execve() ) reciben un último argumento que es un puntero a las variables de entorno. Un ejemplo sencillo nos sacará de dudas:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
	char *args[] = { "/bin/ls", NULL };

	execv("/bin/ls", args);

	printf("Se ha producido un error al ejecutar execv.\n");

	return 0;
}

La función elegida, execv(), recibe dos argumentos, el path al fichero ejecutable (“/bin/ls”) y un array con los parámetros que queremos pasar. Este array tiene la misma estructura que argv, es decir, su primer elemento es el propio programa que queremos llamar, luego se va rellenando con los argumentos para el programa y por último se finaliza con un puntero nulo (NULL). El printf() final no debería salir nunca, ya que para ese entonces execv() se habrá encargado de reemplazar la imagen del proceso actual con la de la llamada a “/bin/ls”. La salida de este programa es la siguiente:

txipi@neon:~$ gcc execv.c -o execv
txipi@neon:~$./execv
doshijos    execv    fil2    files.c         hijopadrenieto.c
doshijos.c  execv.c  fil2.c  hijopadrenieto


Filed under: Informática   |  21 Comments
Tags: C, curso, gcc, GNU/Linux, programación

21 Responses to “Curso de programación en C para GNU/Linux (V)”  

Feed for this Entry Trackback Address
  1. 1 M on Diciembre 25, 2006 said:

    sos grande.. si me toman fork y apruebo sistemas operativos, es gracias a esta pagina :)

    Responder
  2. 2 Elias Morales Escalante on Febrero 6, 2007 said:

    la cracion de procesos que muestra esta pagina esta muy buena
    en la cual me srvio bastante ahora lo quiero saber es como compilarlo en linux el archivo creado en C

    Responder
  3. 3 zixit on Marzo 12, 2007 said:

    Muy bien explicado una excelente aportacion Gracias por la dedicacion y el tiempo :)

    (muchas paginas solo te confunden mas, esta te explica paso a paso)

    Responder
  4. 4 juan diego on Marzo 14, 2007 said:

    Soy un estudiante de la universidad de alicante. En un ejercicio de una asignatura debemos crear un arbol de procesos. Me ha sido muy util esta pagina para saber como crear hermanos, ya que como yo lo hacia me creaba tres hijos, un dos los cuales tenia dos padre, un padre era el hermano de los tres, y el otro padre era uno de los hijos. <un jaleo, asi que muchas gracias. No se muy bien como funciona el foro asi que agradeceria una respuesta personal a mi correo.

    Mi duda es la siguiene:

    Quiero poner un "for" para hacer un arbol segun un parametro que el usuario pasa por parametro al programa, pero no se donde poner el "for".
    mUCHAS GRACIAS Y UN SALUDO.

    Responder
  5. 5 Kattia on Marzo 19, 2007 said:

    Excelente página! Muy bien explicada

    Responder
  6. 6 txipi on Marzo 23, 2007 said:

    Me alegro que os haya servido el tutorial :-)

    @juan diego: ¿cómo es ese parámetro que te pasan? Quizá tengas que parsearlo primero con strtok() o similares.

    Responder
  7. 7 Ángel on Abril 1, 2007 said:

    Es todo un lujo contar con esta información para aprender, muy buenos ejemplos, y muy claritos. gracias

    Responder
  8. 8 Emilio on Mayo 26, 2007 said:

    Cuando haces un:

    pid=wait(&status);

    cómo puedes extraer el valor de salida, el status da un número raro cuando digo que salga con 1 me da un 256, no sé a qué se debe o si tengo que hacer un & con algún valor.

    Responder
  9. 9 Carlos on Septiembre 23, 2007 said:

    COMO LE HAGO PARA PASAR DATOS DE LOS HIJOS AL PADRE CUANDO TERMINARON UNA OPERACION DE CALCULAR ALGO

    Responder
  10. 10 Chori on Octubre 4, 2007 said:

    muchas gracias por esta guia, esta muy clara.

    @Carlos: yo no soy ningún experto ni mucho menos en este tema, pero por lo que aprendí en mi clase de Sistemas Operativos podes usar algún Pipe (tubería). para no confundirte te diría que lo mejor es que busques en Internet como funcionan.

    Saludos

    Responder
  11. 11 manuzafra on Diciembre 27, 2007 said:

    hola, muy buen tutorial, no tenia ni idea de hijos y padres y cosas de esas, y me lo has aclarado todo.

    Una cosa, necesito saber mas sobre la ultima parte, sobre el uso de funciones exec()
    tengo que realizar una practica que haga basicamente lo que hace la funcion system() pero sin usar system() claro esta.

    Te puedo preguntar, me pudes informar, sabes donde hay info especifica.
    Ya le he dado mil vueltas al google.
    MUchas gracias. ¿Puedes responderme a mi email?. Gracias

    Responder
  12. 12 txipi on Diciembre 28, 2007 said:

    @manuzafra: es bastante sencillo el uso de exec() y similares. Solamente tienes que preparar un array tal y como lo necesita la función y listo. De todas maneras, para llamar a comandos lo mejor y más cómodo es popen().

    Responder
  13. 13 manuzafra on Diciembre 28, 2007 said:

    Pero para hacer la alternativa a system(), utilizarias execve() o popen().
    Y la variable environ, para que sirve???

    Responder
  14. 14 antoñito on Enero 9, 2008 said:

    Podrias decir como supeditar la creacion de hijos a un numero pedido por teclado.
    Es decir, el programa te pide un numero por teclado, mas o menos bajo, <10 por ejemplo y el programa debe crear tantos hijos (o padres o nietos, el caso es que sean distintos procesos) como el numero pedido.

    Responder
  15. 15 marysol on Enero 17, 2008 said:

    porfa hallar el tiooo

    Responder
  16. 16 Edwin on Enero 24, 2008 said:

    gracias por los items del tutorial, me han ayudado mucho para comprender la relacion entre procesos. Ahora estoy construyendo un pequeño shell como proyecto, quisiera un poco mas de informacion sobre el EXEC() y señales de enmascaramiento (SIGPROCMASK) ¿donde podria encontrarla please?.

    Responder
  17. 17 mc_disck on Enero 27, 2008 said:

    Ke onda, está chido el tutorial pero tengo una duda con respecto a la familia de funciones exec, quisiera saber si se puede llamar a cualquier programa ejecutable es decir que si puedo llamar a un programa que yo haya hecho??

    Responder
  18. 18 Clima on Mayo 6, 2008 said:

    Hola, con tres procesos A, B, C en que A es Abuelo, B, Padre y C nieto, tenemos Pipes entre A==B==C

    Seria posible crear una Pipe entre A y C directamente para que C escribiera algun mensaje a A? Como se debería hacer??

    Gracias, J.

    Responder
  19. 19 Oriol on Octubre 13, 2008 said:

    Buenas.
    Exelente blog bajo mi humilde punto de vista. Me estoy iniciando en el mundo de los procesos, y la verdad es que me ha sido de gran ayuda. Eso si, estoy haciendo una practica y no consigo sacarla, haber si me podeis hechar un pequeño cable.
    Tengo que sacar 100 hijos, y los dos primeros tienen que tener un nieto, pero este nieto saldrá antes que sus padres, y por ultimo, despues de los 100 hijos, aparecerá el padre. Gracias por la ayuda de antemano ;)

    Oriol

    Responder
  20. 20 Guille on Febrero 16, 2010 said:

    Me encanto la explicación de la maquina de clonar procesos. Muy bueno el post. Me sirvió mucho sobre todo para entender el funcionamiento de fork().

    Responder

  1. 1 Curso de programación en C para GNU/Linux (VI) « txipi:blog

Leave a Reply

Clic para cancelar respuesta.



  • comentarios recientes...

    posicionamiento on Bajar presentaciones no dispon…
    Alonso on Cómo eliminar la contraseña de…
    ilda on Ayahuasca, hacking mental
    ilda on Ayahuasca, hacking mental
    antonio on Ayahuasca, hacking mental
  • Blogroll

    • Feeds en Bloglines
    • Planet e-ghost
    • Software Libre en la UD
  • Enlaces

    • e-ghost
    • Página web en la UD
    • txipi:wiki
    • txipinet, antigua web
  • Posts Más Vistos

    • Cómo eliminar la contraseña de la BIOS
    • Bajar presentaciones no disponibles para descarga en Slideshare
    • Manual de supervivencia en Internet (III): Navegar sin dejar rastro
    • ¿Qué tarjeta Wi-Fi me compro?
    • Curso de programación en C para GNU/Linux (V)
    • Curso de programación en C para GNU/Linux (II)
    • Estudio sobre el diseño de GUIs (I): El ojo humano
    • Curso de programación en C para GNU/Linux (I)
    • Ekiga, VoIP libre (¡olvídate de Skype!)
    • Curso de programación en C para GNU/Linux (VII)
  • sindicación (RSS)

    Suscríbete al blog
    Suscríbete al blog por e-mail
  • RSS twitter

    • txipi: ¿conocéis algo como Google Calendar Notifier para Firefox 3.6+?
    • txipi: @maybrain: las oficinas de patentes tienen más poder cuantas más patentes haya, así que no son el juez indicado :S
    • txipi: @maybrain: las patentes de software son una fuente inagotable de risas y lágrimas a partes iguales, disparates a miles #oracle #microsoft
    • txipi: @suzzlo: lo duro es que no cambian ni el asunto... grrrrr
    • txipi: RT @GuyKawasaki: TED talk: Learn to save the worlds with games http://tinyurl.com/2ugr4w7
  • RSS posts en el blog de software libre

    • Cursillos de verano del e-ghost 2010
    • El presidente de la Free Software Foundation – Europa se viene a Deusto
    • Arranca la Oficina Técnica de apoyo al Software Libre del Gobierno Vasco
    • ¿Qué pasa con los cursillos de julio de este año?
    • MoodleMoot Euskadi 2010 en Tknika
    • La comunidad del software libre vasca me defrauda
    • Conocimiento libre y software libre de la mano de Azkue Fundazioa
    • Jornadas de OpenERP 2010 en Deusto
    • Akademy-es 2010 en Bilbao
    • Micropost exculpatorio
  • licencia

    some rights reserved

    Aviso legal: Condiciones de copia y distribución. Todos los contenidos de esta página están protegidos por la Licencia Creative Commons 2.5, salvo en los casos en los que se especifica lo contrario.


Blog de WordPress.com. • Theme: Unsleepable by Ben Gray.