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í.

killallqué 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
timeoutcomando 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 laSIGALRMentrega entre elwaitpid()regreso y latimeoutsalida (cancelando efectivamente esa alarma ). Durante esa pequeña ventana,timeoutincluso 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).
timeouttambié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
perlcomo:Hay un
timelimitcomando en http://devel.ringlet.net/sysutils/timelimit/ (precede a GNUtimeoutpor 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 deSIGALRMsi 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
timelimitcomando. Ese precede a todos los demás por décadas, adopta otro enfoque, pero no funciona correctamente para los comandos detenidos y devuelve un1estado 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
psrecuperar el estado de ese proceso ykillmatarlo) 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 (ekillinformaría un error).bash'skillal 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
sleepproceso dando vueltas incluso después de quecmdhaya muerto, conbashoksh93es usar una tubería con enread -tlugar desleep:Ese todavía tiene condiciones de carrera, y pierdes el estado de salida del comando. También supone
cmdque 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
timeoutno es portátil, la respuesta mencionó primero una solución portátil.jobsy 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
SIGTERMoSIGQUITsobre el pty.Otra forma más conveniente de crear scripts es utilizar una
screensesió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
psutilque 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/pidarchivo.
Puede aislar un grupo de procesos, básicamente cualquier número de procesos, espaciando sus nombresman namespaces-init.
Elman pid_namespaces-util-linuxpaquete proporciona muchas herramientas útiles para manipular espacios de nombres. Sinunshareembargo, 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
runusercomando es otro binario (no setuid) provisto por elutil-linuxpaquete e incorporarlo podría verse así:...y así.
En el ejemplo anterior dos interruptores se pasan a
unshare(1)la--forkbandera que hace que la invocash -cproceso creó el primer hijo y asegura suinitestado y la--pidbandera que indica aunshare(1)crear un espacio de nombres PID.El
sh -cproceso genera cinco shells secundarios en segundo plano, cada uno unwhileciclo infinito que continuará agregando la salidadateal final delogmientras el tiempo seasleep 1verdadero. Después de generar estos procesos, seshrequierensleep5 segundos adicionales y luego finaliza.Quizás valga la pena señalar que si
-fno se usara la bandera, ninguno de loswhilebucles de fondo terminaría, pero con él ...SALIDA:
fuente
Considera mejorar tu
longrunningthingcomportamiento 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
longrunningthingbifurcació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
longrunningthinges 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!timeoutes 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.timeoutSi 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 pidfileresultado. 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]/mapsy 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]/mapsy 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]/statya 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 delstatarchivo 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
statarchivo 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]/mapscambios 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?md5sumen 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
ifcondición basada en eso antes de matar el proceso.Esta
ifcondición verificará si la ID del proceso$pes inferior a 24 horas (86400 segundos).PD: - El comando
ps -p $p -o etime=tendrá el formato<no.of days>-HH:MM:SSfuente
mtimede/proc/$pno tiene nada que ver con la hora de inicio del proceso.ifcondició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