No es un bug, es una característica no documentada

13/10/15

Procesos y servicios. Señales y sincronización

En un sistema, los procesos que se ejecutan simultáneamente interactúan entre sí. Esta interacción se produce incluso en el caso de procesos independientes, es decir, los que no necesitan cooperar para completar sus tareas.

Esto ocurre cuando varios procesos quieren acceder a los mismos recursos, y para resolver esta situación el SO dispone de un gestor de procesos para determinar el orden de acceso a estos recursos.

Las señales pueden considerarse un tipo de mensajes, aunque, comparado con otros medios de comunicación (sockets, pipes, etc.) resultan un mecanismo más pobre porque no permiten transmitir datos, pero sí proporcionan dos servicios fundamentales:

è Defensa del proceso establecido frente a incidencias comunicadas por el kernel. Si las señales no son gestionadas (o ignoradas, o capturadas) por el proceso al que van dirigidas, éste concluye inmediatamente lo que puede provocar una pérdida irrecuperable de datos.
è Mecanismo de comunicación entre dos procesos. Resulta útil y sencillo para avisar a un proceso de la aparición de eventos excepcionales, aunque no debe ser la forma habitual de que se comuniquen. Por ejemplo, cuando un usuario desea interrumpir un proceso de impresión que ha mandado por error.

Es decir, el uso de señales es un método sencillo de aviso de incidencias ya sea por circunstancias del propio proceso o por la intervención del otro.

Las señales pueden llegar en cualquier momento, por lo que los procesos no pueden limitarse a verificar una variable para comprobar si ha llegado una señal, sino que deben lanzar una rutina de tratamiento de la señal para gestionar automáticamente su recepción en el momento que aparezca.



Hay distintos tipos de señales, que podemos ver en éste gráfico


Una de las principales utilidades de las señales es la sincronización entre dos procesos. Esto es, un proceso realizará un conjunto de instrucciones cuando otro proceso se lo indique, o paralizará su actividad hasta que se cumpla una condición determinada, teniendo en cuenta la región crítica, que sería el trozo de código de un proceso que puede interferir con otro proceso.
Esta secuencia de acciones se ejecutan en paralelo y pueden repetirse infinitamente, con lo que el bucle tendría una traza tal que así:

1.       El proceso padre crea un proceso hijo
2.       El proceso padre ejecuta un conjunto de acciones a partir de las cuales se desea que el proceso hijo continúe
3.       Si no hay error y se desea que el hijo ejecute sus acciones:
3.1.    El proceso padre envía una señal SIGUSR1 al hijo para que comiece.
3.2.    El proceso hijo realiza un conjunto de acciones
3.3.    El proceso hijo envía la señal SIGUSR1 al padre
3.4.    Volvemos a 2
4.       En caso contrario:
4.1.    El proceso padre envía una señal SIGTERM al hijo para que termine
4.2.    El proceso hijo termina
4.3.    El proceso padre termina

El comando kill en Linux envía una señal a un proceso, indicando primero la señal y luego el PID.
Por ejemplo,termina kill -9 PID el proceso con el PID que hemos indicado.

Para crear una señal en C debemos tener en cuenta la estructura de las funciones que podemos utilizar

Esto envía una señal invocando a un manejador por puntero para que la reciba y la trate:
void (*signal(int señal, void(*Func)(int)(int));

Esto detiene el proceso hasta recibir una señal.

int pause(void);

Aquí dormimos el proceso en un tiempo indicado en segundos. También se interrumpe si recibe una señal.

unsigned int sleep(unsigned int seconds);

Y esto envía una señal para matar un proceso.

int kill(int pid, int señal);

Veamos un ejemplo de como un padre invoca a una señal que recibirá el hijo.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

void manejador (int segnal){

     printf("Hijo recibe señal... %d \n", segnal);
}

int main(){

     int pid_hijo;
     pid_hijo = fork(); // creamos hijo

     switch(pid_hijo){
          case -1:
                printf("ERROR AL CREAR EL PROCESO HIJO... \n");
                exit(-1);
                break;
          case 0: // HIJO
                signal(SIGUSR1, manejador); // Invocamos al puntero al que referencia la función
                while(1){};
                break;
          default: // PADRE
                sleep(1);
                kill(pid_hijo, SIGUSR1);
                sleep(1);
                kill(pid_hijo, SIGUSR1);
                sleep(1);
                break;
     }
     return 0;
}

1 comentario: