DOCTORADO EN INFORMÁTICA

PROGRAMACIÓN DE SISTEMAS UNIX:
COMUNICACIÓN ENTRE PROCESOS

RAMÓN M. GÓMEZ LABRADOR

JUNIO 1.996


ÍNDICE.

  1. PROCESOS.

    1. Conceptos generales.
    2. Ejecución de comandos.
      1. Subrutina system.
      2. Subrutinas exec.
    3. Creación de procesos.
      1. Subrutina fork.
      2. Subrutinas wait y waitpid.
    4. Terminación de un proceso.
      1. Subrutina exit.
      2. Subrutina atexit.

  2. SEÑALES.

    1. Conceptos generales.
    2. Lista de las señales más importantes.
    3. Capturar señales.
      1. Subrutina signal:
      2. Subrutina sigaction:
      3. Subrutina kill:
    4. Alarmas y temporizadores.
      1. Subrutinas alarm y ualarm:
    5. Tratamiento de errores.
      1. Lista de errores más importantes.
      2. Subrutina perror:

  3. PIPES (TUBERÍAS).

    1. Conceptos generales.
    2. Redirección.
      1. Subrutinas dup y dup2:
      2. Subrutina fcntl:
    3. Comunicación entre procesos emparentados.
      1. Subrutina pipe
    4. Comunicación entre procesos no emparentados.
      1. Subrutina mkfifo:

Lista de programas.


1. PROCESOS.

1,1. Conceptos generales.

Proceso:
programa o comando en ejecución.
Características:

Ejecución en 1er plano:
proceso iniciado por el usuario o interactivo.

Ejecución en 2o plano:
proceso no interactivo que no necesita ser iniciado por el usuario.

Demonio:
proceso en 2o plano siempre disponible, que da servicio a varias tareas (debe ser propiedad del usuario root).

Proceso zombi:
proceso parado que queda en la tabla de procesos hasta que termine su padre. Este hecho se produce cuando el proceso padre no recoge el código de salida del proceso hijo.

Proceso huérfano:
proceso en ejecución cuyo padre ha finalizado. El nuevo identificador de proceso padre (PPID) coincide con el identificador del proceso init (1).


1,2. Ejecución de comandos.

1,2,1. Subrutina system:

- Descripción:
Llamada a un intérprete para ejecutar un comando.
El proceso espera a que finalice la ejecución de la subrutina y devuelve la salida del programa ejecutado.

- Formato:
#include <stdlib.h>
int system (cadena)
const char *cadena;

- Parámetro:
cadena - Comando a ejecutar.

- Devuelve:
Estado de salida del programa ejecutado. -1 o 127 en caso de error.

- Comentarios:
a) Se crea un proceso hijo (fork) y se lanza (exec) /usr/bin/bsh, que interpreta el comando a ejecutar.
b) Si la llamada se hace con camino seguro, la orden exec ejecuta el intérprete /usr/bin/tsh.
c) Se ignoran las señales SIGINT y SIGQUIT y se bloquea la señal SIGCHLD.
d) La salida de system no afecta a la salida de los procesos hijos del proceso ejecutor.

- Ejemplo:

/* system.c - Listar los procesos del usuario usando system. */
#include <stdio.h>
#include <stdlib.h>

int main ()
    {
    int  salida;	/* Salida del comando */
    char comando[100];	/* Comando a ejecutar */

    printf ("Ejemplo de system.\n");
    sprintf (comando, "/bin/ps -fu %s", getenv ("USER"));
    salida = system (comando);
    printf ("Salida del comando: %d\n", salida);
    exit (salida);
    }

Ejemplo de system.
    USER     PID    PPID     TTY  CMD
   ramon    3638       1   hft/0  -ksh
   ramon   10089   10600   hfp/0  /bin/ps -fu ramon
   ramon   10600   11623   hft/0  bsh bsh bsh
   ramon   11623    3638   hft/0  system.e
Salida del comando: 0

Suponiendo que no existe el comando MUSHO y sustituyendo la ejecución de system por la siguiente línea, se obtiene la salida mostrada a continuación.
salida = system ("MUSHO BETI");

Ejemplo de system.
bsh: MUSHO: no encontrado.
Salida del comando: 256


1,2,2. Subrutinas exec>:

- Descripción:
Ejecuta un nuevo programa en el mismo proceso.
Se crea un proceso imagen sustituyendo el programa actual por el nuevo.

- Formatos:
#include <unistd.h>
int execl (camino, arg0 [, arg1, ...] , 0)
const char *camino, *arg0, *arg1, ...;

int execle (camino, arg0 [, arg1, ...] , 0, p_entorno)
const char *camino, *arg0, *arg1, ...;
char *const p_entorno[];

int execlp (fichero, arg0 [, arg1, ...] , 0)
const char *fichero, *arg0, *arg1, ...;

int execv (camino, val_args)
const char *camino;
char *const val_args[];

int execve (camino, val_arg, p_entorno)
const char *camino;
char *const val_args[], *p_entorno[];

int execvp (fichero, val_args)
const char *fichero;
char *const val_args[];

int exect (camino, val_arg, p_entorno)
char *camino, *val_args, *p_entorno[];

