Hola a todos.
Aqui anexo algunas anotaciones sobre C, espero que a alguien que pase por aqui le sea de utilidad
Estamos sobre un sistema FreeBSD 7.X usando un compilador gcc
$ gcc -v Using built-in specs. Target: i386-undermydesk-freebsd Configured with: FreeBSD/i386 system compiler Thread model: posix gcc version 4.2.1 20070719 [FreeBSD]
La librería estándar de C (libc, implementada sobre entornos GNU como glibc) usa las facilidades de la multitarea de Unix System V; el Unix System V (desde ahora SysV) es una implementación comercial de Unix, es el fundador de una de las dos familias mas importantes de Unix, la otra es Unix BSD.
En la libc el tipo pid_t está definido como un entero capaz de contener un pid. Desde ahora lo usaremos para almacenar el valor de un pid, pero solo por claridad: usar un entero es lo mismo.
Descubramos que función nos dice cual es el pid del proceso que contiene nuestro programa
pid_t getpid (void)
(la cual está definida con pid_t en unistd.h y sus/types.h) y escriba un programa cuyo objetivo sea imprimir por la salida estándar su pid. Con cualquier editor escriba el siguiente código
#include "unistd.h"
#include "sys/types.h"
#include "stdio.h"
int main(void){
pid_t pid;
pid = getpid();
printf("El pid asignado a el proceso es %d\" , pid);
return 0;
}
Guarde el programa como print_pid.c y compílalo
$ gcc -Wall -o pid.exe pid.c && ./pid.exe El pid asignado a el proceso es 87866
Ya es hora de aprender a crear procesos, pero debo añadir algunas palabras sobre que ocurre realmente durante esta acción. Cuando un programa (contenido en el proceso A) crea otro proceso (B) los dos son idénticos, tienen el mismo código, la memoria llena de los mismos datos (no la misma memoria) y el mismo estado del procesador. A partir de este punto los dos se ejecutarán de manera diferente, por ejemplo dependiendo de la entrada del usuario o algún dato aleatorio. El proceso A es el “proceso padre” mientras que el B es el “proceso hijo”; ahora es mas fácil de entender el concepto “padre de todos los procesos” dado a init. La función que crea un nuevo proceso es
pid_t fork(void)
y su nombre viene de la propiedad de bifurcar la ejecución de procesos. El número devuelto es un pid, pero merece una atención particular. Dijimos que el actual proceso se duplica en un padre y un hijo, que se ejecutarán entrelazándose con los otros procesos en ejecución, haciendo diferentes trabajos; pero inmediátamente después de la duplicación, ¿que proceso será ejecutado, el padre o el hijo? Bueno, la respuesta es simple: uno de los dos. La decisión de que proceso debe ser ejecutado la toma una parte del sistema operativo llamado planificador, y no presta atención si un proceso es padre o hijo, sino que sigue un algoritmo basado en otros parámetros.
De todas formas, es importante conocer que proceso está en ejecución, ya que el código es el mismo. Ambos procesos contendrán el código del padre y del hijo, pero cada uno de ellos debe ejecutar solo uno de los códigos. Para clarificar este concepto, veamos el siguiente algoritmo:
- FORK - Si eres HIJO EJECUTA (...) - Si eres PADRE EJECUTA (...)
que representa en un pequeño pseudocódigo el código de nuestro programa. Vamos a revelar el misterio: la función fork devuelve ‘0′ al proceso hijo y el pid del hijo al padre. Así que es suficiente comprobar si el pid devuelto es cero y sabremos que proceso esta ejecutando el código. En el lenguaje C obtendremos
int main(void){
pid_t pid;
pid = fork();
if (pid == 0)
{
CODIGO DE EL PROCESO HIJO
}
CODIGO DEL PROCESO PADRE
}
Es hora de escribir el primer ejemplo real de código multitarea: puedes grabarlo en un fichero fork_demo.c y compilarlo como se hizo anteriormente. He puesto el número de las lineas sólo por claridad. El programa se bifurcará a si mismo y el padre y el hijo escribirán algo en pantalla; la salida final será el entrelazamiento de las dos salidas (si todo va bien).
(01) #include "unistd.h"
(02) #include "sys/types.h"
(03) #include "stdio.h"
(04) int main(void)
(05) {
(05) pid_t pid;
(06) int i;
(07) pid = fork();
(08) if (pid == 0){
(09) for (i = 0; i < 8; i++){
(10) printf("-HIJO-");
(11) }
(12) return(0);
(13) }
(14) for (i = 0; i < 8; i++){
(15) printf("+PADRE+");
(16) }
(17) return(0);
(18) }
Las lineas número (01)-(03) contienen los includes de las librerías necesarias (E/S estándar, multitarea).
El main (como siempre en GNU), devuelve un entero, que es normalmente cero si el programa llega al final sin errores o un código de error si va algo mal; supondremos por ahora que todo marcha sin errores (añadiremos control de errores cuando estén claros los conceptos básicos). Luego definimos un tipo de dato para contener un pid (05) y un entero para usarlo como contador del bucle (06). Estos dos tipos, como se dijo antes, son idénticos, pero están ahí para mayor claridad.
En la linea (07) llamamos a la función fork, la cual devolverá cero al programa ejecutado en el proceso hijo y el pid del proceso hijo al padre; la comprobación está en la linea (08). Ahora el código de las lineas (09)-(13) se ejecutarán en el proceso hijo, mientras que el resto (14)-(16) las ejecutará el padre.
Las dos partes simplemente escriben 8 veces en la salida estándar la palabra “-HIJO-” o “+PADRE+”, dependiendo en que proceso se ejecuta, y luego termina devolviendo 0. Esto es verdaderamente importante, ya que sin este último “return” el proceso hijo, una vez que el bucle termine, seguirá ejecutando el código del padre (compruébalo, no dañará tu máquina, simplemente no hará lo que queremos). Por eso, un error será realmente difícil de encontrar, desde la ejecución de un programa multitarea (especialmente uno complejo) se da diferentes resultados en cada ejecución, haciendo la depuración basada en los resultados simplemente imposible.
Ejecutando el programa quizás quedes insatisfecho: no puedo asegurar de que el resultado será una mezcla real entre dos cadenas, y esto es debido a la velocidad de ejecución del pequeño bucle. Probablemente tu salida sea una sucesión de cadenas “+PADRE+” seguidas por unas de “-HIJO-” o viceversa. Sin embargo, intenta ejecutar mas de una vez el programa y el resultado podrá cambiar.
Insertando un retraso aleatorio antes de cada llamada a printf, obtendremos un mejor efecto visual de la multitarea: lo hacemos con las funciones sleep y rand.
sleep(rand()%4)
esto hace que el programa duerma durante un número aleatorio de segundos entre 0 y 3 (% devuelve el resto de una división entera). Ahora el código es
(09) for (i = 0; i < 8; i++){
(->) sleep (rand()%4);
(10) printf(”-FIGLIO-n”);
(11) }
y lo mismo para el código del padre. Guárdalo como fork_demo2.c, compílalo y ejecútalo. Ahora es mas lento, pero notamos la diferencia en el orden de salida:
$ ./fork_demo.exe +PADRE+ -HIJO- +PADRE+ -HIJO- -HIJO- +PADRE+ +PADRE+ -HIJO- +PADRE+ -HIJO- +PADRE+ -HIJO- +PADRE+ -HIJO- +PADRE+ -HIJO- $
Ahora miremos los problemas que tenemos que ahora hacer frente: podemos crear un cierto número de procesos hijo dado un proceso padre, de modo que ejecuten operaciones diferentes a las que ejecute el proceso padre en un entorno de procesamiento concurrente; a menudo, el padre necesita comunicarse con sus hijos o al menos sincronizarse con ellos, para ejecutar operaciones en el orden correcto. Un primer modo para obtener dicha sincronización entre procesos es la función wait
pid_t waitpid (pid_t PID, int *STATUS_PTR, int OPTIONS)
donde PID es el PID del proceso cuyo fin estamos esperando, STATUS_PTR un puntero a un entero el cual contendrá el estado del proceso hijo (NULL si no se necesita la información) y OPTIONS un conjunto de opciones que no debemos tener en cuenta por ahora. Este es un ejemplo de un programa en el cual el padre crea un proceso hijo y espera a que acabe
#include "unistd.h"
#include "sys/types.h"
#include "stdio.h"
int main()
{
pid_t pid;
int i;
pid = fork();
if (pid == 0){
for (i = 0; i < 14; i++){
sleep (rand()%4);
printf("-HIJO-");
}
return 0;
}
sleep (rand()%4);
printf("+PADRE+ Esperando la terminacion del hijo..");
waitpid (pid, NULL, 0);
printf("+PADRE+ ...terminado");
return 0;
}
