Dado un proceso de shell (p sh
. Ej. ) Y su proceso hijo (p cat
. Ej. ), ¿Cómo puedo simular el comportamiento de Ctrl+ Cusando la ID de proceso del shell?
Esto es lo que he intentado:
Corriendo sh
y luego cat
:
[user@host ~]$ sh
sh-4.3$ cat
test
test
Enviar SIGINT
a cat
otra terminal:
[user@host ~]$ kill -SIGINT $PID_OF_CAT
cat
recibió la señal y terminó (como se esperaba).
Enviar la señal al proceso padre no parece funcionar. ¿Por qué no se propaga la señal cat
cuando se envía a su proceso padre sh
?
Esto no funciona:
[user@host ~]$ kill -SIGINT $PID_OF_SH
Respuestas:
Cómo CTRL+ Cfunciona
Lo primero es entender cómo funciona CTRL+ C.
Cuando presiona CTRL+ C, su emulador de terminal envía un carácter ETX (fin de texto / 0x03).
El TTY está configurado de tal manera que cuando recibe este carácter, envía un SIGINT al grupo de proceso en primer plano del terminal. Esta configuración se puede ver haciendo
stty
y mirandointr = ^C;
. La especificación POSIX dice que cuando se recibe INTR, debe enviar un SIGINT al grupo de procesos en primer plano de ese terminal.¿Qué es el grupo de procesos en primer plano?
Entonces, ahora la pregunta es, ¿cómo se determina cuál es el grupo de procesos en primer plano? El grupo de procesos en primer plano es simplemente el grupo de procesos que recibirá cualquier señal generada por el teclado (SIGTSTOP, SIGINT, etc.).
La forma más sencilla de determinar la ID del grupo de procesos es usar
ps
:La segunda columna será la ID del grupo de procesos.
¿Cómo envío una señal al grupo de proceso?
Ahora que sabemos cuál es la ID del grupo de procesos, necesitamos simular el comportamiento POSIX de enviar una señal a todo el grupo.
Esto se puede hacer
kill
poniendo un-
delante de la ID del grupo.Por ejemplo, si su ID de grupo de proceso es 1234, usaría:
Simule CTRL+ Cusando el número de terminal.
Entonces, lo anterior cubre cómo simular CTRL+ Ccomo un proceso manual. Pero, ¿qué sucede si conoce el número TTY y desea simular CTRL+ Cpara ese terminal?
Esto se vuelve muy fácil.
Supongamos que
$tty
es el terminal al que desea apuntar (puede obtener esto ejecutándosetty | sed 's#^/dev/##'
en el terminal).Esto enviará un SIGINT a cualquiera que sea el grupo de procesos en primer plano
$tty
.fuente
kill
comando puede fallar.sends a SIGINT to the foreground process group of the terminal.
fork
. Ejemplo mínimo de C ejecutable en: unix.stackexchange.com/a/465112/32558Como dice vinc17, no hay razón para que esto suceda. Cuando escribe una secuencia de teclas generadoras de señal (por ejemplo, Ctrl+ C), la señal se envía a todos los procesos que están conectados (asociados con) al terminal. No existe tal mecanismo para las señales generadas por
kill
.Sin embargo, un comando como
enviará la señal a todos los procesos en el grupo de procesos 12345; ver kill (1) y kill (2) . Los hijos de un shell suelen estar en el grupo de procesos del shell (al menos, si no son asíncronos), por lo que enviar la señal al negativo del PID del shell puede hacer lo que desee.
Ups
Como señala vinc17, esto no funciona para shells interactivos. Aquí hay una alternativa que podría funcionar:
ps -pPID_of_shell
obtiene información del proceso en el shell.o tpgid=
le indicaps
a la salida solo la ID del grupo de proceso del terminal, sin encabezado. Si es inferior a 10000,ps
lo mostrará con espacios iniciales; Este$(echo …)
es un truco rápido para quitar los espacios iniciales (y finales).Obtuve esto para trabajar en pruebas superficiales en una máquina Debian.
fuente
La pregunta contiene su propia respuesta. Enviar el
SIGINT
alcat
proceso conkill
es una simulación perfecta de lo que sucede cuando presiona^C
.Para ser más precisos, el carácter de interrupción (
^C
por defecto) se envíaSIGINT
a cada proceso en el grupo de procesos en primer plano del terminal. Si en lugar decat
estar ejecutando un comando más complicado que involucra múltiples procesos, tendría que matar al grupo de procesos para lograr el mismo efecto que^C
.Cuando ejecuta cualquier comando externo sin el
&
operador de fondo, el shell crea un nuevo grupo de proceso para el comando y notifica al terminal que este grupo de proceso está ahora en primer plano. El shell todavía está en su propio grupo de procesos, que ya no está en primer plano. Luego el shell espera a que salga el comando.Ahí es donde parece haberse convertido en la víctima por un error común: la idea de que el shell está haciendo algo para facilitar la interacción entre sus procesos secundarios y el terminal. Eso no es verdad. Una vez que ha realizado el trabajo de configuración (creación del proceso, configuración del modo de terminal, creación de canalizaciones y redirección de otros descriptores de archivo y ejecución del programa de destino), el shell simplemente espera . Lo que escribe
cat
no pasa por el shell, ya sea entrada normal o un carácter especial generador de señal como^C
. Elcat
proceso tiene acceso directo al terminal a través de sus propios descriptores de archivo, y el terminal tiene la capacidad de enviar señales directamente alcat
proceso porque es el grupo de procesos en primer plano.Después de que el
cat
proceso muere, se notificará al shell, ya que es el padre delcat
proceso. Luego, el caparazón se activa y vuelve a ponerse en primer plano.Aquí hay un ejercicio para aumentar su comprensión.
En el indicador de shell en una nueva terminal, ejecute este comando:
La
exec
palabra clave hace que el shell se ejecutecat
sin crear un proceso secundario. La cáscara se reemplaza porcat
. El PID que anteriormente pertenecía al shell ahora es el PID decat
. Verifique esto conps
en una terminal diferente. Escriba algunas líneas aleatorias y vea que se lascat
repite, demostrando que todavía se comporta normalmente a pesar de no tener un proceso de shell como padre. ¿Qué pasará cuando presiones^C
ahora?Responder:
fuente
exec cat
presionar^C
no aterrizaría^C
en gato. ¿Por qué terminaría elcat
que ahora ha reemplazado el shell? Dado que el shell ha sido reemplazado, el shell es lo que implementa la lógica de enviar SIGINT a sus hijos al recibirlo^C
.No hay razón para propagarlo
SIGINT
al niño. Además, lasystem()
especificación POSIX dice: "La función system () ignorará las señales SIGINT y SIGQUIT, y bloqueará la señal SIGCHLD, mientras espera que termine el comando".Si el shell propaga lo recibido
SIGINT
, por ejemplo, siguiendo un Ctrl-C real, esto significaría que el proceso secundario recibiría laSIGINT
señal dos veces, lo que puede tener un comportamiento no deseado.fuente
system()
. Pero tiene razón, si capta la señal (obviamente lo hace), entonces no hay razón para propagarla hacia abajo.setpgid
Ejemplo mínimo del grupo de procesos POSIX CPuede ser más fácil de entender con un ejemplo ejecutable mínimo de la API subyacente.
Esto ilustra cómo se envía la señal al niño, si el niño no cambió su grupo de proceso
setpgid
.C Principal
GitHub aguas arriba .
Compilar con:
Correr sin
setpgid
Sin ningún argumento CLI,
setpgid
no se hace:Posible resultado:
y el programa se cuelga.
Como podemos ver, el pgid de ambos procesos es el mismo, ya que se hereda
fork
.Luego, cada vez que golpeas:
Sale de nuevo:
Esto muestra cómo:
kill(-pgid, SIGINT)
Salga del programa enviando una señal diferente a ambos procesos, por ejemplo, SIGQUIT con
Ctrl + \
.Corre con
setpgid
Si corre con un argumento, por ejemplo:
luego el niño cambia su pgid, y ahora solo se imprime una sola señal cada vez solo del padre:
Y ahora, cada vez que golpeas:
solo el padre recibe la señal también:
Todavía puedes matar al padre como antes con un SIGQUIT:
¡Sin embargo, el niño ahora tiene un PGID diferente y no recibe esa señal! Esto se puede ver en:
Tendrás que matarlo explícitamente con:
Esto deja en claro por qué existen grupos de señales: de lo contrario, nos quedarían un montón de procesos que se limpiarían manualmente todo el tiempo.
Probado en Ubuntu 18.04.
fuente