Come funzionano i segnali internamente?


31

In generale, per uccidere i processi generiamo segnali come SIGKILL, SIGTSTPecc.

Ma come si sa chi ha ordinato quel particolare segnale, chi lo ha inviato a un particolare processo, e in generale in che modo i segnali eseguono le loro operazioni? Come funzionano i segnali internamente?


La domanda è un po 'difficile da capire. Mi scuso e non intendo mancare di rispetto. Vuoi sapere chi potrebbe aver eseguito un comando che ha interrotto un processo o vuoi saperne di più su SIGKILL e SIGSTP?
pullsumo,

@mistermister Voglio sapere chi potrebbe aver eseguito un comando che ha interrotto un processo e come?
Varun Chhangani,

Risposte:


35

La vista di 50.000 piedi è che:

  1. Un segnale viene generato internamente dal kernel (ad esempio, SIGSEGVquando si accede a un indirizzo non valido, o SIGQUITquando si preme Ctrl+ \), o da un programma che utilizza la killsyscall (o più di quelli correlati).

  2. Se si trova in uno dei syscall, il kernel conferma che il processo di chiamata ha privilegi sufficienti per inviare il segnale. In caso contrario, viene restituito un errore (e il segnale non si verifica).

  3. Se è uno dei due segnali speciali, il kernel agisce incondizionatamente su di esso, senza alcun input dal processo di destinazione. I due segnali speciali sono SIGKILL e SIGSTOP. Tutto ciò che segue sulle azioni predefinite, i segnali di blocco, ecc., Sono irrilevanti per questi due.

  4. Successivamente, il kernel capisce cosa c'entra con il segnale:

    1. Per ogni processo, esiste un'azione associata a ciascun segnale. Ci sono un sacco di valori predefiniti e i programmi possono impostarne di diversi usando sigaction, signalecc. Questi includono cose come "ignoralo completamente", "uccidi il processo", "uccidi il processo con un dump principale", "interrompi il processo", eccetera.

    2. I programmi possono anche disattivare la consegna dei segnali ("bloccati"), segnale per segnale. Quindi il segnale rimane in sospeso fino allo sblocco.

    3. I programmi possono richiedere che, invece che il kernel esegua un'azione in sé, fornisca il segnale al processo in modo sincrono (con sigwait, et al. O signalfd) o in modo asincrono (interrompendo qualsiasi cosa il processo stia facendo e chiamando una funzione specifica).

Esiste una seconda serie di segnali chiamati "segnali in tempo reale", che non hanno alcun significato specifico e consentono anche l'accodamento di più segnali (i segnali normali accodano solo uno di ciascuno quando il segnale è bloccato). Questi sono utilizzati in programmi multi-thread per consentire ai thread di comunicare tra loro. Diversi sono usati nell'implementazione dei thread POSIX di glibc, per esempio. Possono anche essere utilizzati per comunicare tra processi diversi (ad esempio, è possibile utilizzare diversi segnali in tempo reale per fare in modo che un programma fooctl invii un messaggio al demone foo).

Per una vista non di 50.000 piedi, prova man 7 signalanche la documentazione interna del kernel (o sorgente).


"I due segnali speciali sono SIGKILL e SIGSTOP" quindi cosa potrebbe essere SIGCONT ...
Hauke ​​Laging,

@HaukeLaging SIGCONT è il segnale che annulla SIGSTOP. La documentazione non lo elenca come speciale ... Quindi non sono sicuro che tecnicamente un processo possa impostarlo per ignorarlo, quindi non saresti in grado di riprenderlo (solo SIGKILL).
derobert,

22

L'implementazione del segnale è molto complessa ed è specifica del kernel. In altre parole, kernel diversi implementeranno i segnali in modo diverso. Una spiegazione semplificata è la seguente:

La CPU, basata su un valore di registro speciale, ha un indirizzo in memoria in cui si aspetta di trovare una "tabella descrittore di interrupt" che in realtà è una tabella vettoriale. Esiste un vettore per ogni possibile eccezione, come divisione per zero o trap, come INT 3 (debug). Quando la CPU rileva l'eccezione, salva i flag e il puntatore dell'istruzione corrente nello stack, quindi salta all'indirizzo specificato dal vettore in questione. In Linux questo vettore punta sempre nel kernel, dove esiste un gestore di eccezioni. La CPU è terminata e il kernel Linux prende il sopravvento.