- Sufijos:
L - usa lista de parámetros, el último debe ser 0.
V - usa matriz de parámetros generada previamente, el último debe ser 0.
T - trazado del programa con ptrace (en desuso).
E - usa la matriz de variables de entorno.
P - búsqueda utilizando la variable PATH.

- Parámetros:
camino - Camino completo del fichero ejecutable.
fichero - Nombre del fichero ejecutable.
argN - Argumento N-ésimo.
val_args - Puntero a la matriz de argumentos.
p_entorno - Puntero a la matriz del entorno.

- Devuelve:
-1, en caso de error.

- Comentarios:
a) La rutina principal (main) de un programa C ejecutable recibe los siguientes parámetros:
int main (cont_args, val_args, p_entorno)
int cont_args; /* Contador de argumentos. */
char *val_args; /* Puntero a la matriz de argumentos. */
char *p_entorno; /* Puntero a la matriz del entorno. */
Las variables val_args y p_entorno son similares a las utilizadas en las subrutinas exec.

b) Esta rutina principal llama a una subrutina de iniciación que construye la variable de entrono. Dicha variable global es accesible desde el programa declarándola de la siguiente manera:
extern char **environ;
Las subrutinas exec que no usan el parámetro p_entorno utilizan la variable environ.

c) Los descriptores de ficheros abiertos se pasan al nuevo proceso imagen, excepto los que tengan activo el bit FD_CLOEXEC (ver fcntl).

d) Las señales capturadas se reasignan a sus acciones por defecto; las ignoradas, continúan siendo ignoradas (ver sigaction).

e) Si el nuevo proceso imagen tiene activo el bit SUID, la identificación efectiva de usuario (EUID) del nuevo proceso toma el valor del identificador del propietario. Idem, si tiene activo el bit SGID.

f) Los identificadores reales de usuario y de grupo (RUID y RGID) mantienen el valor que tenían en el proceso llamador.

g) El párrafo anterior puede aplicarse a ficheros remotos (previa traducción de los identificadores).

h) El párrafo e) no afecta a las shell-scripts.

i) El proceso nuevo mantiene las siguientes características del proceso llamador:


- Ejemplo:

/* exec.c - Listar los procesos del usuario usando exec. */
#include <stdio.h>
#include <unistd.h>

int main ()
    {
    int  salida;/* Salida del comando */

    printf ("Ejemplo de exec.\n");
    execl ("/bin/ps", "ps", "-fu", getenv ("USER"), 0);
    printf ("Salida del comando: %d\n", salida);

exit (salida); }

Ejemplo de system.
    USER     PID    PPID     TTY  CMD
   ramon    3638       1   hft/0  -ksh
   ramon   10739    3638   hft/0  /bin/ps -fu ramon

Suponiendo que no existe el comando MUSHO y sustituyendo la ejecución de execl por la siguiente línea, se obtiene la salida mostrada a continuación.
salida = execl ("MUSHO", "BETI", 0);

Ejemplo de exec.
Salida del comando: -1


1,3. Creación de procesos.

1,3,1. Subrutina fork:

- Descripción:
Crea un nuevo proceso (hijo), copia casi exacta del proceso generador (padre).

- Formato:
#include <unistd.h>
pid_t fork ();

- Devuelve:
0 al proceso hijo y PID del hijo al proceso padre (-1, si error).

- Comentarios:
a) La versión BSD (en la librería libbsd.a) es:
int vfork ();

b) Atributos que hereda el proceso hijo.

c) Atributos diferenciadores entre padre e hijo:


- Ejemplos:

/* fork.c - Ejecución conjunta de procesos padre e hijo */
#include <stdio.h>
#include <unistd.h>
main ()
    {
    printf ("Ejemplo de fork.\n");

printf ("Inicio del proceso padre. PID=%d\n", getpid ());

if (fork() == 0)

{ /* Proceso hijo */ printf ("Inicio proceso hijo. PID=%d, PPID=%d\n", getpid (), getppid ()); sleep (1); } else { /* Proceso padre */ printf ("Continuación del padre. PID=%d\n", getpid ()); sleep (1); } printf ("Fin del proceso %d\n", getpid ()); exit (0); }

Ejemplo de fork.
Inicio proceso padre. PID=8153
Inicio proceso hijo. PID=6618, PPID=8153
Continuación proceso padre. PID=8153
Fin del proceso 6618
Fin del proceso 8153


- Ejemplo:

