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.