¿Cómo funcionan las señales internamente?

31

En general, para matar procesos generamos señales como SIGKILL, SIGTSTPetc.

Pero, ¿cómo se sabe quién ordenó esa señal en particular, quién la envió a un proceso en particular y, en general, cómo realizan sus operaciones las señales? ¿Cómo funcionan las señales internamente?

Varun Chhangani
fuente
La pregunta es un poco difícil de entender. Me disculpo y no quiero faltarle al respeto. ¿Desea saber quién pudo haber ejecutado un comando que eliminó un proceso o desea saber más sobre SIGKILL y SIGSTP?
pullsumo
@mistermister Quiero saber quién pudo haber ejecutado un comando que mató un proceso y cómo.
Varun Chhangani

Respuestas:

35

La vista de 50,000 pies es que:

  1. El núcleo genera una señal internamente (por ejemplo, SIGSEGVcuando se accede a una dirección no válida, o SIGQUITcuando presiona Ctrl+ \), o mediante un programa que utiliza la killllamada al sistema (o varias relacionadas).

  2. Si es por una de las llamadas al sistema, entonces el núcleo confirma que el proceso de llamada tiene suficientes privilegios para enviar la señal. Si no, se devuelve un error (y la señal no sucede).

  3. Si es una de dos señales especiales, el núcleo actúa incondicionalmente sobre ella, sin ninguna entrada del proceso de destino. Las dos señales especiales son SIGKILL y SIGSTOP. Todo lo siguiente sobre acciones predeterminadas, señales de bloqueo, etc., es irrelevante para estos dos.

  4. A continuación, el núcleo descubre qué hacer con la señal:

    1. Para cada proceso, hay una acción asociada con cada señal. Hay un montón de valores predeterminados, y los programas pueden establecer diferentes usando sigaction, signaletc. Estos incluyen cosas como "ignorarlo completamente", "matar el proceso", "matar el proceso con un volcado de núcleo", "detener el proceso", etc.

    2. Los programas también pueden desactivar la entrega de señales ("bloqueadas"), señal por señal. Entonces la señal permanece pendiente hasta que se desbloquee.

    3. Los programas pueden solicitar que, en lugar de que el kernel tome alguna acción, entregue la señal al proceso de forma síncrona (con sigwait, et. Al. O signalfd) o asíncronamente (interrumpiendo lo que sea que esté haciendo el proceso y llamando a una función específica).

Hay un segundo conjunto de señales llamadas "señales en tiempo real", que no tienen un significado específico, y también permiten que se pongan en cola múltiples señales (las señales normales solo ponen en cola una de cada una cuando la señal está bloqueada). Estos se utilizan en programas de subprocesos múltiples para que los hilos se comuniquen entre sí. Varios se utilizan en la implementación de hilos POSIX de glibc, por ejemplo. También se pueden usar para comunicarse entre diferentes procesos (por ejemplo, podría usar varias señales en tiempo real para que un programa fooctl envíe un mensaje al demonio fooctl).

Para una vista que no sea de 50,000 pies, pruebe man 7 signaly también la documentación interna del kernel (o fuente).

derobert
fuente
"Las dos señales especiales son SIGKILL y SIGSTOP", entonces, ¿qué puede ser SIGCONT ...
Hauke ​​Laging
@HaukeLaging SIGCONT es la señal que deshace SIGSTOP. La documentación no lo enumera como especial ... Por lo tanto, no estoy seguro de si técnicamente un proceso puede hacer que ignore, entonces no podrá reanudarlo (solo SIGKILL).
derobert
22

La implementación de la señal es muy compleja y es específica del núcleo. En otras palabras, diferentes núcleos implementarán señales de manera diferente. Una explicación simplificada es la siguiente:

La CPU, basada en un valor de registro especial, tiene una dirección en la memoria donde espera encontrar una "tabla de descriptores de interrupción" que en realidad es una tabla de vectores. Hay un vector para cada posible excepción, como la división por cero, o trap, como INT 3 (depuración). Cuando la CPU encuentra la excepción, guarda los indicadores y el puntero de instrucción actual en la pila y luego salta a la dirección especificada por el vector relevante. En Linux, este vector siempre apunta al núcleo, donde hay un controlador de excepciones. La CPU ya está lista y el kernel de Linux se hace cargo.

