Facebook Twitter Google Digg Reddit Email Imprimir
Time4VPS.EU - VPS hosting in Europe

Programador V/S Zombies

Evite dejar procesos "defunct" al utilizar fork() en su programación

Introducción

Hace unos dís, compartiendo con mi hija, me dijo Hooo ... los Zombies ... Cuidado con los Zombies, lo que me llevó a recordar los procesos "Zombie" o "defunct" en Linux, que son una molestias y por los cuales hay que tener cuidado. Para evitarlos, solo se necesita entenderlo y hacer una programación adecuada.

Los ejemplos se encuentran en lenguaje Perl, pero entendiendo el concepto puede ser implementado en otros lenguajes

¿Qué es un Zombie?

Si no está vivo, ni muerto, entonces es un Zombie, el proceso terminó, pero aun utiliza una entrada en la tabla de procesos del Sistema Operativo, dado que su proceso Padre (Proceso que lo genera) no a recepcionado su código de salida, como en exit(0);

¿Por qué se producen?

Se trata de un problema de programación al utilizar la funcion fork(), que crea un nuevo proceso para ejecutarse en paralelo. El proceso que lo crea se le llama Padre y el nuevo proceso se le llama Hijo

La función fork() crea una copia del proceso actual por lo que tenemos 2 contextos dentro de nuestro programa, que se determina por el valor retornado por la función:

Veamos un programa simple utilizando fork(), donde se crearán 10 procesos hijo que terminará luego de 1 a 10 segundos respectivamente. Mientras el Padre se encuentre ejecutando, los hijos quedarán como Zombie a medida que ellos terminen:



#!/usr/bin/perl
#

$|=1 ;

for ( $i=0 ; $i<10 ; $i++) {
  if ( (local $pid=fork()) == 0 ) {
    print "Hijo $i en ejecucion\n" ;
    sleep($i+1) ;
    exit($i) ;
  } else {
    print "Se inicia proceso $pid\n" ;
  }
}

print "Hijos creados, espero 300 segundos ... Cuantos zombies hay?\n" ;
for ($c=0;$c<100;$c++) {
  sleep 3 ;
  print "." ;
}

print "Terminado normalmente\n" ;
exit(0);

Nos vamos a otro terminal y revisamos los procesos "defunct"

ps -fea | grep defunct

testuser 27090 27089  0 16:11 pts/8    00:00:00 [zombiemaker.pl] <defunct>
testuser 27091 27089  0 16:11 pts/8    00:00:00 [zombiemaker.pl] <defunct>
testuser 27092 27089  0 16:11 pts/8    00:00:00 [zombiemaker.pl] <defunct>
testuser 27093 27089  0 16:11 pts/8    00:00:00 [zombiemaker.pl] <defunct>
testuser 27094 27089  0 16:11 pts/8    00:00:00 [zombiemaker.pl] <defunct>
testuser 27095 27089  0 16:11 pts/8    00:00:00 [zombiemaker.pl] <defunct>
testuser 27096 27089  0 16:11 pts/8    00:00:00 [zombiemaker.pl] <defunct>
testuser 27097 27089  0 16:11 pts/8    00:00:00 [zombiemaker.pl] <defunct>
testuser 27098 27089  0 16:11 pts/8    00:00:00 [zombiemaker.pl] <defunct>
testuser 27099 27089  0 16:11 pts/8    00:00:00 [zombiemaker.pl] <defunct>
testuser 27421 11388  0 16:11 pts/7    00:00:00 grep defunct

¿Cómo podemos evitarlo?

Cuando el proceso Hijo termina, se genera una señal CHLD que nos da la opción de Ignorarlo o ejecutar una función que capture el resultado:

IGNORE

En algunos sistemas operativos, la implementación de Perl evita los molestos zombies asignando 'IGNORE' en el vector de señales, al comienzo del programa, entre ellos se encuentra CentOS 6 y 7:


#!/usr/bin/perl
#

local $SIG{CHLD} = 'IGNORE' ;

$|=1 ;
...

Capturar el resultado con función wait()

También podemos asignar al vector de señales una función que actúe como handler y nos permita reaccionar, conocer el proceso que terminó y el código de retorno:


#!/usr/bin/perl
#

local $SIG{CHLD} = \&signal_chld_handler;

$|=1 ;

for ( $i=0 ; $i<10 ; $i++) {
  if ( (local $pid=fork()) == 0 ) {
    print "Hijo $i en ejecucion\n" ;
    sleep($i+1) ;
    exit($i) ;
  } else {
    print "Se inicia proceso $pid\n" ;
  }
}

print "Hijos creados, espero 300 segundos ... Cuantos zombies hay?\n" ;
for ($c=0;$c<100;$c++) {
  sleep 3 ;
  print "." ;
}

print "Terminado normalmente\n" ;
exit(0);

sub signal_chld_handler {
    local $pid = wait();
    local $rc = $? >> 8 ;
    print "Finalizado proceso $pid con codigo $rc\n" ;
    return (0) ;
}

La función wait() espera hasta que alguno de los hijos haya terminado, que justamente es la condición por la que se llama a la función de handler. El código de exit($rc) es retornado en la variable $?, pero debe ser desplazado 8 posiciones a la derecha (o dividido en 256).

Si el programa utiliza llamadas a system() o similares como qx{}, su salida también sería intercetpada por el handler.

Otras observaciones

Existe la opción de utilizar la función waitpid() que puede ser utilizada de manera de no bloquear el programa y permite evitar el riego que se acumule un reentrada en el handler (que dos hijos terminen juntos, generando solo una llamada al handler), pero requiere de librerias adicionales:


   use POSIX ":sys_wait_h";
    $SIG{CHLD} = sub {
        while ((my $child = waitpid(-1, WNOHANG)) > 0) {
            $Kid_Status{$child} = $?;
        }
    };

Escrito por: Luis Hernán de la Barra, 17/03/2017

Generado por Sistema y almacenado en cache

Wyzer
Luis Hernán de la Barra
E-Mail:ldelabar@wyzer.cl
Web:www.wyzer.cl