Nota che puoi anche attivare un'eccezione dal software. Ad esempio, l'utente preme CTRL- C, quindi questa chiamata va al kernel che chiama il proprio gestore delle eccezioni. In generale, ci sono diversi modi per arrivare al gestore, ma a prescindere accade la stessa cosa di base: il contesto viene salvato nello stack e viene saltato il gestore delle eccezioni del kernel.

Il gestore delle eccezioni decide quindi quale thread deve ricevere il segnale. Se si verifica qualcosa come la divisione per zero, allora è facile: il thread che ha causato l'eccezione ottiene il segnale, ma per altri tipi di segnali, la decisione può essere molto complessa e in alcuni casi insoliti un thread più o meno casuale potrebbe ottieni il segnale.

Per inviare il segnale che cosa fa il kernel è prima di tutto impostare un valore che indichi il tipo di segnale, SIGHUPo altro. Questo è solo un numero intero. Ogni processo ha un'area di memoria "segnale in sospeso" in cui è memorizzato questo valore. Quindi il kernel crea una struttura di dati con le informazioni sul segnale. Questa struttura include un segnale "disposizione" che può essere predefinito, ignorato o gestito. Il kernel quindi chiama la propria funzione do_signal(). Inizia la fase successiva.

do_signal()prima decide se esso gestirà il segnale. Ad esempio, se si tratta di un'uccisione , quindi do_signal()uccide solo il processo, fine della storia. Altrimenti, guarda la disposizione. Se la disposizione è predefinita, quindi do_signal()gestisce il segnale secondo una politica predefinita che dipende dal segnale. Se la disposizione è handle, significa che esiste una funzione nel programma utente progettata per gestire il segnale in questione e il puntatore a questa funzione si troverà nella struttura di dati di cui sopra. In questo caso do_signal () chiama un'altra funzione del kernel,handle_signal(), che passa quindi attraverso il processo per tornare alla modalità utente e chiamare questa funzione. I dettagli di questo trasferimento sono estremamente complessi. Questo codice nel programma viene in genere collegato automaticamente al programma quando si utilizzano le funzioni in signal.h.

Esaminando il valore del segnale in sospeso in modo appropriato, il kernel può determinare se il processo sta gestendo tutti i segnali e, se non lo è, intraprenderà le azioni appropriate, il che potrebbe mettere in sospensione il processo o ucciderlo o altre azioni, a seconda del segnale.


15

Sebbene questa domanda abbia avuto una risposta, lasciatemi pubblicare un flusso dettagliato di eventi nel kernel Linux.
Questo è copiato interamente dai post di Linux: Segnali Linux - Internals nel blog "Post di Linux" su sklinuxblog.blogspot.in.

Programma Spazio utente segnale C

Cominciamo con la scrittura di un semplice programma C di spazio utente del segnale:

#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;
};

Questo codice assegna un nuovo gestore per il segnale SIGINT. SIGINT può essere inviato al processo in esecuzione utilizzando la combinazione di tasti Ctrl+ C. Quando Ctrl+ Cviene premuto, il segnale asincrono SIGINT viene inviato all'attività. È anche equivalente all'invio del kill -INT <pid>comando in un altro terminale.

Se fai un kill -l(che è una minuscola L, che sta per "elenco"), arriverai a conoscere i vari segnali che possono essere inviati a un processo in esecuzione.

