“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