“Cando alguén intenta matar un proceso zombi”
E é que a quen non lle pasou que tentaba pechalos e era imposible :P, pero claro, que pasa se o proceso que os xerou nunca os recolle?, e se comezan a toma-los recursos do servidor e non queda máis remedio que reinicialo proceso, non é moi boa forma, non?
Pois xa que estivemos fozando co GDB para manipular un proceso e pechar os descriptores dos arquivos, ímos ver de eliminar os zombis :)
Ben, recapitulando, os procesos zombies foron creados por `fork()` e non foron recollidos polo proceso que os xerou, hai dúas formas de matalos, un é que o proceso os recolla (faga `wait()`) ou que ese proceso finalice, nese caso o proceso pai pasará a ser `init (pid 1)`, que os elimina cada pouco tempo.
Para as probas tiraremos deste programa, que xera 3 zombies:
#include <stdio.h>
#include <unistd.h>
#define ZOMBIE_NUMBER 3
int main(int argc, char **argv){
int i;
for (i = 0; i < ZOMBIE_NUMBER; i++){
pid_t pid = fork();
if (pid == 0){
return 0;
}
else if (pid == -1){
perror("fork");
return 1;
}
}
while (1){
printf(".");
fflush(stdout);
sleep(1);
}
return 0;
}
E xa está, compila sen cousas extrañas.
gcc zombie_spawner.c -o zombie_spawner
Como a idea é non reiniciar o server, teremos que facer `wait()`, ímos facer un script para automatizar o proceso, tomará como parámetro o PID do proceso a limpar, a partir de ahí xa podemos tomar os procesos zombies, por exemplo no meu caso o contexto é este:
$ ps aux|grep zombie 1000 25202 0.0 0.0 4044 324 pts/2 S+ 01:43 0:00 ./zombie_spawner 1000 25203 0.0 0.0 0 0 pts/2 Z+ 01:43 0:00 [zombie_spawner] <defunct> 1000 25204 0.0 0.0 0 0 pts/2 Z+ 01:43 0:00 [zombie_spawner] <defunct> 1000 25205 0.0 0.0 0 0 pts/2 Z+ 01:43 0:00 [zombie_spawner] <defunct> 1000 25208 0.0 0.0 14264 936 pts/4 S+ 01:43 0:00 grep --color=auto zombie # Ignorade esta liña $
Pero a idea é identificar os zombies programáticamente e de forma limpa.
Sabemos que o PID do proceso pai é 25202, para saber os procesos que descenden del:
$ ps h --ppid=25202 25203 pts/2 Z+ 0:00 [zombie_spawner] <defunct> 25204 pts/2 Z+ 0:00 [zombie_spawner] <defunct> 25205 pts/2 Z+ 0:00 [zombie_spawner] <defunct> $
E neste caso todos son zombis, pero por se se da o caso de que houbera un que non o fora podémos limpar a lista tirando de expresión regular, vexamos:
Comezo da liña: ^
Un número calquera de espazos en branco: \s*
25203 Un ou máis díxitos: \d+
Un ou máis espazos en branco: \s+
pts/2 Un ou máis caracteres non espacios: [^ ]+
Un ou máis caracteres en branco: \s+
Z E polo fin unha Z: Z
A expresión regular é enton "^\s*\d+\s+[^ ]+\s+Z"
$ ps h --ppid=25202|grep -P '^\s*\d+\s+[^ ]+\s+Z' # O h elimina a cabeceira 25203 pts/2 Z+ 0:00 [zombie_spawner] <defunct> 25204 pts/2 Z+ 0:00 [zombie_spawner] <defunct> 25205 pts/2 Z+ 0:00 [zombie_spawner] <defunct> $
E esta parte xa está, con `awk` podemos separar a primeira columna.
$ ps h --ppid=25202|grep -P '^\s*\d+\s+[^ ]+\s+Z'|awk '{print $1;}'
25203
25204
25205
$E o resto non ten chicha ningunha, é chamar a `tempfile` para xerar un arquivo temporal e meter dentro os `waitpid()` e un `detach` o final para deixar correr o proceso.
fname=`tempfile`
(for zpid in `ps h --ppid=$ppid|grep -P '^\s*\d+\s+[^ ]+\s+Z'|awk '{print $1;}'`;do
echo "print waitpid($zpid, 0, 0)"
done
echo "detach") > $fname
E só queda darlle ese arquivo de órdes a GDB (podemos usar o /proc/$ppid/exe coma binario):
gdb -batch -p $ppid /proc/$ppid/exe -x $fname
Resultado:
$ ps h --ppid=25202|grep -P '^\s*\d+\s+[^ ]+\s+Z'|awk '{print $1;}'
25203
25204
25205
$ sudo bash kill-zombie.sh 25202
warning: not using untrusted file "/home/kenkeiras/.gdbinit"
0x00007f71416c6bf0 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-template.S:82
82 ../sysdeps/unix/syscall-template.S: Non hai tal ficheiro ou directorio.
in ../sysdeps/unix/syscall-template.S
$1 = 25203
$2 = 25204
$3 = 25205
$ ps h --ppid=25202|grep -P '^\s*\d+\s+[^ ]+\s+Z'|awk '{print $1;}'
$ # A lista, limpiña :)
E o script completo
#!/usr/bin/env bash
# Comprobamos se temos o parámetro
if [ -z "$1" ];then
echo "$0 <zombie parent pid&gT;"
exit 0
fi
# Gardámolo nunha variable cómoda
ppid=$1
# Xeramos un arquivo temporal
fname=`tempfile`
# Xeramos as instrucción para GDB
(for zpid in `ps h --ppid=$ppid|grep -P '^\s*\d+\s+[^ ]+\s+Z'|awk '{print $1;}'`;do
echo "print waitpid($zpid, 0, 0)" # Limpar os procesos zombi
done
# E deixalo correr o acabar
echo "detach") > $fname
# Executamos as órdes
gdb -batch -p $ppid /proc/$ppid/exe -x $fname
# E limpamos o arquivo
rm $fname
Espero que vos resulte útil, saúdos

No hay comentarios:
Publicar un comentario