DOCTORADO EN INFORMÁTICA
PROGRAMACIÓN DE SISTEMAS UNIX:
COMUNICACIÓN ENTRE PROCESOS
JUNIO 1.996
ÍNDICE.
-
- Conceptos generales.
- Ejecución de comandos.
- Subrutina system.
- Subrutinas exec.
- Creación de procesos.
- Subrutina fork.
- Subrutinas wait y waitpid.
- Terminación de un proceso.
- Subrutina exit.
- Subrutina atexit.
-
- Conceptos generales.
- Lista de las señales más importantes.
- Capturar señales.
- Subrutina signal:
- Subrutina sigaction:
- Subrutina kill:
- Alarmas y temporizadores.
- Subrutinas alarm y ualarm:
- Tratamiento de errores.
- Lista de errores más importantes.
- Subrutina perror:
-
- Conceptos generales.
- Redirección.
- Subrutinas dup y dup2:
- Subrutina fcntl:
- Comunicación entre procesos emparentados.
- Subrutina pipe
- Comunicación entre procesos no emparentados.
- Subrutina mkfifo:
Lista de programas.
1. PROCESOS.
- Proceso:
- programa o comando en ejecución.
- Características:
- Un proceso consta de código, datos y pila.
- Los procesos existen en una jerarquía de árbol (varios Hijos, un sólo padre).
- El sistema asigna un identificador de proceso (PID) único al iniciar el proceso.
- El planificador de tareas asigna un tiempo compartido para el proceso según su prioridad (sólo root puede cambiar prioridades).
- 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:
- Identificadores de proceso (PID), de proceso padre (PPID) y de grupo de procesos (PGID).
- Valores de prioridad (nice), de TTY y del bit de trazado.
- Directorio actual y directorio raíz.
- Máscara de ficheros, limites de longitud de ficheros, límites de recursos.
- Tiempos para activar alarmas y subrutinas times.
- Identificador de usuario de conexión.
- 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.
- Entorno.
- Bit FD_CLOEXEC para cada descriptor de fichero.
- Señales capturadas.
- SUID y SGID.
- Estado de privilegios y prioridades.
- Librerías compartidas y segmentos de memoria compartida.
- PGID y TTYGID.
- Directorio actual y directorio raíz.
- Máscara y límites de medida para ficheros.
- Eventos y estado de auditoría.
- Estado de depuración.
- c) Atributos diferenciadores entre padre e hijo:
- PID único.
- PPID distintos (el PPID del hijo coincide con el PID del padre).
- El
proceso hijo tiene su propia copia de los descriptores de fichero del
padre, pero comparte con éste un puntero a fichero para cada descriptor
del proceso padre.
- Bloqueos de proceso, texto y datos no se heredan.
- Las subrutinas times se ponen a 0.
- Las alarmas pendientes toman su valor inicial.
- Se eliminan las señales pendientes para el proceso 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:
- En el ejemplo, el proceso padre no espera la finalización del proceso hijo y termina antes que éste.
- Cuando un proceso queda huérfano, el proceso de iniciación (init) se convierte en su padre.
- Una
vez que finaliza el proceso padre, se devuelve el control al intérprete
de comandos, de ahí que aparezca el mensaje del "punto indicativo". El
proceso hijo no deberá mandar mensajes a la consola, como ocurre en
este ejemplo.
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:
- -1: waitpid actúa igual que wait, esperando cualquier hijo.
- >0: PID de un proceso hijo determinado.
- 0: para cualquier hijo con el mismo grupo de procesos que el padre.
- <-1: para cualquier hijo cuyo grupo de proceso sea igaul al valor absoluto de PID.
- opciones - Máscara de opciones. Sus bits son:
- WNOHANG: evita la suspensión del padre mientras esté esperando a algún hijo.
- WUNTRACED: el padre obtiene información adicional si el hijo recibe alguna de las señales SIGTTIN, SIGTTOU, SIGSSTP o SIGTSTOP .
- 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:
- Llamada a la función _cleanup para limpiar las áreas de E/S.
- Llamada a la subrutina especificada en la subrutina atexit.
- Llamada a la subrutina _exit para finalizar el proceso.
- 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.
- Generar un fichero core.
- Ignorar la señal.
- Parar el proceso que recibe la señal.
- 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:
- SIG_DFL: acción por defecto para dicha señal.
- SIG_IGN: ignorar la señal,
- - 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:
- SIG_DFL: acción por defecto para dicha señal.
- SIG_IGN: ignorar la señal.
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;
- SA_ONSTACK: La captura de la señal se realiza en una pila de señales en vez de en la pila del proceso.
- SA_OLDSTYLE: El parámetro señal se asocia con la ación por defecto (SIG_DFL) antes de llamar a la rutina de captura (no recomendable, la señal puede recurrir).
- SA_NOCLDSTOP: Evita que el proceso padre reciba una señal SIGCHLD cuando para el proceso hijo.
- - 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:
- >0: Identificador de un único proceso.
- 0: Procesos cuyo identificador del grupo de procesos sea igual al PID del proceso actual.
- <-1: Procesos cuyo identificador del grupo de procesos sea igaul al valor absoluto de proceso.
- 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,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:
- 0: entrada normal (stdin).
- 1: salida normal (stdout).
- 2: salida de error (stderr).
- 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:
- Permite comunicar procesos no emparentados.
- Tiene una entrada en el sistema de archivos.
- Usa una política de colas "primero en llegar, primero en servirse".
- Sólo disponible en UNIX System V.
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
- - Descripción:
- Realiza operaciones de control sobre ficheros abiertos, tales como:
- duplicar el descriptor,
- poner o leer características del descriptor,
- poner o leer estado del fichero,
- gestionar bloqueos de registros,
- gestionar la propiedad de la E/S asíncrona,
- cerrar varios ficheros.
- - 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
-Món en la Telaraña.