/* fork_huerf.c  - Ejemplo de proceso huérfano *
#include <stdio.h>
#include <unistd.h>
main ()
    {
    printf ("Ejemplo de proceso huérfano.\n");
    printf ("Inicio del proceso padre. PID=%d\n", getpid ());
    if (fork () == 0)
	{
	printf ("Inicio proceso hijo. PID=%d, PPID=%d\n",
	getpid (), getppid ());
	sleep (1);
	printf ("El proceso queda huérfano. PID=%d PPID=%d\n",
	getpid (), getppid ());
	}
    else
	printf ("Concinuación del padre. PID=%d\n", getpid ());
    printf ("Fin del proceso %d\n", getpid ());
    exit (0);
    }

Ejemplo de proceso huérfano.
Inicio proceso padre. PID=11330
Inicio proceso hijo. PID=6467, PPID=11330
Continuación proceso padre. PID=11330
Fin del proceso 11330
$punto indicativo> El proceso queda huérfano. PID=6467, PPID=1
Fin del proceso 6467

Notas:


1,3,2. Subrutinas wait y waitpid:

- Descripción:
Espera a que pare o termine un proceso hijo, permitiendo obtener sus estados de salida.
Una señal no bloqueada o no ignorada puede reactivar el proceso padre.

- Formato:
#include <sys/wait.h>
pid_t wait (estados)
int *estados;

pid_t wait ((void *) 0);

pid_t waitpid (PID, estados, opciones)
pid_t PID;
int *estados, opciones;

- Parámetros:<
PID - PID del proceso o grupo de proceso. Sus valores son:
opciones - Máscara de opciones. Sus bits son:
estados - Puntero a una tabla con los estados de salida de los procesos.

- Devuelve:

0, si no ha terminado ningún proceso.

- Macros:
WIFSTOPPED (estado) /* !=0, si estado es de un hijo parado */
pid_t estado;

int WSTOPSIG (estado) /* Nú de señal que ha causado la parada */
pid_t estado;

WIFEXITED (estado) /* !=0, si estado es de salida normal */
pid_t estado;

int WEXITSTATUS (estado) /* 8 bits bajos del estado de salida */
pid_t estado;

WIFSIGNALED (estado) /* !=0, si estado es de salida anormal */
pid_t estado;

int WTERMSIG (estado) /* Nú de sañal que ha causado la slida */
pid_t estado;

- Cometarios:
a) Estas subrutinas pueden verse afectadas por la señal SIGCHLD (ver sigaction).
b) La subrutina wait espera la terminación de cualquier proceso hijo.


- Ejemplos:

/* waitpid.c - Esperar la terminación de un proceso hijo */
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>

main ()
    {
    pid_t id_padre;	/* PID del proceso padre */
    pid_t id_hijo;	/* PID del proceso hijo */
    int   estado;	/* Estado de salida */

    printf ("Ejemplo de waitpid.\n");
    printf ("Inicio proceso padre. PID=%d\n", getpid ());
    id_padre = getpid ();
    if ((id_hijo = fork ()) == 0)
        {		/* Proceso hijo */
        printf ("Inicio proceso hijo. PID=%d, PPID=%d\n",
        getpid (), id_padre);
        sleep (3);
        printf ("Salida proceso hijo. PID=%d\n", getpid ());
        exit (getpid () > id_padre);        /* 1, si PID > PPID */
        }
    else
        {
        signal (SIGINT, SIG_IGN);        /* Ignorar CTRL-C */
        while (waitpid (id_hijo, &estado, 0) != id_hijo);
        if (WIFSIGNALED (estado))
            printf ("El proceso hijo ha recibido la señal %d\n", WTERMSIG (estado));
        if (WIFEXITED (estado))
           {
           printf ("Estado de salida del proceso hijo: %d\n", WEXITSTATUS (estado));
           if (WEXITSTATUS (estado) == 1)
               printf ("PID hijo > PID padre.\n");
           else
               printf ("PID padre > PID hijo.\n");
           }
        printf ("Fin del proceso %d\n", getpid ());
        exit (0);
    }

Ejemplo de waitpid.
Inicio proceso padre. PID=24213
Inicio proceso hijo. PID=31638, PPID=24213
Fin proceso hijo. PID=31638
Estado de salida del proceso hijo: 1
PID hijo > PID padre
Fin del proceso 24213

La salida siguiente muestra el efecto de generar una señal de interrupción pulsando [CTRL][C]. Dicha señal provoca la terminación automática del proceso hijo, mientras que el proceso padre la ignora (ver signal).

Ejemplo de waitpid.
Inicio proceso padre. PID=7240
Inicio proceso hijo. PID=5705, PPID=7240
^CEl proceso hijo ha recibido la señal: 2
Fin del proceso 7240


1,4. Terminación de un proceso.

1,4,1. Subrutina exit:

- Descripción:
Termina la ejecución de un proceso.

- Formato:
#include <stdlib.h>
void exit (estado)
int estado;

- Parámetro:
Estado de salida del proceso.

- Comentarios:
a) El proceso de salida de un proceso es el siguiente:

b) Si _cleanup no puede cancelar las peticiones de E/S asíncrona, la aplicación se bloquea hasta que se completen dichas peticiones.

c) Se cierran todos los descriptores de fichero.

d) Si el proceso padre está en espera (ver wait), se devuelve el valor de los 8 bits menos significativos del estado de salida.

e) Se envía una señal SIGCHLD al proceso padre. La acción por defecto es ignorar esta señal. Si no se ignora, el proceso hijo puede quedar como proceso zombi.

f) La salida de un proceso no provoca la terminación de sus hijos. El PPID de los hijos será el PPID del proceso init (1).

g) Se eliminan los bloqueos de ficheros (ver fcntl).

h) Si para un proceso perteneciente a un grupo huérfano, se envían las señales SIGHUP y SIGCONT a cada proceso del grupo de procesos huérfanos.


