Está en la página 1de 25

Concurrencia en UNIX / LINUX Introduccin:

Procesos e Hilos POSIX

El estndar POSIX
POSIX: Interfaz de sistemas operativos portables. Basado en UNIX
A pesar de que UNIX es ya de por s estndar, haba muchos sabores que impositiblitaban la transportabilidad de los programas (cdigo fuente) Disponible en UNIX / LINUX En forma de subsistema en Windows NT / XP El estndar define: Tipos de datos Nombres de funciones Valores devueltos por las funciones Es habitual que los recursos se referencien mediante un nmero entero >= 0 que recibe el nombre de descriptor.

Creacin de Procesos Concurrentes


Llamada al sistema fork().
A partir de la devolucin del control por parte del sistema, existen en ejecucin dos imgenes de memoriabsicamente iguales: el padre y el hijo. Las diferencias entre ambos procesos son: PIDs de los procesos El PID del hijo es distinto al del padre. En la llamada a fork() el sistema devuelve un 0 al proceso hijo y un valor positivo distinto de 0 (pid del hijo) al padre.

Recursos compartidos entre el proceso Padre e Hijo


la imagen en memoria del proceso hijo es una copia igual a la del proceso padre, pero en distinta zonas de memoria. No se comparten las variables de memoria. Los recursos que s comparten son los dispositivos y ficheros que tuviera abiertos el proceso padre. el stdin, stdout y stderr.
Esto quiere decir que la salida estndar y salida de error estndar de padre e hijo aparecern mezcladas.

Fork () - 1
# include <stdio.h> main() { int pid; /************************************************ creacin de un proceso concurrente con el creador *************************************************/ pid=forck (); if ((pid == -1) { printf ("error en creacion de proceso hijo\n"); exit(1); } else if ( pid == 0) /* proceso hijo */ { printf ("Proceso hijo 1\n"); fflush(stdout); exit (0); /* terminacion con codigo 0 */ } else /* proceso padre */ { printf ("Proceso padre\n"); fflush(stdout); wait (0); } }

Padre

Fork () - 2
Proceso Padre Proceso Hijo

# include <stdio.h> main() { int pid; /************************************************ creacin de un proceso concurrente con el creador *************************************************/ pid=forck (); if ((pid == -1) { printf ("error en creacion de proceso hijo\n"); exit(1); } else if ( pid == 0) /* proceso hijo */ { printf ("Proceso hijo 1\n"); fflush(stdout); exit (0); /* terminacion con codigo 0 */ } else /* proceso padre */ { printf ("Proceso padre\n"); fflush(stdout); wait (0); } }

# include <stdio.h> main() { int pid; /************************************************ creacin de un proceso concurrente con el creador *************************************************/ pid=forck (); if ((pid == -1) { printf ("error en creacion de proceso hijo\n"); exit(1); } else if ( pid == 0) /* proceso hijo */ { printf ("Proceso hijo 1\n"); fflush(stdout); exit (0); /* terminacion con codigo 0 */ } else /* proceso padre */ { printf ("Proceso padre\n"); fflush(stdout); wait (0); } }

Padre

Hijo

Suponemos que se pudo crear el proceso hijo (fork devuelve nmeros no negativos) Ambos procesos continan ejecutando la siguiente sentencia al fork().

Fork () - 3
Proceso Padre Proceso Hijo

# include <stdio.h> main() { int pid; /************************************************ creacin de un proceso concurrente con el creador *************************************************/ pid=forck (); if ((pid == -1) { printf ("error en creacion de proceso hijo\n"); exit(1); } else if ( pid == 0) /* proceso hijo */ { printf ("Proceso hijo 1\n"); fflush(stdout); exit (0); /* terminacion con codigo 0 */ } else /* proceso padre */ { printf ("Proceso padre\n"); fflush(stdout); wait (0); } }

# include <stdio.h> main() { int pid; /************************************************ creacin de un proceso concurrente con el creador *************************************************/ pid=forck (); if ((pid == -1) { printf ("error en creacion de proceso hijo\n"); exit(1); } else if ( pid == 0) /* proceso hijo */ { printf ("Proceso hijo 1\n"); fflush(stdout); exit (0); /* terminacion con codigo 0 */ } else /* proceso padre */ { printf ("Proceso padre\n"); fflush(stdout); wait (0); } }

