Supongamos, por ejemplo, que tiene un script de shell similar a:
longrunningthing &
p=$!
echo Killing longrunningthing on PID $p in 24 hours
sleep 86400
echo Time up!
kill $p
Debería hacer el truco, ¿no? Excepto que el proceso puede haber terminado antes y su PID puede haber sido reciclado, lo que significa que algún trabajo inocente obtiene una bomba en su cola de señal. En la práctica, esto posiblemente importa, pero de todos modos me preocupa. Hackear algo largo para caer muerto por sí mismo, o mantener / eliminar su PID en el FS funcionaría, pero estoy pensando en la situación genérica aquí.
killall
qué coincidencias en el nombre, por lo que al menos solo está eliminando un proceso con el mismo nombre quelongrunningthing
. Asumiendo que solo tendrá uno de estos funcionando a la vez.Respuestas:
Lo mejor sería usar el
timeout
comando si lo tiene, que está destinado para eso:La implementación actual de GNU (8.23) al menos funciona utilizando
alarm()
o equivalente mientras espera el proceso secundario. No parece estar protegiendo contra laSIGALRM
entrega entre elwaitpid()
regreso y latimeout
salida (cancelando efectivamente esa alarma ). Durante esa pequeña ventana,timeout
incluso puede escribir mensajes en stderr (por ejemplo, si el niño arrojó un núcleo), lo que agrandaría aún más esa ventana de carrera (indefinidamente si stderr es una tubería completa, por ejemplo).Personalmente, puedo vivir con esa limitación (que probablemente se solucionará en una versión futura).
timeout
también tendrá mucho cuidado de informar el estado de salida correcto, manejar otros casos de esquina (como SIGALRM bloqueado / ignorado en el inicio, manejar otras señales ...) mejor de lo que probablemente podría hacer a mano.Como aproximación, podría escribirlo
perl
como:Hay un
timelimit
comando en http://devel.ringlet.net/sysutils/timelimit/ (precede a GNUtimeout
por unos meses).Ese usa un
alarm()
mecanismo similar pero instala un controladorSIGCHLD
(ignorando a los niños detenidos) para detectar la muerte del niño. También cancela la alarma antes de ejecutarwaitpid()
(eso no cancela la entrega deSIGALRM
si estaba pendiente, pero por la forma en que está escrito, no puedo ver que sea un problema) y mata antes de llamarwaitpid()
(así que no puedo matar un pid reutilizado )netpipes también tiene un
timelimit
comando. Ese precede a todos los demás por décadas, adopta otro enfoque, pero no funciona correctamente para los comandos detenidos y devuelve un1
estado de salida al finalizar el tiempo de espera.Como respuesta más directa a su pregunta, puede hacer algo como:
Es decir, verifique que el proceso todavía sea un hijo nuestro. Nuevamente, hay una pequeña ventana de carrera (entre
ps
recuperar el estado de ese proceso ykill
matarlo) durante el cual el proceso podría morir y su pid será reutilizado por otro proceso.Con algunas conchas (
zsh
,bash
,mksh
), puede pasar especificaciones de trabajo en lugar de los PID.Eso solo funciona si genera un solo trabajo en segundo plano (de lo contrario, obtener la especificación de trabajo correcta no siempre es posible de manera confiable).
Si eso es un problema, simplemente inicie una nueva instancia de shell:
Eso funciona porque el shell elimina el trabajo de la tabla de trabajos cuando el niño muere. Aquí, no debería haber ninguna ventana de carrera ya que para cuando el shell llama
kill()
, o la señal SIGCHLD no se ha manejado y el pid no se puede reutilizar (ya que no se ha esperado), o se ha manejado y el el trabajo se ha eliminado de la tabla de procesos (ekill
informaría un error).bash
'skill
al menos bloquea SIGCHLD antes de acceder a su tabla de trabajo para expandirlo%
y desbloquearlo después dekill()
.Otra opción para evitar tener ese
sleep
proceso dando vueltas incluso después de quecmd
haya muerto, conbash
oksh93
es usar una tubería con enread -t
lugar desleep
:Ese todavía tiene condiciones de carrera, y pierdes el estado de salida del comando. También supone
cmd
que no cierra su fd 4.Podrías intentar implementar una solución libre de carrera en
perl
:(aunque necesitaría ser mejorado para manejar otros tipos de casos de esquina).
Otro método sin raza podría ser el uso de grupos de procesos:
Sin embargo, tenga en cuenta que el uso de grupos de procesos puede tener efectos secundarios si hay E / S en un dispositivo terminal involucrado. Sin embargo, tiene el beneficio adicional de matar todos los demás procesos adicionales generados por
cmd
.fuente
timeout
no es portátil, la respuesta mencionó primero una solución portátil.jobs
y luego saber que (ya que es su propio shell, en el que tiene control sobre lo que sucede a continuación) el próximo fondo trabajo será N + 1? [entonces puedes guardar N y luego matar a% N + 1])En general, no puedes. Todas las respuestas dadas hasta ahora son heurísticas con errores. Solo hay un caso en el que puede usar con seguridad el pid para enviar señales: cuando el proceso de destino es un hijo directo del proceso que enviará la señal, y el padre aún no lo ha esperado. En este caso, incluso si ha salido, el pid está reservado (esto es lo que es un "proceso zombie") hasta que el padre lo espere. No conozco ninguna forma de hacerlo limpiamente con el shell.
Una forma segura alternativa de eliminar procesos es iniciarlos con un conjunto de control tty en un pseudo-terminal para el cual es dueño del lado maestro. Luego puede enviar señales a través del terminal, por ejemplo, escribiendo el carácter para
SIGTERM
oSIGQUIT
sobre el pty.Otra forma más conveniente de crear scripts es utilizar una
screen
sesión con nombre y enviar comandos a la sesión de pantalla para finalizarla. Este proceso se lleva a cabo a través de una tubería o un conector Unix nombrado de acuerdo con la sesión de pantalla, que no se reutilizará automáticamente si elige un nombre único seguro.fuente
Al iniciar el proceso, guarde su hora de inicio:
Antes de intentar matar el proceso, deténgalo (esto no es realmente esencial, pero es una forma de evitar las condiciones de carrera: si detiene el proceso, su pid no se puede reutilizar)
Verifique que el proceso con ese PID tenga la misma hora de inicio y, en caso afirmativo, elimínelo; de lo contrario, deje que el proceso continúe:
Esto funciona porque solo puede haber un proceso con el mismo PID y hora de inicio en un sistema operativo determinado.
Detener el proceso durante la verificación hace que las condiciones de carrera no sean un problema. Obviamente, esto tiene el problema de que algunos procesos aleatorios pueden detenerse durante algunos milisegundos. Dependiendo del tipo de proceso, esto puede o no ser un problema.
Personalmente, simplemente usaría python y
psutil
que maneja la reutilización de PID automáticamente:fuente
ps -o start=
formato cambia de 18:12 a 26 de enero después de un tiempo. Tenga cuidado con los cambios de horario de verano también. Si está en Linux, probablemente lo prefieraTZ=UTC0 ps -o lstart=
.lstart
, loEn un sistema Linux, puede asegurarse de que un pid no se reutilizará manteniendo vivo su espacio de nombres pid. Esto se puede hacer a través del
/proc/$pid/ns/pid
archivo.man namespaces
-init
.man pid_namespaces
-util-linux
paquete proporciona muchas herramientas útiles para manipular espacios de nombres. Sinunshare
embargo, por ejemplo, si aún no ha organizado sus derechos en un espacio de nombres de usuario, requerirá derechos de superusuario:Si no ha organizado un espacio de nombres de usuario, aún puede ejecutar comandos arbitrarios de forma segura eliminando inmediatamente los privilegios. El
runuser
comando es otro binario (no setuid) provisto por elutil-linux
paquete e incorporarlo podría verse así:...y así.
En el ejemplo anterior dos interruptores se pasan a
unshare(1)
la--fork
bandera que hace que la invocash -c
proceso creó el primer hijo y asegura suinit
estado y la--pid
bandera que indica aunshare(1)
crear un espacio de nombres PID.El
sh -c
proceso genera cinco shells secundarios en segundo plano, cada uno unwhile
ciclo infinito que continuará agregando la salidadate
al final delog
mientras el tiempo seasleep 1
verdadero. Después de generar estos procesos, sesh
requierensleep
5 segundos adicionales y luego finaliza.Quizás valga la pena señalar que si
-f
no se usara la bandera, ninguno de loswhile
bucles de fondo terminaría, pero con él ...SALIDA:
fuente
Considera mejorar tu
longrunningthing
comportamiento un poco, un poco más como un demonio. Por ejemplo, puede hacer que cree un archivo pid que permita al menos un control limitado del proceso. Hay varias formas de hacer esto sin modificar el binario original, todas involucrando un contenedor. Por ejemplo:un script de envoltura simple que iniciará el trabajo requerido en segundo plano (con redirección de salida opcional), escriba el PID de este proceso en un archivo, luego espere a que el proceso termine (usando
wait
) y elimine el archivo. Si durante la espera el proceso se mata, por ejemplo, por algo comoel contenedor solo se asegurará de que se elimine el archivo pid.
un contenedor de monitor, que colocará su propio PID en algún lugar y capturará (y responderá) las señales que se le envíen. Ejemplo simple:
Ahora, como señalaron @R .. y @ StéphaneChazelas, estos enfoques a menudo tienen una condición de carrera en algún lugar o imponen una restricción en la cantidad de procesos que puede generar. Además, no maneja los casos, donde la
longrunningthing
bifurcación puede separarse y los niños se separan (lo que probablemente no sea el problema en la pregunta original).Con los núcleos de Linux recientes (leídos hace un par de años), esto se puede tratar muy bien mediante el uso de cgroups , a saber, el congelador , que, supongo, es lo que usan algunos sistemas modernos de inicio de Linux.
fuente
longrunningthing
es que no tienes control sobre lo que es. También di un ejemplo de script de shell porque explicaba el problema. Me gustan las suyas y todas las otras soluciones creativas aquí, pero si está usando Linux / bash, hay un "tiempo de espera" incorporado para eso. ¡Supongo que debería obtener la fuente de eso y ver cómo lo hace!timeout
es un shell incorporado. Ha habido varias implementaciones de un comando para Linux, una se agregó recientemente (2008) a GNU coreutils (por lo tanto, no es específica de Linux), y eso es lo que la mayoría de las distribuciones de Linux usan hoy en día.timeout
Si está ejecutando en Linux (y algunos otros * nixes), puede verificar si el proceso que pretende matar todavía se usa y si la línea de comando coincide con su largo proceso. Algo como :
Una alternativa puede ser verificar durante cuánto tiempo se está ejecutando el proceso que pretende matar, con algo así
ps -p $p -o etime=
. Puede hacerlo usted mismo extrayendo esta información/proc/$p/stat
, pero esto sería complicado (el tiempo se mide en segundos y también deberá usar el tiempo de actividad del sistema/proc/stat
).De todos modos, generalmente no puede asegurarse de que el proceso no se reemplace después de su verificación y antes de matarlo.
fuente
cat pidfile
resultado. No recuerdo una manera limpia de hacerlo solo en shell. Sin embargo, la respuesta propuesta para el espacio de nombres parece interesante ...Esta es realmente una muy buena pregunta.
La forma de determinar la unicidad del proceso es mirar (a) dónde está en la memoria; y (b) lo que contiene ese recuerdo. Para ser específicos, queremos saber en qué lugar de la memoria está el texto del programa para la invocación inicial, porque sabemos que el área de texto de cada hilo ocupará una ubicación diferente en la memoria. Si el proceso muere y se inicia otro con el mismo pid, el texto del programa para el nuevo proceso no ocupará el mismo lugar en la memoria y no contendrá la misma información.
Entonces, inmediatamente después de iniciar su proceso, haga
md5sum /proc/[pid]/maps
y guarde el resultado. Más tarde, cuando desee matar el proceso, haga otro md5sum y compárelo. Si coincide, entonces mata al pid. Si no, no lo hagas.Para ver esto por ti mismo, lanza dos proyectiles de bash idénticos. Examínelos
/proc/[pid]/maps
y encontrará que son diferentes. ¿Por qué? Porque aunque es el mismo programa, ocupan diferentes ubicaciones en la memoria y las direcciones de su pila son diferentes. Entonces, si su proceso muere y su PID se reutiliza, incluso si el mismo comando se relanza con los mismos argumentos , el archivo "mapas" será diferente y sabrá que no está tratando con el proceso original.Ver: página de manual de proc para más detalles.
Tenga en cuenta que el archivo
/proc/[pid]/stat
ya contiene toda la información que otros carteles han mencionado en sus respuestas: edad del proceso, padre pid, etc. Este archivo contiene información estática e información dinámica, por lo que si prefiere utilizar este archivo como base de comparación, luego, al iniciar sulongrunningthing
, debe extraer los siguientes campos estáticos delstat
archivo y guardarlos para compararlos más tarde:pid, nombre de archivo, pid de padre, id de grupo de proceso, terminal de control, proceso de tiempo iniciado después del inicio del sistema, tamaño del conjunto residente, la dirección del inicio de la pila,
tomados en conjunto, lo anterior identifica de manera única el proceso, por lo que esto representa otro camino a seguir. En realidad, podría salirse con la suya con solo "pid" y "proceso de tiempo iniciado después del arranque del sistema" con un alto grado de confianza. Simplemente extraiga estos campos del
stat
archivo y guárdelo en algún lugar al iniciar su proceso. Más tarde, antes de matarlo, extráigalo nuevamente y compárelo. Si coinciden, entonces está seguro de que está viendo el proceso original.fuente
/proc/[pid]/maps
cambios a lo largo del tiempo a medida que se asigne memoria adicional o la pila crezca o se mapeen nuevos archivos ... ¿Y qué significa inmediatamente después del lanzamiento ? ¿Después de que todas las bibliotecas se hayan mapeado? ¿Cómo determinas eso?md5sum
en sus archivos de mapas. Lo dejaré correr por un día o dos e informaré aquí con los resultados.Otra forma sería verificar la edad del proceso antes de matarlo. De esa manera, puede asegurarse de no matar un proceso que no se genera en menos de 24 horas. Puede agregar una
if
condición basada en eso antes de matar el proceso.Esta
if
condición verificará si la ID del proceso$p
es inferior a 24 horas (86400 segundos).PD: - El comando
ps -p $p -o etime=
tendrá el formato<no.of days>-HH:MM:SS
fuente
mtime
de/proc/$p
no tiene nada que ver con la hora de inicio del proceso.if
condición. Por favor, siéntase libre de comentar si tiene errores.Lo que hago es, después de haber matado el proceso, hacerlo de nuevo. Cada vez que hago eso, la respuesta vuelve, "no hay tal proceso"
No podría ser más simple y he estado haciendo esto durante años sin ningún problema.
fuente