1,4,2. Subrutina atexit:

- Descripción:
Ejecuta una determinada función antes de la terminación del proceso.

- Formato:
#include <sys/limits.h>
int atexit (función)
void (*función) (void);

- Parámetro:
Puntero a la función llamada.

- Devuelve:
0: si no hay errores.

- Comentarios:
a) La función se ejecuta si se ha completado con éxito la subrutina _cleanup.


- Ejemplo:

/* atexic.c - Ejecución de una rutina al salir de un programa */
#include <stdio.h>
#include <sys/limits.h>

int bucle=0;		/* Contador de vueltas del bucle */

void salida ();		/* Prototipo de la función de salida */

int main ()
   {
   int n;

   atexit (salida);
   printf ("Ejemplo de atexit.\n");
   for (bucle=1; bucle<255; bucle++)
	{
	n=rand ();
	printf ("%d-%d\t", bucle, n);
	if (n > 30000)
	   exit (1);
	}
   exit (0);
   }

void salida ()
     {
     printf ("El bucle ha dado %d vueltas.\n");
     printf ("Hasta luega Lucas.\n");
     }

Ejemplo de atexit.
1-16838	2-5758	3-10113	4-17515	5-31051
El bucle ha dado 5 vueltas.
Hasta luego Lucas.


2. SEÑALES.

2,1. Conceptos generales.

Señal:
Evento que debe ser procesado y que puede interrumpir el flujo normal de un programa.

Capturar una señal:
Una señal puede asociarse con una función que procesa el evento que ha ocurrido.

Ignorar una señal:
El evento no interrumpe el flujo del programa. Las señales SIGINT y SIGSTOP no pueden ser ignoradas (ver tabla de señales).

Acción por defecto:
Proceso suministrado por el sistema para capturar la señal (ver tabla de señales).

Alarma:
Señal que es activada por los temporizadores del sistema.

Error:
Fallo o acción equivocada que puede provocar la terminación del proceso.

Error crítico:
Error que provoca la salida inmediata del programa.


2,2. Lista de las señales más importantes.

Núm. Nombre Comentarios
1 SIGHUP Colgar. Generada al desconectar el terminar.
2 SIGINT Interrupción. Generada por teclado.
3 SIGQUIT1 Salir. Generada por teclado.
4 SIGILL1 Instrucción ilegal. No se puede recapturar.
5 SIGTRAP1 Trazado. No se puede recapturar.
6 SIGABRT1 Abortar proceso.
8 SIGFPE1 Excepción aritmética, de coma flotante o división por cero.
9 SIGKILL1 Matar proceso. No puede capturarse, ni ignorarse.
10 SIGBUS1 Error en el bus.
11 SIGSEGV1 Violación de segmentación.
12 SIGSYS1 Argumento erróneo en llamada al sistema.
13 SIGPIPE Escritura en una tubería que otro proceso no lee.
14 SIGALRM Alarma de reloj.
15 SIGTERM Terminación del programa.
16 SIGURG2 Urgencia en canal de E/S.
17 SIGSTOP3 Parada de proceso. No puede capturarse, ni ignorarse.
18 SIGTSTP3 Parada interactiva. Generada por teclado.
19 SIGCONT4 Continuación. Generada por teclado.
20 SIGCHLD2 Parada o salida de proceso hijo.
21 SIGTTIN3 Un proceso en 2o plano intenta leer del terminal.
22 SIGTTOU3 Un proceso en 2o plano intenta escribir en el terminal.
23 SIGIO2 Operación de E/S posible o completada.
24 SIGXCPU Tiempo de UCP excedido.
25 SIGXFSZ Excedido el límite de tamaño de fichero.
30 SIGUSR1 Definida por el usuario número 1.
31 SIGUSR2 Definida por el usuario número 2.
34 SIGVTALRM Alarma de tiempo virtual.
36 SIGPRE Excepción programada. Definida por el usuario.

Notas sobre la acción por defecto para la señal.

  1. Generar un fichero core.
  2. Ignorar la señal.
  3. Parar el proceso que recibe la señal.
  4. Reiniciar o continuar el proceso que recibe la señal.
Las señales comprendidas entre la 37 y la 58 (ambas inclusive) están reservadas por el sistema.

El rango de señales en el UNIX de Berkeley (BSD) es de 1 a 31.


2,3. Capturar señales.

2,3,1. Subrutina signal:

- Descripción:
Asocia una acción determinada con una señal.

- Formato:
#include <signal.h>
void (*signal (señal, acción)) ()
int señal;
void (*accón) ();

- Parámetros:
señal: Número de señal, excepto SIGKILL.
acción: Puntero a la rutina asociada con la señal o uno de los valores:

- Devuelve:
Valor de la acción anteriormente asociada; -1, en caso de error.

- Comentarios:
a) Existe una versión de la subrutina signal compatible con el UNIX de Berkeley (BSD).
b) No se permiten máscaras de bloqueo de señales y se activa el bit SA_OLDSTYLE (ver >sigaction).


- Ejemplo:

/* signal.c - Contar el número de CTRL-C en 15 segundos */
#include <stdlib.h>
#include <signal.h>