Padre

Hijo

Ambos procesos pasan a determinar el valor devuelto por fork ()

Fork () - 4
Proceso Padre Proceso Hijo

# include <stdio.h> main() { int pid; /************************************************ creacin de un proceso concurrente con el creador *************************************************/ pid=forck (); if ((pid == -1) { printf ("error en creacion de proceso hijo\n"); exit(1); } else if ( pid == 0) /* proceso hijo */ { printf ("Proceso hijo 1\n"); fflush(stdout); exit (0); /* terminacion con codigo 0 */ } else /* proceso padre */ { printf ("Proceso padre\n"); fflush(stdout); wait (0); } }

# include <stdio.h> main() { int pid; /************************************************ creacin de un proceso concurrente con el creador *************************************************/ pid=forck (); if ((pid == -1) { printf ("error en creacion de proceso hijo\n"); exit(1); } else if ( pid == 0) /* proceso hijo */ { printf ("Proceso hijo 1\n"); fflush(stdout); exit (0); /* terminacion con codigo 0 */ } else /* proceso padre */ { printf ("Proceso padre\n"); fflush(stdout); wait (0); } }

Padre

Hijo El padre ve que fork() devuelve el PID del hijo, y sigue una rama del if. Espera bloqueado (wait) a que el hijo termine. El hijo ve que fork() devuelve un 0 y sigue la otra rama del if.

Identidades de padre e hijo


El proceso padre sabe de forma explcita la identidad de sus procesos hijo
Es el valor devuelto por fork().

El proceso hijo observa que fork() devuelve un 0


0 no es un PID vlido por lo que se identifica como proceso hijo.

Los procesos hijos pueden tener a su vez sus propios hijos.


Se establece una jerarqua de procesos debdia a la relacin padre/hijo.

Funciones relacionadas:
Para saber su propio PID: getpid(); Para saber el PID del padre: getppid(); Para no tener problemas, debemos incluir los prototipos que estn en #include <sys/types.h> #include <unistd.h>

wait ()
Cuando en UNIX un proceso termina, hace que terminen todos sus hijos (finalizacin en cascada).
Para hacer que el hijo sobreviva a la muerte del padre (ej uso de nohup en el shell) el proceso hijo, ya no es hijo de su padre, sino que es adoptado por el proceso init que siempre est vivo.

Para hacer que el padre espere a que el hijo termine (y no muera antes que su hijo, haciendo que el hijo tambin muera), se debe usar la funcin wait ().
Esta funcin devuelve el pid del hijo terminado. Si tiene varios hijos el mismo padre, wait() desbloquea al padre cuando CUALQUIER hijo termina. Si tenemos que esperar a que finalicen todos, habr un wait() por cada hijo. Con la funcin waitpid() se puede esperar a la finalizacin de un hijo en particular (el padre conoce explcitamente los pids de sus hijos)

Ejecucin de distinto cdigo: exec__()


Si el nico procedimiento de creacin de procesos visto (fork()) slo crea clones del padre, no sera posible crear procesos que realicen tareas totalmente diferentes a las de su progenitor. Hay varias llamadas al S.O. que substituyen la imagen del cdigo que ha heredado el hijo por otra, permitiendo ejecuciones de distintos programas en los procesos padre e hijo. Estas llamadas son:
execl (camino, arg1, arg2,...) execlp() execle() execv() Execvp()

Ejemplo de uso de exec__()


#include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> main () { pid_t childpid; int status; if ((childpid = fork()) == -1) { perror ("Error al ejecutar fork"); exit(-1); } else if (childpid == 0) { // Hijo if (execl("/bin/ls", "ls", "-l", NULL) < 0) { // nombre, argv[0], argv[1]... perror("Fallo en la ejecucin de ls."); exit(-1); } exit(0); } else if (childpid != wait(&status)) perror("El hijo ha terminado de forma anormal."); exit(0); }

Depuracin de programas (1)


Depurar un programa que crea procesos concurrentes es mucho ms complicado que con procesos secuenciales.
Ahora en la pantalla se mezclarn los mensajes generados por todos los procesos concurrentes, y deber identificarse adecuadamente quin es el proceso que gener cada uno de ellos.

Garantizar la causalidad en el orden de los mensajes


En un principio es imposible saber el orden en que se van a ejecutar los procesos concurrentes, el orden depende de las velocidades relativas de ejecucin de los procesos, de la carga del sistema, del algoritmo de planificacin, y de otras circunstancias. Se utiliza la funcin fflush (), que tiene como misin vaciar el bffer asociado al fichero que se le pasa como nico argumento. Si a cada sentencia que visualiza un mensaje (por ejemplo, usando printf o puts) la seguimos de una sentencia fflush (stdout), queda asegurado que el mensaje se imprime antes de continuar con la ejecucin del resto del proceso. ATENCIN: la pantalla (dispositivo stdout) es un recurso compartido por todos los procesos, y su acceso en manera concurrente debe ser regulado adecuadamente, como se ver en prximas sesiones de laboratorio.

IMPORTANTE: el uso de fflush () penaliza la concurrencia, y slo debe utilizarse cuando sea estrictamente necesario, cono en el caso de una traza para depuracin.

Depuracin de programas 2
printf (msj1); F(....); printf (msj2);
Buffer de salida El uso de buffers de E/S aumenta la eficiencia del sistema, permitiendo un mayor grado de concurrencia. Es decir, el proceso puede seguir ejecutando incluso antes de que la operacin de salida se haya completado.

Depuracin de programas 3
printf (msj1); F(....); printf (msj2);
msj1 El buffer de salida no se descarga inmediatamente en el dispositivo (p.ej. pantalla). La escritura se hace Cuando est lleno Cuando se le indica que as lo haga A intervalos regulares de tiempo. Cuando el S.O. considera oportuno, p.ej. cuando no tiene otra tarea ms importante que hacer

Depuracin de programas 4
printf (msj1); F(....); printf (msj2);
msj1 Error grave Posiblemente genera un fichero Core

El proceso termina y el S.O. elimina todas las tareas que este proceso tena pendientes, entre ellas las de vaciar sus buffers de salida. El mensaje msj1 no llega a imprimirse.

Depuracin de programas 5
printf(msj1); fflush(NULL); F(....); printf(msj2); fflush (NULL);
Buffer de salida

Depuracin de programas 6
printf(msj1); fflush(NULL); F(....); printf(msj2); fflush (NULL);
msj1 printf: el mensaje va al buffer fflush: el proceso pide al S.O. que vace el buffer, y no continua su ejecucin hasta que el S.O. haya enviado el contenido del buffer al dispositivo. Est penalizando la concurrencia.

Depuracin de programas 7
Pantalla msj1

printf(msj1); fflush(NULL); F(....); printf(msj2); fflush (NULL);

Al producirse el error, el proceso termina, pero ya sabemos que el error se produjo al entrar en la funcin F, puesto que ha aparecido msj1 pero no msj2.

Error grave

Hilos POSIX - 1
Permiten la ejecucin concurrente de varias secuencias de instrucciones asociadas a diferentes funciones dentro de un mismo proceso Los hilos hermanos entre s comparten la misma imagen de memoria, es decir:
Cdigo, variables de memoria globales, y los dispositivos y ficheros que tuviera abierto el padre.

Cdigo Variables Globales PC: localiza la siguiente Instruccin a ejecutar en la funcin principal Reg. CPU Pila: Variables locales de la funcin F

Los hilos hermanos no comparten:


El Contador de Programa: cada hilo podr ejecutar una seccin distinta de cdigo. Los registros de CPU. La pila en la que se crean las variables locales de las funciones a las que se va llamando despus de la creacin del hilo. El estado: puede haber hilos en ejecucin, listos, o bloqueados en espera de un evento.

Hilos POSIX - 2
Permiten la ejecucin concurrente de varias secuencias de instrucciones asociadas a diferentes funciones dentro de un mismo proceso Los hilos hermanos entre s comparten la misma imagen de memoria, es decir:
Cdigo, variables de memoria globales, y los dispositivos y ficheros que tuviera abierto el padre.

Proceso padre

Hilo1: Ejecuta F()

Hilo2: Ejecuta G() Cdigo Variables Globales

Los hilos hermanos no comparten:


El Contador de Programa: cada hilo podr ejecutar una seccin distinta de cdigo. Los registros de CPU. La pila en la que se crean las variables locales de las funciones a las que se va llamando despus de la creacin del hilo. El estado: puede haber hilos en ejecucin, listos, o bloqueados en espera de un evento.

PC: localiza la siguiente Instruccin a ejecutar en la funcin F Reg. CPU Pila: Parms. de llamada a la funcin F Variables locales de la funcin F

PC: localiza la siguiente Instruccin a ejecutar en la funcin G Reg. CPU Pila: Parms. de llamada a la funcin G Variables locales de la funcin G

Hilos - 3
Consumen menos memoria. Cuesta menos crearlos. Ya tienen disponible un mecanismo de comunicacin entre ellos: las variables globales que son compartidas.
Si bien el uso de variables globales est totalmente desaconsejado en otras circunstancias, en concurrencia a travs de hilos es el mtodo idneo para compartir informacin. Cuando alguien se atreve a programar de manera concurrente con hilos, se da por supuesto que sabe programar de forma correcta, y podr afrontar sin problemas las posibles tareas de depuracin.

Es ms sencillo hacer un cambio de contexto entre hilos, lo que los hace ideales para la realizacin de tareas concurrentes cooperativas. La creacin de hilos no es una tarea estndar de UNIX
Hay que incluir una biblioteca de funciones especfica en la lnea de compilacin: gcc fuente.c -lpthread

Funciones para Hilos POSIX


Deben incluirse los ficheros de cabecera
#include <stdio.h> #include <pthread.h>

Principal pthread_create()

pthread_create(&id, NULL, funcin, args): crea un hilo que se asocia a la ejecucin de la funcin que se indica en forma de puntero a funcin, siempre de tipo void; tambin se adjunta la lista de argumentos, pthread_exit(): final de la ejecucin de un hilo. Ser la ltima sentencia ejecutada por el hilo. pthread_kill():detiene la ejecucin del hilo especificado pthread_join(): espera la finalizacin del hilo especificado. pthread_self(): permite que un hilo se identifique a s mismo

Hilo 1 (Principal)

Hilo 2 (funcin F)

Principal ejecuta pthread_join() Espera a que el hilo termine y luego sigue.

Hilo 2 ejecuta pthread_exit()

Ejemplo Creacin de hilos


#include <stdio.h> #include <pthread.h> int argext[2]={2,3}; // Variables Globales-Variables compartidas void *mifuncion(void *arg) { printf("Soy mifuncion...\n"); printf("Arg. ext. 1: %d. Arg. ext. 2: %d\n", argext[0], argext[1]); printf("Trato de modificar los argumentos...\n"); argext[0] = 7; argext[1] = 8; printf("Arg. ext. 1: %d. Arg. ext. 2: %d\n", argext[0], argext[1]); printf("Saliendo de mifuncion...\n"); fflush(stdout); // me aseguro que el mensaje se escribe pthread_exit(NULL); } main (){ pthread_t tid; printf("Creando hilo...\n"); fflush(stdout); pthread_create(&tid, NULL, mifuncion, (void *) NULL); printf("Hilo creado. Esperando su finalizacion...\n"); fflush(stdout); pthread_join(tid, NULL); printf("Hilo finalizado...\n");fflush(stdout);

Ejercicio 1
Un programa que cree N procesos hijos, tal que todos sean "hermanos' entre s, es decir, que todos sean hijos directos del programa principal main (). Un programa que cree N procesos hijos, tal que el proceso main () slo puede tener un hijo, que a su vez slo puede tener otro nico proceso hijo, y as sucesivamente.
P

P H1 H2 H3

Ejercicio 2
escribe(n) el valor de n puede ser 0, 1, 2, 3. La funcin escribir en orden creciente nmeros menores que 1000, uno en cada lnea y con tres cifras. Los nmeros estarn encolumnados a partir de la columna 3n. El primer nmero ser el n, y entre un nmero y el siguiente hay una diferencia de 4. Escribir un programa C que lance 4 procesos concurrentes que correspondan respectivamente a la ejecucin de las funciones escribe(0), escribe(1), escribe(2) y escribe(3).
Con procesos pesados Con hilos.
escribe(0) 000 004 008 ... escribe(1) 001 005 009 ...

escribe(2) 002 006 010 ...

escribe(3) 003 007 011 ...

También podría gustarte