LABORATORIO Nro 3
Procesos e Intercomunicación entre Procesos
La llamada al sistema fork
La primitiva fundamental para la creación de procesos es la llamada al sistema fork. Este es el mecanismo por el cual todo sistema operativo Unix-like se transforma en un sistema multitarea.
Uso
int pid;
pid = fork();
Programa Ejemplo
#include <stdio.h>
#include <unistd.h>
/* spawn -- demostracion de fork */
int main(void)
{ int pid; /* maneja el id del proceso en el padre */
printf("Solo un proceso hasta ahora\n");
printf("Invocando a fork . . . \n");
pid = fork(); /* Crea un nuevo proceso */
if(pid == 0)
printf("Yo soy el hijo\n");
else if(pid > 0)
printf("Yo soy el padre, el hijo tiene pid %d\n",pid);
else
printf("fork retorno codigo de error, no hay hijo\n");
return 0;
}
La familia exec
Si fork
fuera la única primitiva de creación de procesos
disponible al programador, cualquier sistema operativo Unix-like
sería un poco aburrido debido al hecho de que solo se
podría hacer copias del mismo programa. Felizmente, un miembro
de la familia de las llamadas al sistema exec
pueden ser usadas para iniciar la ejecución de un nuevo
programa. La siguiente descripción de uso, muestra
los miembros de la familia.
Uso
char *path, *file
char *arg0, *arg1, . . ., *argn;
char *argv[];
int ret;
.
.
.
ret = execl(path, arg0, arg1,..., argn, (char *)0);
ret = execv(path,argv);
ret = execlp(file, arg0, arg1, ..., argn,(char *)0);
ret = execvp(file, argv);
Programa ejemplo 1
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdlib.h>
/* runls -- usa "execl" para correr ls */
int main(void)
{
printf("ejecutando ls\n");
execl("/bin/ls", "ls", "-l", (char *)0);
/* Si execl retorna, la invocacion ha fallado, asi que... */
fprintf(stderr,"execl fallo al correr ls");
exit(1);
}
Programa ejemplo 2
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdlib.h>
/* runls2 -- usa "execv" para correr ls */
int main(void)
{ char *av[3];
av[0] = "ls";
av[1] = "-l";
av[2] = (char *)0;
execv("/bin/ls", av);
/* nuevamente - obtener esto implica error */
fprintf(stderr, "execv failed");
exit(1);
}
Usando exec y fork juntos
Combinadas las llamadas al sistema fork y exec ofrecen al programador una herramienta potente. Después que un proceso ha invocado a fork se puede usar exec
con el hijo, de esta manera un programa puede correr otro programa con
un subproceso y sin destruirse a sí mismo. El siguiente programa
muestra cómo hacerlo, en él introducimos también
una rutina de error llamada fatal, y más prematuramente la llamada al sistema wait.
Programa ejemplo
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
/* runls3 -- corre ls en un subproceso */
_PROTOTYPE(void fatal, (char *));
int main(void)
{ int pid;
pid = fork();
/* Si es el padre usa wait para suspender
ejecucion hasta que el hijo
termine */
if(pid > 0){
wait((int *)0);
printf("ls terminado\n");
exit(0);
}
/* Si es el hijo entonces exec ls */
if(pid == 0) {
execl("/bin/ls", "ls", "-l", (char *)0);
fatal("execl fallo");
}
/* llegar a esta parte signifca que el pid es negativo,
asi que ha ocurrido un error */
fatal("fork fallo");
}
void fatal(char *s) /* imprime mensaje de error y muere */
{ perror(s);
exit(1);
}
La llamada al sistema exit
Uso
int status;
exit(status);
La llamada al sistema exit
es una vieja amiga, pero ahora nostros podemos colocarla en su contexto
adecuado. Esta es usada para terminar un proceso, aunque un proceso
también podrá detenerse cuando haya alcanzado el final de
la función main, o cuando main ejecuta la proposicion return.
Sincronización de procesos con wait
Uso
int retval,status;
retval = wait(&status);
o
retval = wait((int *)0);
Esta llamada al sistema suspende
temporalmente la ejecución de un proceso mientrras un proceso
hijo está corriendo. Una vez que el hijo ha terminado, el
proceso padre que se quedó esperando se reinicia. Si
más de un hijo está corriendo entonces wait retorna en el momento en que el primero de los hijos del padre termina.
La llamada wait amenudo es invocada por el proceso padre sólo después de que se ha invocado a fork.
Fragmento de ejemplo
pid = fork(); /* crea un nuevo proceso */
if(pid == 0) {
/* hijo
hace algo */
}else {
/* padre, espera por el hijo */
wait((int *) 0);
}
Intercomunicación entre procesos usando pipes
Un pipe
es un canal de comunicación que conecta un proceso con otro, y
es una vez más otra generalización de archivos en UNIX.
Como se verá en los programas ejemplos un proceso puede
enviar data al pipe usando la llamada al sistema write, y otro proceso puede recibir la data en el otro extremo con la llamada read .
Programando con pipes
Uso
int filedes[2], retval;
retval = pipe(filedes);
Programa ejemplo 1
/* Primer ejemplo de pipe */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
/* Este incluye la terminacion null */
#define MSGSIZE 16
char *msg1 = "hello, world #1";
char *msg2 = "hello, world #2";
char *msg3 = "hello, world #3";
int main(void)
{ char inbuf[MSGSIZE];
int p[2],j;
/* Abriendo pipe */
if(pipe(p) < 0){
fprintf(stderr,"Fallo al crear pipe");
exit(1);
}
/* Escribe en el pipe */
write(p[1], msg1, MSGSIZE);
write(p[1], msg2, MSGSIZE);
write(p[1], msg3, MSGSIZE);
/* Lee del pipe */
for(j=0; j < 3; j++){
read(p[0], inbuf, MSGSIZE);
printf("%s\n",inbuf);
}
exit(0);
}
La salida de este programa es
hello, world #1
hello, world #2
hello, world #3
Programa ejemplo 2
/* segundo ejemplo */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
/* Este incluye la terminacion null */
#define MSGSIZE 16
char *msg1 = "hello, world #1";
char *msg2 = "hello, world #2";
char *msg3 = "hello, world #3";
int main(void)
{ char inbuf[MSGSIZE];
int p[2],j,pid;
/* Abriendo pipe */
if(pipe(p) < 0){
fprintf(stderr,"Fallo al crear pipe");
exit(1);
}
if((pid = fork()) < 0) {
fprintf(stderr, "Fallo fork\n");
exit(2);
}
/* Si es el padre entonces escribe en el pipe */
if(pid > 0) {
write(p[1], msg1, MSGSIZE);
write(p[1], msg2, MSGSIZE);
write(p[1], msg3, MSGSIZE);
wait((int *)0);
}
/* Si es el hijo lee del pipe */
if(pid == 0) {
for(j=0; j < 3; j++){
read(p[0], inbuf, MSGSIZE);
printf("%s\n",inbuf);
}
}
exit(0);
}
FIFOS o pipes con nombre
Los pipes son elegantes y potentes mecanismos de comunicación entre procesos. Sin embargo ellos tienen serios inconvenientes.
Primero, y el más serio, los pipes
sólo pueden ser usados para conectar dos procesos que
comparten un ancestro en común, tales como un proceso
padre y su hijo. Este inconveniente se presenta cuando se trata de
desarrollar un programa servidor cuya existencia es permanente con
la finalidad de proveer un servicio. Idealmente, un proceso cliente
debería poder comunicarse con un proceso servidor, con el cual
no tiene ninguna relación, vía pipe y luego alejarse. Desgraciadamente, esto no puede ser llevado a cabo usando pipes convencionales.
Segundo, los pipes
no pueden ser permanentes. Ellos tienen que ser creados cada vez que
son necesarios y son destruidos cuando los procesos que han
accedido a ellos terminan.
Para remediar estas deficiencias, una variante de pipes se introdujo con el release de UNIX System III. El nuevo mecanismo de comunicación entre procesos es llamado FIFO o pipes con nombre.
Programando con FIFOS
Para una gran parte de la
programación con FIFOS es identica a la programación con
pipes ordinarios. La única diferencia significativa es en la
inicialización. En lugar de usar pipe, un FIFO es creado con la llamada al sistema mkfifo.
Uso
mkfifo("./datos",0777);
Programa ejemplo
/* rcvmsg -- Este proceso lee del FIFO la cadena y la imprime por pantalla */
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#define BUFSIZE 50
int main(void)
{ int fd;
char buf[BUFSIZE];
if((fd=open("datos",O_RDONLY ))==-1) {
fprintf(stderr,"Error al abrir pipe con nombre\n");
exit(1);
}
if(read(fd,buf,sizeof(buf))<0) {
fprintf(stderr,"Error al leer en el pipe\n");
exit(1);
}
fprintf(stdout,"%s",buf);
return 0;
}
---------------------------------------------------
/* sendmsg -- Este proceso crea el FIFO evita el error si ya esta creado y escribe
una cade en el
FIFO
*/
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#define BUFSIZE 50
int main(void)
{ int fd;char buf[BUFSIZE];
if((mkfifo("datos",0777)!=0) && (errno!=EEXIST) ) {
fprintf(stderr,"Error al crear pipe\n");
exit(1);
}
if((fd=open("datos", O_WRONLY ))==-1) {
fprintf(stderr,"Error al abrir conector de escritura\n");
exit(1);
}
sprintf(buf,"Mensaje enviado por proceso con pid %d\n",getpid());
if(write(fd,buf,strlen(buf)+1)<0) {
fprintf(stderr,"Error al escribir en el pipe\n");
exit(1);
}
return 0;
}
Para que ambos procesos se comuniquen debe lanzar sendmsg en segundo plano.
$ ./sendmsg&
$ ./rcvmsg
La salida será parecida a la siguiente:
Mensaje enviado por proceso con pid 125
Tarea
En esta ocasión su tarea
consistirá en analizar y entender todos los programas que se han
desarrollado como ejemplos. Estos serviran como bloques que luego
usted tendrá que armar para obtener el programa que se le pida
en el laboratorio.