int numcortes=0;	/* Contador de CTRL-C */
int enbucle=1;		/* Controlador de salida del bucle de espera */

void alarma ();		/* Captura la señal de alarma SIGALRM */
void cortar ();		/* Captura la señal de interrupción SIGINT */

int main ()
    {
    signal (SIGINT, cortar);
    signal (SIGALRM, alarma);
    printf ("Ejemplo de signal.\n");
    printf ("Pulsa varias veces CTRL-C durante 15 segundos.\n");
    alarm (15);
    while (bucle);
    signal (SIGINT, SIG_IGN);
    printf ("Has intentado cortar %d veces.\n", numcortes);
    printf ("Hasta luego Lucas.\n");
    exit (0);
    }

void alarma ()
     {
     signal (SIGALRM, SIG_IGN);
     bucle=0;		/* Salir del bucle */
     printf ("¡Alarma!\n");
     }

void cortar ()
     {
     signal (SIGINT, SIG_IGN);
     printf ("Has pulsado CTRL-C\n");
     numcortes++;
     signal (SIGINT, cortar);
     }

Ejemplo de signal.
Pulsa CTRL-C varias veces durante 15 segundo.
^CHas pulsado CTRL-C
^CHas pulsado CTRL-C
^CHas pulsado CTRL-C
^CHas pulsado CTRL-C
^CHas pulsado CTRL-C
¡Alarma!
Has intentado cortar 5 veces.
Hasta luego Lucas.


2,3,2. Subrutina sigaction:

- Descripción:
Especifica la acción a realizar cuando un proceso recibe una señal.

- Formato:
#include <signal.h>
int sigaction (señal, acción, acción_salida) ()
int señal;
struct sigaction *accón, *acción_salida;

- Parámetros:
señal: Número de señal, excepto SIGKILL.
acción: Acción especificada cuando se recibe la señal.
acción_salida: Acción a realizar cuando termine la función sigaction.

- Campos de la estructura sigaction:
void (*sa_handler) (); Puntero a la rutina asociada con la señal o uno de los valores:
sigset_t sa_mask; Especifica la máscara de las señales que serán bloqueadas durante la captura de la señal especificada.
int sa_flags;

- Devuelve:
0, si es correcta; -1, en caso de error.

- Comentarios:
a) Las siguientes funciones pueden ser llamadas sin problemas desde una rutina de captura de señales:
_exit access alarm chdir chmod chown
close creat dup dup2 exec fcntl
fork fstat getegid geteuid getgid getgroups
getpgrp getpid getppid getuid kill link
lseek mkdir mkfifo open pause pipe
readx rename rmdir setgid setpgrp setuid
sigaction sigaddset sigdelset sigfillset sigismember signal
sigpending sigprocmask sigsuspend sleep statx tcdrain
tcflow tcflush tcgetattr tcgetpgrp tcsendbreak tcsetattr
tcsetpgrp time times umask uname unlink
ustat utime write

b) Una vez que una acción está instalada para una señal, continúa hasta que haya otra llamada a sigaction o se llame a la subrutina exec, excepto si se ha activado el bit SA_OLDSTYLE.

c) Las señales SIGKILL y SIGSTOP no pueden ser ignoradas.


2,3,3. Subrutina kill:

- Descripción:
Envía una señal a un proceso.

- Formato:
#include <signal.h>
int kill (proceso, señal)
pid_t proceso;
int señal;

- Parámetros:
proceso: Identificador del proceso o del grupo de procesos que recibirá la señal. Puede tomar los siguientes valores:
señal: Número de señal enviada.

- Devuelve:
0, si se ha completado correctamente; -1, en caso de error.

- Comentarios:
a) La subrutina raise envía una señal al proceso actual.
#include <sys/signal.h>
int raise (señal)
int señal;
Este código es equivalente al mostrado a continuación:
error = kill (getpid (), señal);

b) La subrutina killpg envía una señal a un grupo de procesos. Esta subrutina es compatible con el UNIX de Berkeley (librería libbsd.a).
#include <signal.h>
int killpg (int grupo_procesos, int señal);
El código anterior equivale al mostrado a continuación:
if (grupo_procesos < 0)
   {
   errno = ESRCH;
   return (-1);
   }
return (kill(-grupo_procesos, señal));

c) Para enviar una señal a otro proceso deben coincidir el identificador de usuario (UID) real o efactivo de ambos procesos, o que el proceso emisor tenga prioridad de usuario root.


- Ejemplo:

/* kill.c - Ejecución con tiempo de espera usando kill */
#include <stdlib.h>
#include <signal.h>

int espera;		/* Tiempo de espera */

void hijo ();		/* Controlador de fin de proceso hijo */

int main (int contargs, char *args[]);
    {
    pid_t pid;

    if (contargs < 3)
       {
       printf ("Formato: %s segundos comando [opciones].\n", args[0]);
       exit (1);
       }
    printf ("Ejemplo de kill.\n");
    printf ("Ejecución con tiempo de espera.\n");
    signal (SIGCHLD, hijo);
    pid = fork ();
    if (pid == 0)
       {
       execvp (args[2]; &args[2]);
       perror (args[0]);
       }
    else
        {
        espera = atoi (args[1]);
        sleep (espera);
        printf ("El hijo %d ha excedido el tiempo de %d s.\n",
		pid, espera);
        signal (SIGCHLD, SIG_IGN);
        kill (pid, SIGINT);
        }
    exit (1);
    }

