jueves, 5 de enero de 2012

Usando el comando kill

For english version

En mi experiencia de trabajo, he escuchado muchas veces con preocupación que la técnica utilizada para terminar procesos / servicios del sistema, es utilizando kill -9, porque el proceso siempre finaliza (verídico). Esta acción presenta un inconveniente, se puede perder información.

Para explicar esto, primero hay que saber que hace el comando kill. El comando kill, lo que hace es enviar señales a los procesos (ver explicación). Este mecanismo de comunicación entre procesos es simple y asíncrono (generalmente). Si ya revisaste el manual (man 7 signal) o el enlace en wikipedia, veras que hay varios tipos de señales, por ejemplo: SIGINT, SIGTERM, SIGKILL, etc; y entre estas, hay ciertas señales que no se pueden manejar, por ejemplo: SIGKILL. ¿Qué significa que no se pueden manejar? Según mi experiencia es que no puedes programar una función que haga algo con la señal, en otras palabras, tu programa nunca la ve llegar, el sistema operativo simplemente la aplica al proceso.

Para comprender mejor este punto conviene programar un poco con nuestro querido lenguaje C (espero que tengas el compilador y todos los juguetes)

Primero creamos un archivo / fichero en C llamado test.c con lo siguiente

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

volatile int FINISH = 0;

void handler(int sig)
{
    if (sig == SIGINT) {
        fprintf(stderr, "Interrupted: Closing files\n");
        FINISH = 1;
    } else if (sig == SIGTERM) {
        fprintf(stderr, "Terminating: Closing files\n");
        FINISH = 1;
    } else {
        fprintf(stderr, "I don't know what to do with this signal\n");
    }
}

int main(void)
{
    FILE *f = NULL;
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = handler;
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction with SIGINT");
    }
    if (sigaction(SIGTERM, &sa, NULL) == -1) {
        perror("sigaction with SIGTERM");
    }
    if ((f = fopen("test_file.txt", "w")) == NULL) {
        perror("Couldn't open test_file.txt for writing");
        exit(1);
    }
    while (!FINISH) {
        fprintf(f, "hello");
        fprintf(f, " ");
        sleep(5);
        fprintf(f, "world\n");
    }
    fclose(f);
    exit(0);
}

Como puntos importantes, podemos resaltar la función handler, que es la que maneja las señales que definimos con sigaction. Se nota que no intente manejar la señal SIGKILL a.k.a 9, porque no se puede manejar, y si lo intentas el sistema operativo ignorara tu petición. El código como se ve esta en un bucle infinito escribiendo hola mundo en un archivo / fichero. Cuando le enviemos la señal de interrumpir o de terminar, el programa terminara de escribir un último hola mundo y procederá a cerrar el archivo / fichero. Si en cambio terminamos el proceso con SIGKILL, es muy probable que el archivo / fichero termine vacío o con una linea que no diga hola mundo. A probarlo.

Compilar
$ cc -Wall -o test test.c

Ejecutar
$ ./test

En otro terminal ejecutamos alguno de los siguientes tres comandos
pkill -SIGINT test
pkill -SIGTERM test
pkill -SIGKILL test

Entre ejecuciones revisamos el archivo / fichero test_file.txt y comparamos los resultados. Seguramente la vez que ejecutaste el kill con SIGKILL los resultados no fueron los esperados. Ahora que sabes porque por ejemplo, no es conveniente terminar procesos de un manejador de bases e datos con SIGKILL. Puedes terminar con información incompleta o corrupta (cantidades infinitas de diversión ^_^).

Con esta moraleja en mente, ya sabes que primero se intenta terminar un proceso pidiendoselo diplomáticamente (SIGTERM), y ya si despues de un tiempo no responde, llamas a tu amigo sicario (el sistema operativo) para que le haga el trabajo sucio (SIGKILL).

Hasta otra oportunidad.

Alejandro

No hay comentarios:

Publicar un comentario