Tenga en cuenta que también puede activar una excepción del software. Por ejemplo, el usuario presiona CTRL- C, luego esta llamada va al kernel que llama a su propio controlador de excepciones. En general, hay diferentes formas de llegar al controlador, pero independientemente de lo mismo sucede: el contexto se guarda en la pila y se accede al controlador de excepciones del núcleo.

El controlador de excepciones luego decide qué hilo debe recibir la señal. Si ocurre algo como la división por cero, entonces es fácil: el hilo que causó la excepción recibe la señal, pero para otros tipos de señales, la decisión puede ser muy compleja y, en algunos casos inusuales, un hilo más o menos aleatorio podría Recibe la señal.

Para enviar la señal, lo que hace el núcleo es establecer primero un valor que indique el tipo de señal, SIGHUPo lo que sea. Esto es solo un número entero. Cada proceso tiene un área de memoria de "señal pendiente" donde se almacena este valor. Luego, el núcleo crea una estructura de datos con la información de la señal. Esta estructura incluye una "disposición" de señal que puede ser predeterminada, ignorada o manejada. El núcleo luego llama a su propia función do_signal(). La siguiente fase comienza.

do_signal()primero decide si se va a manejar la señal. Por ejemplo, si es un asesinato , do_signal()simplemente mata el proceso, final de la historia. De lo contrario, mira la disposición. Si la disposición es predeterminada, do_signal()maneja la señal de acuerdo con una política predeterminada que depende de la señal. Si la disposición es manejar, significa que hay una función en el programa de usuario que está diseñada para manejar la señal en cuestión y el puntero a esta función estará en la estructura de datos mencionada anteriormente. En este caso, do_signal () llama a otra función del núcleo,handle_signal(), que luego pasa por el proceso de volver al modo de usuario y llamar a esta función. Los detalles de este traspaso son extremadamente complejos. Este código en su programa generalmente se vincula automáticamente a su programa cuando usa las funciones en signal.h.

Al examinar adecuadamente el valor de la señal pendiente, el núcleo puede determinar si el proceso está manejando todas las señales, y tomará las medidas apropiadas si no lo está, lo que podría poner el proceso en suspensión o matarlo u otra acción, dependiendo de la señal.

Tyler Durden
fuente
15

Aunque esta pregunta ha sido respondida, permítanme publicar un flujo detallado de eventos en el kernel de Linux.
Esto se copia completamente de las publicaciones de Linux: Señales de Linux: partes internas en el blog "Publicaciones de Linux" en sklinuxblog.blogspot.in.

Programa Signal User Space C

Comencemos escribiendo un programa simple de espacio de usuario de señal C:

#include<signal.h>
#include<stdio.h>

/* Handler function */
void handler(int sig) {
    printf("Receive signal: %u\n", sig);
};

int main(void) {
    struct sigaction sig_a;

    /* Initialize the signal handler structure */
    sig_a.sa_handler = handler;
    sigemptyset(&sig_a.sa_mask);
    sig_a.sa_flags = 0;

    /* Assign a new handler function to the SIGINT signal */
    sigaction(SIGINT, &sig_a, NULL);

    /* Block and wait until a signal arrives */
    while (1) {
            sigsuspend(&sig_a.sa_mask);
            printf("loop\n");
    }
    return 0;
};

Este código asigna un nuevo controlador para la señal SIGINT. SIGINT puede enviarse al proceso en ejecución utilizando la combinación de teclas Ctrl+ C. Cuando se presiona Ctrl+ C, la señal asíncrona SIGINT se envía a la tarea. También es equivalente a enviar el kill -INT <pid>comando en otra terminal.

Si haces un kill -l(que es una minúscula L, que significa "lista"), conocerás las diversas señales que se pueden enviar a un proceso en ejecución.

[root@linux ~]# kill -l
 1) SIGHUP        2) SIGINT        3) SIGQUIT       4) SIGILL        5) SIGTRAP
 6) SIGABRT       7) SIGBUS        8) SIGFPE        9) SIGKILL      10) SIGUSR1