void espera ()
     {
     int id_hijo, est_hijo;

     id_hijo = wait (&est_hijo);
     printf ("El hijo %d ha terminado antes de %d s.\n",
		id_hijo, espera);
     exit (0);
     }

$ kill.e 3 wc kill.c
Ejemplo de kill.
Ejecución de un comando con tiempo de espera.
45 132 1065 kill.c
El hijo 10489 ha terminado antes de 3 s.

$ kill.e 3 sleep 5
Ejemplo de kill.
Ejecución de un comando con tiempo de espera.
El hijo 10851 ha excedido el tiempo de espera de 3 s.


2,4. Alarmas y temporizadores.

2,4,1. Subrutinas alarm y ualarm:

- Descripción:
Genera alarmas de reloj (señal SIGALRM) para el proceso actual.

- Formato
#include <unistd.h>
unsigned int alarm (segundos)
unsigned int segundos;

unsigned int ualarm (valor, intervalo)
unsigned int valor, intervalo;

- Parámetros:
segundos: Número de segundos para enviar al proceso la señal SIGALRM.
valor: Número de señales generadas.
intervalo: Intervalo (en ms.) entre las señales.

- Devuelve:
alarm devuelve el número de segundos que restan para generar la señal.
ualarm devuelve el número de microsegundos que restan hasta la próxima señal.

- Comentarios:
a) Sólo puede generarse una única alarma (no son aplilables).
b) El parámetro intervalo no puede ser menor que 10 para un usuario sin privilegios.
c) Estas 2 subrutinas son compatibles con las primeras versiones del AIX, con UNIX System V y con UNIX de Berkeley (BSD). En AIX, se han programado como llamadas a la subrutina incinterval.


- Ejemplo:

/* alarm.c - Esperar una alarma */
#include <stdlib.h>
#include <unistd.h>
int main ()
    {
    printf ("Una alarma en 3 segundos.\n");
    alarm (3);
    printf ("Esperando...\n");
    while (1);
    printf ("Esta línea no se ejecutará nunca.\n");
    exit (0);
    }

Una alarma en 3 segundos.
Esperando...
Alarm clock


2,5. Tratamiento de errores.

2,5,1. Lista de errores más importantes.

Núm. Nombre Descripción
1 EPERM Operación no permitida.
2 ENOENT El archivo o directorio no existe.
3 ESRCH El proceso no existe.
4 EINTR Llamada al sistema interrumpida.
5 EIO Error de E/S.
6 ENXIO No existe dispositivo o dirección.
7 E2BIG Lista de argumentos demasiado larga.
8 ENOEXEC Error en formato de ejecución.
9 EBADF Descriptor de fichero erróneo.
10 ECHILD No existe el proceso hijo.
11 EGAIN Recurso no disponible temporalmente.
12 ENOMEM No hay suficiente espacio de memoria.
13 EACCES Permiso denegado.
14 EFAULT Dirección de memoria errónea.
15 ENOTBLK Se necesita un fichero de bloques.
16 EBUSY Recurso ocupado.
17 EEXIST Fichero existente.
18 EXDEV Enlace impropio.
19 ENODEV Dispositivo inexistente.
20 ENOTDIR No es un directorio.
21 EISDIR Es un directorio.
22 EINVAL Argumento no válido.
23 ENFILE Demasiados ficheros abiertos en el sistema.
24 EMFILE Demasiados ficheros abiertos.
26 ETXBUSY Fichero de texto ocupado.
27 EFBIG Fichero demasiado largo.
28 ENOSPC No queda espacio en el dispositivo.
29 ESPIPE Búsqueda no válida.
30 EROFS Fichero sólo de lectura.
32 EPIPE Tubería rota.
33 EDOM Error de dominio matemático.
34 ERANGE Resultado fuera de rango.
78 ETIMEDOUT Excedido tiempo de conexión (NFS).
88 EDQUOT Cuota de disco excedida.

Nota: No se tratan aquí los errores relativos a comunicaciones, ni a sockets.


2,5,2. Subrutina perror:

- Descripción:
Escribe un mensaje explicando un error.

- Formato:
#include <errno.h>
void perror (cadne)
char *cadena;

- Parámetro:
Cadena de caracteres que explica el error.

- Variables globales de errno.h:
extern int errno; Número de error.
extern char *sys_errlist[]; Tabla con la descripción de los errores del sistema.

- Comentarios:
a) Se imprime en la salida normal con un formato equivalente a la siguiente orden:
printf ("%s: %s\n", cadena, sys_errlist[errno]);


3. PIPES (TUBERÍAS).

3,1. Conceptos generales.

Descriptor de fichero:
Número entero positivo usado por un proceso para identificar un fichero abierto. Esta traducción se realiza mediante una tabla de descriptores de fichero, ubicado en la zona de datos del proceso.

