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 shy luego cat:
[user@host ~]$ sh
sh-4.3$ cat
test
test
Enviar SIGINTa catotra 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 catcuando 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
sttyy 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
killponiendo 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
$ttyes 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
killcomando 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_shellobtiene información del proceso en el shell.o tpgid=le indicapsa la salida solo la ID del grupo de proceso del terminal, sin encabezado. Si es inferior a 10000,pslo 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
SIGINTalcatproceso conkilles una simulación perfecta de lo que sucede cuando presiona^C.Para ser más precisos, el carácter de interrupción (
^Cpor defecto) se envíaSIGINTa cada proceso en el grupo de procesos en primer plano del terminal. Si en lugar decatestar 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
catno pasa por el shell, ya sea entrada normal o un carácter especial generador de señal como^C. Elcatproceso 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 alcatproceso porque es el grupo de procesos en primer plano.Después de que el
catproceso muere, se notificará al shell, ya que es el padre delcatproceso. 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
execpalabra clave hace que el shell se ejecutecatsin 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 conpsen una terminal diferente. Escriba algunas líneas aleatorias y vea que se lascatrepite, demostrando que todavía se comporta normalmente a pesar de no tener un proceso de shell como padre. ¿Qué pasará cuando presiones^Cahora?Responder:
fuente
exec catpresionar^Cno aterrizaría^Cen gato. ¿Por qué terminaría elcatque 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
SIGINTal 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 laSIGINTseñ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.setpgidEjemplo 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
setpgidSin ningún argumento CLI,
setpgidno 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
setpgidSi 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