11) SIGSEGV      12) SIGUSR2      13) SIGPIPE      14) SIGALRM      15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD      18) SIGCONT      19) SIGSTOP      20) SIGTSTP
21) SIGTTIN      22) SIGTTOU      23) SIGURG       24) SIGXCPU      25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF      28) SIGWINCH     29) SIGIO        30) SIGPWR
31) SIGSYS       34) SIGRTMIN     35) SIGRTMIN+1   36) SIGRTMIN+2   37) SIGRTMIN+3
38) SIGRTMIN+4   39) SIGRTMIN+5   40) SIGRTMIN+6   41) SIGRTMIN+7   42) SIGRTMIN+8
43) SIGRTMIN+9   44) SIGRTMIN+10  45) SIGRTMIN+11  46) SIGRTMIN+12  47) SIGRTMIN+13
48) SIGRTMIN+14  49) SIGRTMIN+15  50) SIGRTMAX-14  51) SIGRTMAX-13  52) SIGRTMAX-12
53) SIGRTMAX-11  54) SIGRTMAX-10  55) SIGRTMAX-9   56) SIGRTMAX-8   57) SIGRTMAX-7
58) SIGRTMAX-6   59) SIGRTMAX-5   60) SIGRTMAX-4   61) SIGRTMAX-3   62) SIGRTMAX-2
63) SIGRTMAX-1   64) SIGRTMAX

También se puede usar la siguiente combinación de teclas para enviar señales particulares:

  • Ctrl+ C- envía SIGINT qué acción predeterminada es finalizar la aplicación.
  • Ctrl+ \  - envía SIGQUIT cuya acción predeterminada es terminar el núcleo de volcado de la aplicación.
  • Ctrl+ Z- envía SIGSTOP que suspende el programa.

Si compila y ejecuta el programa C anterior, obtendrá el siguiente resultado:

[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop

Incluso con Ctrl+ Co kill -2 <pid>el proceso no terminará. En su lugar, ejecutará el controlador de señal y volverá.

Cómo se envía la señal al proceso

Si vemos el funcionamiento interno de la señal que se envía a un proceso y ponemos Jprobe con dump_stack en __send_signalfunción, veremos el siguiente seguimiento de llamada:

May  5 16:18:37 linux kernel: dump_stack+0x19/0x1b
May  5 16:18:37 linux kernel: my_handler+0x29/0x30 (probe)
May  5 16:18:37 linux kernel: complete_signal+0x205/0x250
May  5 16:18:37 linux kernel: __send_signal+0x194/0x4b0
May  5 16:18:37 linux kernel: send_signal+0x3e/0x80
May  5 16:18:37 linux kernel: do_send_sig_info+0x52/0xa0
May  5 16:18:37 linux kernel: group_send_sig_info+0x46/0x50
May  5 16:18:37 linux kernel: __kill_pgrp_info+0x4d/0x80
May  5 16:18:37 linux kernel: kill_pgrp+0x35/0x50
May  5 16:18:37 linux kernel: n_tty_receive_char+0x42b/0xe30
May  5 16:18:37 linux kernel:  ? ftrace_ops_list_func+0x106/0x120
May  5 16:18:37 linux kernel: n_tty_receive_buf+0x1ac/0x470
May  5 16:18:37 linux kernel: flush_to_ldisc+0x109/0x160
May  5 16:18:37 linux kernel: process_one_work+0x17b/0x460
May  5 16:18:37 linux kernel: worker_thread+0x11b/0x400
May  5 16:18:37 linux kernel: rescuer_thread+0x400/0x400
May  5 16:18:37 linux kernel:  kthread+0xcf/0xe0
May  5 16:18:37 linux kernel:  kthread_create_on_node+0x140/0x140
May  5 16:18:37 linux kernel:  ret_from_fork+0x7c/0xb0
May  5 16:18:37 linux kernel: ? kthread_create_on_node+0x140/0x140

Entonces, la función principal para enviar la señal es la siguiente:

First shell send the Ctrl+C signal using n_tty_receive_char
n_tty_receive_char()
isig()
kill_pgrp()
__kill_pgrp_info()
group_send_sig_info() -- for each PID in group call this function
do_send_sig_info()
send_signal()
__send_signal() -- allocates a signal structure and add to task pending signals
complete_signal()
signal_wake_up()
signal_wake_up_state()  -- sets TIF_SIGPENDING in the task_struct flags. Then it wake up the thread to which signal was delivered.

Ahora todo está configurado y se realizan los cambios necesarios en task_structel proceso.

Manejo de señal

La señal es verificada / manejada por un proceso cuando regresa de la llamada al sistema o si se realiza la devolución de la interrupción. La devolución de la llamada del sistema está presente en el archivo entry_64.S.

Se llama a la función int_signal function desde la entry_64.Scual se llama a la función do_notify_resume().

Verifiquemos la función do_notify_resume(). Esta función verifica si tenemos la TIF_SIGPENDINGbandera establecida en task_struct:

 /* deal with pending signal delivery */
 if (thread_info_flags & _TIF_SIGPENDING)
  do_signal(regs);
do_signal calls handle_signal to call the signal specific handler
Signals are actually run in user mode in function:
__setup_rt_frame -- this sets up the instruction pointer to handler: regs->ip = (unsigned long) ksig->ka.sa.sa_handler;

SISTEMA llamadas y señales

Llamadas de sistema "lentas", por ejemplo, bloqueo de lectura / escritura, poner procesos en estado de espera: TASK_INTERRUPTIBLEo TASK_UNINTERRUPTIBLE.

Una tarea en estado TASK_INTERRUPTIBLEcambiará al TASK_RUNNINGestado mediante una señal. TASK_RUNNINGsignifica que se puede programar un proceso.

Si se ejecuta, su controlador de señal se ejecutará antes de completar la llamada al sistema "lenta". El syscallno se completa por defecto.

Si se SA_RESTARTestablece el indicador, syscallse reinicia después de que finaliza el controlador de señal.

Referencias

K_K
fuente
Gracias por hacer un esfuerzo para contribuir al sitio, pero (1) si va a copiar material de otro sitio (palabra por palabra, letra por letra, incluidos los errores gramaticales y de puntuación), debe decir que está haciendo Entonces, mucho más claramente. Listar la fuente como una "Referencia", aunque es necesario, no es suficiente. A menos que sea el autor del blog (K_K = sk?), En cuyo caso no está obligado a vincularlo, pero si lo hace, debe revelar (es decir, decir) que es suyo. … (Continúa)
G-Man dice 'Restablecer a Monica' el
(Cont.) ... (2) Su fuente (el blog que copió) no es muy buena. Han pasado cuatro años desde que se hizo la pregunta; ¿no podrías encontrar una mejor referencia para copiar? (Si usted es el autor original, lo siento). Además de los errores de gramática y puntuación antes mencionados (y en general una redacción descuidada y un formato deficiente), está mal. (2a) Ctrl + Z envía SIGTSTP, no SIGSTOP. (SIGTSTP, como SIGTERM, puede ser atrapado; SIGSTOP, como SIGKILL, no puede.) ... (Cont.)
G-Man dice 'Reinstate Monica' el
(Cont.) ... (2b) El shell no envía la señal Ctrl + C. El shell no tiene ningún papel en el envío de señales (excepto cuando el usuario usa el killcomando, que es un shell incorporado). (2c) Si bien los puntos y comas después del cierre }de una función no son, estrictamente hablando, errores, son innecesarios y muy poco ortodoxos. (3) Incluso si todo fuera correcto, no sería una muy buena respuesta a la pregunta. (3a) La pregunta, aunque algo poco clara, parece centrarse en cómo los actores (usuarios y procesos) inician (es decir, envían ) señales. ... (Continúa)
G-Man dice 'Restablecer a Monica' el
(Cont.) ... La respuesta parece centrarse en las señales generadas por el núcleo (específicamente, las señales generadas por el teclado) y cómo el proceso del receptor reacciona a las señales. (3b) La pregunta parece estar en el nivel de "Alguien mató mi proceso, ¿quién lo hizo y cómo?" estructuras de datos del núcleo. OMI, ese es un nivel inapropiadamente bajo, especialmente porque no proporciona ninguna referencia en la que un lector pueda aprender más sobre este funcionamiento interno.
G-Man dice 'reinstalar a Monica' el
1
Es mi propio blog ... mis propias huellas ... eso es lo que quiero ... todo el mundo conocerá ese flujo detallado ... hablar en el aire no tiene sentido ... incluso si después de violar las pautas de esta comunidad, elimine mi respuesta a través de canal ... esta es la respuesta interna del núcleo, no la gramática interna.
K_K