Descriptores reservados:

Redirección:
Establecer copias del descriptor de ficheros de un archivo para encauzar las operaciones de E/S hacia otro fichero.

Tubería:
Mecanismo de intercomunicación entre procesos que permite que 2 o más procesos envíen información a cualquier otro.

Tubería sin nombre:
Enlace de comunicación unidireccional, capaz de almacenar su entrada (hasta 4 KB en BSD o hasta 40 KB en System V).

Tuberías nombradas (FIFO):
Permiten una comunicación menos restringida, ya que las colas FIFO existen en el sistema de archivos hasta que son borradas.
Características:


3,2. Redirección.

3,2,1. Subrutinas dup y dup2:

- Descripción:
Duplica un descriptor de fichero.

- Formatos:
#include <unistd.h>
#include <fcntl.h>
#include <sys/types>
int dup (desc_abierto)
int desc_abierto;

int dup2 (desc_abierto, desc_nuevo)
int desc_abierto, desc_nuevo;

- Parámetros:
desc_abierto: Descriptor de fichero abierto.
desc_nuevo: Nuevo descriptor de fichero devuelto por dup2.

- Devuelve:
dup devuelve el menor descriptor de fichero que esté libre.
dup2 devuelve el valor de desc_nuevo.
Ambas subrutinas devuelven el valor -1 en caso de error.

- Comentarios:
a) Las subrutinas dup y dup2 son equivalentes a la subrutina fcntl de la siguiente forma:
dup: fcntl (desc_abierto, F_DUPFD, 0);
dup2: close (desc_nuevo); fcntl (desc_abierto, F_DUPFD, desc_nuevo);

b) Puede redirigirse hacia un fichero cualquier descriptor especial.


- Ejemplo:

/* dup2.c - Redirección usando dup2 */
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main (int contargs, char *args[])
    {
    int desc_fich;

    if contargs < 3)
       {
       printf ("Formato: %s fichero comando [opciones].\n", args[0]);
       exit (1);
       }
    printf ("Ejemplo de redirección.\n");
    desc_fich = open (args[1], O_CREAT|O_TRUNC|O_WRONLY, 0);
    dup2 (desc_fich, 1);		/* Redirige la salida normal */
    close (desc_fich);
    execvp (args[2], &args[2];	/* Ejecuta comando */
    exit (1);
    }

$ dup2.e dup2.sal ls *.c
Ejemplo de redirección.

$ chmod 600 dup2.sal; cat dup2.sal
alarm.c		atexit.c	dup2.c		escritor_fifo.c
exec.c		fork.c		fork_huerf.c	kill.c
lector_fifo.c	pipe.c		pipe_conec.c	signal.c
system.c	waitpid.c


3,2,2. Subrutina fcntl:

- Descripción:
Realiza operaciones de control sobre ficheros abiertos, tales como:

- Formato:
#include <unistd.h>
#include <fcntl.h>
#include <sys/types>
int fcntl (descriptor, comando, argumento)
int descriptor, comando, argumento);

- Parámetros:
descriptor: Descriptor del fichero.
comando: Operación ha realizar.
argumento: Parámetro del comando.

- Devuelve:
Valor devuelto por el comando; -1, en caso de error.

- Operaciones:
F_DUPFD: Obtener el menor descriptor de fichero disponible que sea mayor que el parámetro descriptor. Mantiene el mismo puntero y las mismas características del fichero original.
F_GETFD: Obtener características del descriptor.
F_SETFD: Poner características del descriptor.
F_GETFL: Obtener estado del fichero.
F_SETFL: Poner estado del fichero.
F_GETLK: Obtener información de bloqueo.
F_SETLK: Poner bloqueo.
F_SETLKW: Poner bloqueo en una zona bloqueada.
F_GETOWN: Obtener PID (>0) o PGID (<0) del proceso que recibe las señales SIGIO o SIGURG.
F_SETOWN: Poner PID (>0) o PGID (<0) del proceso gestor de la E/S asíncrona.
F_CLOSEM: Cierra todos los descriptores desde descriptor hasta el valor máximo (OPEN_MAX).

- Características del descriptor de ficheros:

FD_CLOEXEC: Indica si el descriptor se cerrará ante una función exec.

- Estados del modo de acceso al fichero:
O_RDONLY: Abierto sólo para lectura.
O_RDWR: Abierto para lectura y escritura.
O_WRONLY: Abierto sólo para escritura.

- Bloqueos:
F_RDLCK: Bloqueo de lectura (compartido).
F_WRLCK: Bloqueo de escritura (exclusivo).
F_UNLCK: Sin bloqueo.

- Comentarios:
a) Un bloqueo de lectura evita que otros procesos activen bloqueos de lectura en cualquier zona del área protegida. Sí se permiten otros bloqueos de lectura en toda el área o en partes de ella.

b) Un bloqueo de escritura evita que otros procesos bloqueen dicha zona.

c) Los "abrazos mortales" en un sistema distribuido no siempre son detectables. El programa deberá usar temporizadores para poder liberar sus bloqueos.


3,3. Comunicación entre procesos emparentados.

3,3,1. Subrutina pipe