[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

Inoltre, è possibile utilizzare la seguente combinazione di tasti per inviare segnali particolari:

  • Ctrl+ C- invia a SIGINT quale azione predefinita è terminare l'applicazione.
  • Ctrl+ \  - invia a SIGQUIT quale azione predefinita è terminare il core di dumping dell'applicazione.
  • Ctrl+ Z- invia SIGSTOP che sospende il programma.

Se compili ed esegui il programma C sopra, otterrai il seguente output:

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

Anche con Ctrl+ Co kill -2 <pid>il processo non verrà terminato. Invece eseguirà il gestore del segnale e ritornerà.

Come viene inviato il segnale al processo

Se vediamo l'internals del segnale che invia a un processo e mettiamo Jprobe con dump_stack alla __send_signalfunzione vedremo la seguente traccia di chiamata:

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

Quindi la funzione principale richiede l'invio del segnale è come:

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.

Ora tutto è impostato e vengono apportate le modifiche necessarie task_structal processo.

Gestione del segnale

Il segnale viene controllato / gestito da un processo quando ritorna dalla chiamata di sistema o se viene eseguito il ritorno dall'interruzione. Il ritorno dalla chiamata di sistema è presente nel file entry_64.S.

Viene chiamata la funzione int_signal da entry_64.Scui chiama la funzione do_notify_resume().

Controlliamo la funzione do_notify_resume(). Questa funzione controlla se il TIF_SIGPENDINGflag è impostato in 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;

Chiamate e segnali di SISTEMA

Syscall "lenti", ad es. Blocco della lettura / scrittura, messa in attesa dei processi: TASK_INTERRUPTIBLEo TASK_UNINTERRUPTIBLE.

Un'attività nello stato TASK_INTERRUPTIBLEverrà cambiata nello TASK_RUNNINGstato da un segnale. TASK_RUNNINGsignifica che è possibile pianificare un processo.

Se eseguito, il suo gestore di segnale verrà eseguito prima del completamento della scalinata "lenta". Il syscallnon si completa per impostazione predefinita.

Se SA_RESTARTflag impostato, syscallviene riavviato al termine del gestore del segnale.

Riferimenti


Grazie per fare uno sforzo per contribuire al sito, ma (1) se hai intenzione di copiare materiale da un altro sito (parola per parola, lettera per lettera, inclusi gli errori grammaticali e di punteggiatura), dovresti dire che stai facendo quindi, molto più chiaramente. Elencare la fonte come "Riferimento", sebbene necessario, non è sufficiente. A meno che tu non sia l'autore del blog (K_K = sk?), Nel qual caso non ti viene richiesto di collegarti ad esso - ma, se lo fai, devi rivelare (cioè dire) che è tuo. ... (proseguendo)
G-Man dice "Ripristina Monica" il

(Proseguendo) ... (2) La tua fonte (il blog da cui hai copiato) non è molto buona. Sono passati quattro anni da quando è stata posta la domanda; non hai trovato un riferimento migliore da cui copiare? (Se sei l'autore originale, mi dispiace.) Oltre ai summenzionati errori grammaticali e di punteggiatura (e in genere una formulazione sciatta e una formattazione scadente), è sbagliato. (2a) Ctrl + Z invia SIGTSTP, non SIGSTOP. (SIGTSTP, come SIGTERM, può essere catturato; SIGSTOP, come SIGKILL, non può.) ... (proseguendo)
G-Man dice 'Reinstate Monica' il

(Continua) ... (2b) La shell non invia il segnale Ctrl + C. La shell non ha alcun ruolo nell'invio dei segnali (tranne quando l'utente utilizza il killcomando, che è incorporato nella shell). (2c) Sebbene i punti e virgola dopo la chiusura }di una funzione non siano, a rigor di termini, errori, sono inutili e altamente non ortodossi. (3) Anche se tutto fosse corretto, non sarebbe un'ottima risposta alla domanda. (3 bis) La domanda, mentre un po 'poco chiara, sembra essere concentrandosi su come gli attori (utenti e di processo) avviare (ad esempio, inviare ) segnali. ... (proseguendo)
G-Man dice "Ripristina Monica" il

(Proseguendo) ... La risposta sembra concentrarsi sui segnali generati dal kernel (in particolare, i segnali generati dalla tastiera) e su come il processo del destinatario reagisce ai segnali. (3b) La domanda sembra essere al livello di «Qualcuno ha ucciso il mio processo - chi l'ha fatto e come?» La risposta discute l'API di gestione del segnale, le routine del kernel, il debug del kernel (Jprobe?), Le tracce dello stack del kernel e strutture di dati del kernel. IMO, che è di livello inopportuno basso, soprattutto perché non fornisce alcun riferimento in cui un lettore potrebbe saperne di più su questi meccanismi interni.
G-Man dice "Ripristina Monica" il

1
È il mio blog .. le mie tracce .. questo è quello che voglio .. tutti conosceranno un flusso così dettagliato .. parlare in aria non ha senso .. anche se dopo aver violato le linee guida di queste comunità per favore rimuovi la mia risposta attraverso canale .. questa è una risposta interna del kernel, non interna alla grammatica.
K_K,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.