cc31a Desarrollo de Software de Sistemas

Patricio Poblete (ppoblete@dcc.uchile.cl)

Procesos en Unix

  1. Procesos
  2. Señales

Procesos

Un proceso se crea con la operación fork. Esto crea un clon del proceso actual, pero cada proceso resultante sabe si él es el padre o el hijo.

Esquema típico:

        if((child=fork())!=0)
          {
            /* aquí se ejecuta el padre */
            /* la variable "child" contiene el pid del hijo */
        
            ...
          }
        else
          {
            /* aquí se ejecuta el hijo */
        
            ...

Si el padre ejecuta la llamada


        wait(&status);

entonces el padre espera hasta que algún hijo muera y retorna el pid de ese hijo. A la variable status (int) se le asigna la causa del deceso (ver manual).

Si en el momento de ejecutar wait ya había algún proceso que ya había muerto ("zombie"), la llamada retorna inmediatamente.

Un wait también termina si llega una señal que cause la terminación del proceso o la invocación de una función de manejo de señales.

También existe una función que permite un manejo más específico:


        waitpid(pid, &status, flags);

Si pid==-1 se espera a cualquier proceso (como hace wait). Si pid>0 se espera al proceso que posee ese pid.

Los flags incluyen como valor posible WNOHANG que hace que la llamada no se bloquee.

Para utilizar estas llamadas se debe incluir


        #include <sys/types.h>
        #include <sys/wait.h>

Una vez que el hijo se inicia, frecuentemente lo que hace es invocar a algún comando, lo que hace llamando a una función de la familia exec. Esto no cre un proceso nuevo, sino que sustituye la imagen del proceso. Un exec que funciona no retorna, sino que inicia un proceso nuevo.


        execl(filename, s1, s2, ..., NULL);
          /* ejecuta el programa indicado por filename con parámetros s1, s2, ... */
        
        execle(filename, s1, s2, .., NULL, environ );
          /* igual al anterior + se traspasa ambiente indicado en environ */
        
        execlp(filename, s1, s2, ..., NULL);
          /* si filename no empieza por "/", se le busca en el PATH */
        
        execv(filename, argv[]);
          /* el último elemento de argv[] debe ser NULL */
        
        execve(filename, argv[], environ);
        
        execvp(filename, argv[]);

El archivo ejecutabledebe corresponder a un programa con encabezamiento


        int main(int argc, char *argv[])

El puntero environ apunta a un arreglo de strings de la forma


        name=value
terminando con un puntero NULL.

Ejemplo de variables de ambiente:


        HOME
        PATH
        TERM
        TZ
        LOGNAME

Para buscar un nombre de variable en el environment se usa


        p=getenv(name);

El parámetro name apunta al nombre de la variable y p recibe el puntero a la línea name=value respectiva, o bien NULL si no existe. Esta función requiere <stdlib.h>.

Formas de terminar un proceso

  1. return del main. Si el valor retornado de 0 se considera que el fin fue exitoso.

  2. exit(value);

  3. abort()
    El efecto de esta última llamada es indefinido, pero puede generar un core.

Para matar a otro proceso se usa


        kill(pid, SIGKILL);

Ejemplo: Filtrar la salida de un programa a través de more.

fork.c


        #include <stdio.h>
        #include <sys/types.h>
        #include <unistd.h>
        #include <sys/types.h>
        #include <sys/wait.h>
        
        main()
          {
            int fd[2];
            int pid;
            FILE *out;
            int status;
            int i;
            
            if(pipe(fd)!=0)
                error("Can't create pipe");
            if((pid=fork())==-1)
                error("Can't fork");
            
            if(pid==0) /* es el hijo */
              {
                /* el siguiente truco pone al extremo del lectura del pipe
                   como el stdin del nuevo proceso */
                close(STDIN_FILENO);
                dup(fd[0]); /* aprovechamos que se usa el mismo fd recién liberado */
                close(fd[0]);
                close(fd[1]);
            
                execlp("more", "more", NULL);
                /* no retorna */
              }
            
            /* este es el padre */
            close(fd[0]); /* no va a leer del pipe */
            
            out=fdopen(fd[1], "w"); /* simula que el extremo de lectura del pipe
                                       fue abierto con fopen */
            /* ahora generamos el output */
            
            for(i=1; i<=200; ++i)
                fprintf(out, "Línea número %d\n", i);
            
            fclose(out);
            
            (void)wait(&status); /* esperamos que el hijo muera */
          }

Información disponible durante la ejecución


        pid=getpid();  /* retorna el pid propio */
        
        ppid=getppid(); /* retorna el pid del padre */
        
        s=getlogin();  /* retorna puntero al nombre del usuario, o NULL */
        
        uid=getuid(); /* retorna real UID */
        
        uid=geteuid(); /* retorna effective UID */
        
        gid=getgid();  /* real group ID */
        
        gid=getegid();  /* effective group ID */
        
        s=ctermid(NULL);  /* puntero al nombre del terminal en un buffer estático */
        
        (void)ctermid(termname); /* copia nombre del terminal a termname[L_ctermid] */

Dado un uid, es posible obtener un record de información asociada al usuario, tomada de lo que aparece en el archivo /etc/passwd:


        #include <pwd.h>
        #include <sys/types.h>
        
        struct passwd *pwptr;
        
        pwptr=getpwuid(uid);

La estructura a la cual apunta pwptr es


        /* The passwd structure.  */
        struct passwd
        {
          char *pw_name;                /* Username.  */
          char *pw_passwd;              /* Password.  */
          __uid_t pw_uid;               /* User ID.  */
          __gid_t pw_gid;               /* Group ID.  */
          char *pw_gecos;               /* Real name.  */
          char *pw_dir;                 /* Home directory.  */
          char *pw_shell;               /* Shell program.  */
        };

También existe la función


        pwptr=getpwnam(username);

que busca por nombre y retorna un record al puntero respectivo. En ambos casos se retorna NULL si no existe el usuario no existe.

Hora y Fecha


        #include <time.h>
        
        time_t t; /* usualmente unsigned long */
        
        t=time(NULL); /* Almacena en t el número de segundos desde 1/1/1970 */
        
        (void)time(&t); /* forma alternativa */

La función anterior basta para calcular todo lo que se necesita, pero las siguientes funciones son útiles para simplificar esta tarea:


        struct tm
        {
          int tm_sec;                   /* Seconds.     [0-60] (1 leap second) */
          int tm_min;                   /* Minutes.     [0-59] */
          int tm_hour;                  /* Hours.       [0-23] */
          int tm_mday;                  /* Day.         [1-31] */
          int tm_mon;                   /* Month.       [0-11] */
          int tm_year;                  /* Year - 1900.  */
          int tm_wday;                  /* Day of week. [0-6] */
          int tm_yday;                  /* Days in year.[0-365] */
          int tm_isdst;                 /* DST.         [-1/0/1]*/
         
          long int tm_gmtoff;           /* Seconds east of UTC.  */
          const char *tm_zone;          /* Timezone abbreviation.  */
        };
        
        struct tm *t;
        time_t now;
        
        now=time(NULL);
        t=localtime(&now);
        t=gmtime(&now);
        
        seconds=mktime(&t); /* conversion inversa */
           /* Los campos tm_wday y tm_yday se ignoran y se actualizan en t */
        
        s=ctime(&now); /* Genera string de la forma "Wed Jun 30 21:49:08 1993\n" */
        
        s=asctime(&t); /* Genera string de la forma "Wed Jun 30 21:49:08 1993\n" */

La función strftime es una especie de sprintf para formatear tiempos (ver manual).

Para medir intervalos de tiempo de procesador se usa:


        #include <time.h>
        
        ticks=clock(); /* Tiempo medido desde algún instante de referencia */
                       /* Para transformar a segundos dividir por CLOCKS_PER_SEC */

Ejemplo:

hora.c


        #include <stdio.h>
        #include <time.h>
        
        main()
          {
            time_t now;
            struct tm *t;
        
            now=time(NULL);
            t=localtime(&now);
        
            printf("Son las %02d:%02d:%02d del %d/%d/%d\n",
                t->tm_hour, t->tm_min, t->tm_sec,
                t->tm_mon+1, t->tm_mday, t->tm_year+1900);
        
            printf("Día de la semana: %d\n", t->tm_wday+1);
        
            printf("Día del año: %d\n", t->tm_yday+1);
        
            printf(t->tm_isdst>0?"Horario de Verano\n":"Horario Normal\n");
          }

Señales

Las señales informan a un proceso cuando ha ocurrido un evento.

Los eventos pueden ser síncronos (p.ej. errores) o asíncronos (p.ej. terminación de otro proceso).

Un proceso tiene que realizar una acción en respuesta a la señal. El tiempo entre que la señal se activa y el proceso la atende se dice que la señal está pendiente.

Un proceso puede bloquear algunas señales, mediante un signal mask. Esta máscara se hereda.

Posibilidades de proceso de una señal:

  1. Terminar el proceso.

  2. Ignorar la señal.

  3. Detener el proceso.

  4. Reanudar el proceso.

  5. Atrapar la señal mediante una función del programa.
Señales estándares:
SIGALRMAlarma
SIGFPEDivisión por cero
SIGHUPHangup
SIGINT^C
SIGKILLTerminación (no se puede atrapar)
SIGPIPEBroken Pipe
SIGTERMTerminación

Para contarle al sistema cómo se debe manejar una señal, se usa


        signal(señal, accion);
Acciones asociadas a una señal:
SIG_DFLDefault action
SIG_IGNIgnore
Puntero a una funciónSe ejecuta la función con la señal como parámetro y luego se reanuda el programa

Un programa le puede enviar una señal a un proceso haciendo


        kill(pid, señal);

y para enviarse una señal a sí mismo:


        raise(señal);

Para esperar una señal:


        pause(); /* suspende hasta que llegue una señal */

Para esperar durante un número dado de segundos:


        sleep(num_segundos); /* despierta al final de ese período u cuando
                        llega una señal; retorna número de segundos restantes */

Para programar una alarma:


        alarm(num_segundos); /* genera SIGALRM en ese número de segundos */

Ejemplo: Lectura con timeout.

timeout.c


        /* Leer una línea del teclado dándole 10 segundos de plazo */
        #include <stdio.h>
        #undef __USE_BSD /* para que el read pueda ser interrumpido por una señal */
        #include <signal.h>
        #include <unistd.h>
        
        
        volatile int flag;
        
        /* función para atrapar la señal de alarma */
        void ring()
          {
            flag=0;
          }
        
        /* función que lee con timeout */
        int gettext(char *buf, int bufsize, int timeout)
          {
            int nchars;
        
            signal(SIGALRM, ring);
            flag=1;
            (void)alarm(timeout);
            nchars=read(STDIN_FILENO, buf, bufsize);
            (void)alarm(0); /* para cancelar alarma pendiente */
            if(!flag)
                nchars=0;
            buf[nchars]='\0';
            return nchars;
          }
        
        #define MAXLINEA 100
        main()
          {
            char linea[MAXLINEA+1];
        
            printf("Escriba su nombre: ");
            fflush(stdout);
            if(gettext(linea, MAXLINEA, 10)>0)
                printf("Gracias %s", linea);
            else
                printf("*** TIMEOUT ***\n");
          }