- Descripción:
Crea un canal de comunicación entre procesos emparentados.

- Formato:
#include <unistd.h>
int pipe (descriptores)
int descriptores[2];

- Parámetros:
Tabla que recibirá los descriptores de entrada y de salida de la tubería.

- Devuelve:
0, si se ha completado correctamente; -1, en caso de error.

- Comentarios:
a) descriptores[0] se abre para lectura y descriptores[1], para escritura.
b) La operación de lectura en descriptores[0] accede a los datos escritos en descriptores[1] como en una cola FIFO (primero en llegar, primero en servirse),


- Ejemplos:

/* pipe.c - Tubería sin nombre entre procesos padre e hijo */
#include <stdlib.h>
#include <unistd.h>

#define LEER		0
#define ESCRIBIR	1

int main ()
    {
    int descr[2];	/* Descriptores de E y S de la turbería */
    int  bytesleidos;
    char mensaje[100],
	*frase="Veremos si la transferecia es buena.";

    printf ("Ejemplo de tuberÍa entre padre e hijo.\n");
    pipe (descr);
    if (fork () == 0)
       {
       close (descr[LEER]);
       write (descr[ESCRIBIR], frase, strlen(frase));
       close (descr[ESCRIBIR]);
       }
    else
       {
       close (descr[ESCRIBIR]);
       bytesleidos = read (descr[LEER], mensaje, 100);
       printf ("Bytes leidos: %d\n");
       printf ("Mensaje: %s\n", bytesleidos, mensaje);
       close (descr[LEER]);
       }
    }

Ejemplo de tubería entre padre e hijo.
Bytes leídos: 36
Mensaje: Veremos si la transferencia es buena.


/* pipe_conec.c - Tubería entre 2 comandos usando pipe. */
#include <stdlib.h>
#include <unistd.h>

#define LEER		0
#define ESCRIBIR	1

int main (int contargs, char *args[])
    {
    int descr[2];	/* Descriptores de E y S de la turbería */

    if (contargs != 3)
       {
       printf ("Formato: %s comando_ent comando_sal.\n", args[0]);
       exit (1);
       }
    pipe (descr);
    if (fork () == 0)
       {
       close (descr[LEER]);
       dup2 (descr[ESCRIBIR], 1);
       close (descr[ESCRIBIR]);
       execlp (args[1], args[1], NULL);
       perror (args[0]);
       }
    else
       {
       close (descr[ESCRIBIR]);
       dup2 (descr[LEER], 0);
       close (descr[LEER]);
       execlp (args[2], args[2], NULL);
       perror (args[0]);
       }
    }

$ pipe_conec.e ls wc
37 37 354


3,4. Comunicación entre procesos no emparentados.

3,4,1. Subrutina mkfifo:

- Descripción:
Crea un canal FIFO de comunicaciones entre procesos que no necesitan estar emparentados.

- Formato:
#include <sys/mode.h>
int mkfifo (camino, modo)
const char *camino;
int modo;

- Parámetros:
camino: Camino completo del fichero FIFO.
modo: Tipo de fichero y permisos de acceso.

- Devuelve:
0, si se ha completado correctamente; -1, en caso de error.

- Comentarios:
a) La subrutina mkfifo es un interfaz de la rutina mknod para crear colas FIFO, las cuales no necesitan privilegios especiales del sistema.
b) El comando ls -al identifica una tubería nombrada con el carácter descriptor p


- Ejemplos:

/* lector_fifo.c - Tuberia con nombre usando mkfifo */
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mode.h>

int linea (int df, char *cad);

int main ()
    {
    int descr;
    char cadena[100];

    unlink ("tuberia");
    mkfifo ("tuberia", 0);
    chmod ("tuberia", 460);
    descr = open ("tuberia", O_RDONLY);
    while (linea (descr, cadena))
         printf ("%s\n", cadena);
    close (descr);
    pritnf ("Fin del lector.\n");
    }

int linea (int df, char *cad)
    {
    int n;

    do
      {
      n = read (df, cad, 1);
      }
    while (n > 0 && *cad++ != NULL);
    return (n > 0);
    }


#include <stdlib.h> #include <fcntl.h> #include <sys/mode.h> int main () { int descr, longmens, i; char mensaje[100]; sprintf (mensaje, "Un saludo desde el proceso %d", getpid ()); longmens = strlen (mensaje) + 1; do { /* intentar la conexion */ descr = open ("tuberia", O_WRONLY); if (descr == -1) sleep (1); } while (descr == -1); for (i=0; i<3; i++) { write (descr, mensaje, longmens); sleep (3); } close (descr); printf "Fin del escritor %d\n", getpid ()); }


#!/bin/ksh # fifo - Carga los procesos lector y escritor en 2o plano. lector_fifo.e & escritor_fifo.e & escritor_fifo.e &

$ fifo
$Un saludo desde el proceso 11996
Un saludo desde el proceso 10971
Un saludo desde el proceso 11996
Un saludo desde el proceso 10971
Un saludo desde el proceso 11996
Un saludo desde el proceso 10971
Fin del escritor 10971
Fin del escritor 11996
Fin del lector


<- Informática. <- -Món en la Telaraña.