C Programmazione

Come usare i gestori di segnale in linguaggio C?

Come usare i gestori di segnale in linguaggio C?
In questo articolo ti mostreremo come usare i gestori di segnale in Linux usando il linguaggio C. Ma prima discuteremo cos'è il segnale, come genererà alcuni segnali comuni che puoi usare nel tuo programma e poi vedremo come vari segnali possono essere gestiti da un programma mentre il programma viene eseguito. Quindi iniziamo.

Segnale

Un segnale è un evento che viene generato per notificare a un processo o thread che è arrivata una situazione importante. Quando un processo o un thread ha ricevuto un segnale, il processo o il thread interromperà ciò che sta facendo e intraprenderà un'azione. Il segnale può essere utile per la comunicazione tra processi.

Segnali standard

I segnali sono definiti nel file di intestazione segnale.h come macro costante. Il nome del segnale è iniziato con un "SIG" e seguito da una breve descrizione del segnale. Quindi, ogni segnale ha un valore numerico univoco. Il tuo programma dovrebbe sempre usare il nome dei segnali, non il numero dei segnali. Il motivo è che il numero del segnale può variare in base al sistema, ma il significato dei nomi sarà standard.

La macro NSIG è il numero totale di segnale definito. Il valore di NSIG è uno maggiore del numero totale di segnali definito (tutti i numeri di segnale sono assegnati consecutivamente).

Di seguito i segnali standard:

Nome del segnale Descrizione
SIGHUP Riagganciare il processo. Il segnale SIGHUP viene utilizzato per segnalare la disconnessione del terminale dell'utente, probabilmente perché una connessione remota viene persa o riaggancia.
SIGINT Interrompi il processo. Quando l'utente digita il carattere INTR (normalmente Ctrl + C) viene inviato il segnale SIGINT.
SIGQUIT Esci dal processo. Quando l'utente digita il carattere QUIT (normalmente Ctrl + \) viene inviato il segnale SIGQUIT.
SIGILL Istruzione illegale. Quando si tenta di eseguire un'istruzione spazzatura o privilegiata, viene generato il segnale SIGILL. Inoltre, SIGILL può essere generato quando lo stack trabocca o quando il sistema ha problemi nell'esecuzione di un gestore di segnale.
SIGTRAP Traccia trappola. Un'istruzione breakpoint e un'altra istruzione trap genereranno il segnale SIGTRAP. Il debugger usa questo segnale.
SIGABRT Interrompi. Il segnale SIGABRT  viene generato quando viene chiamata la funzione abort(). Questo segnale indica un errore che viene rilevato dal programma stesso e segnalato dalla chiamata alla funzione abort().
SIGFPE Eccezione in virgola mobile. Quando si verifica un errore aritmetico fatale, viene generato il segnale SIGFPE.
SIGUSR1 e SIGUSR2 I segnali SIGUSR1 e SIGUSR2 possono essere utilizzati a piacere. È utile scrivere un gestore di segnale per loro nel programma che riceve il segnale per una semplice comunicazione tra processi.

Azione predefinita dei segnali

Ogni segnale ha un'azione predefinita, una delle seguenti:

Termine: Il processo terminerà.
Nucleo: Il processo terminerà e produrrà un file core dump.
Accendi: Il processo ignorerà il segnale.
Fermare: Il processo si fermerà.
Continua: Il processo continuerà dall'essere interrotto.

L'azione predefinita può essere modificata utilizzando la funzione del gestore. L'azione predefinita di alcuni segnali non può essere modificata. SIGKILL e SIGABRT l'azione predefinita del segnale non può essere modificata o ignorata.

Gestione del segnale

Se un processo riceve un segnale, il processo ha una scelta di azione per quel tipo di segnale. Il processo può ignorare il segnale, specificare una funzione del gestore o accettare l'azione predefinita per quel tipo di segnale.

Possiamo gestire il segnale usando segnale o sigazione funzione. Qui vediamo come il più semplice segnale() la funzione viene utilizzata per gestire i segnali.

int signal() (int signum, void (*func)(int))

Il segnale() chiamerà il funzione funzione se il processo riceve un segnale signum. Il segnale() restituisce un puntatore alla funzione funzione se ha successo o restituisce un errore a errno e -1 altrimenti.

Il funzione il puntatore può assumere tre valori:

  1. SIG_DFL: è un puntatore alla funzione predefinita del sistema SIG_DFL(), dichiarato in h file di intestazione. Viene utilizzato per eseguire l'azione predefinita del segnale.
  2. SIG_IGN: È un puntatore alla funzione di ignoranza del sistema SIG_IGN(),dichiarato in h file di intestazione.
  3. Puntatore alla funzione del gestore definito dall'utente: il tipo di funzione del gestore definito dall'utente è void(*)(int), significa che il tipo restituito è void e un argomento di tipo int.

Esempio di gestore di segnale di base

#includere
#includere
#includere
void sig_handler(int signum)
//Il tipo di ritorno della funzione del gestore dovrebbe essere void
printf("\nFunzione gestore interna\n");

int main()
signal(SIGINT,sig_handler); // Registra il gestore del segnale
for(int i=1;;i++)    //Ciclo infinito
printf("%d : All'interno della funzione principale\n",i);
dormire (1); // Ritardo di 1 secondo

restituisce 0;

Nello screenshot dell'output di Esempio1.c, possiamo vedere che nella funzione principale è in esecuzione un ciclo infinito. Quando l'utente digita Ctrl+C, l'esecuzione della funzione principale si interrompe e viene richiamata la funzione di gestione del segnale. Dopo il completamento della funzione del gestore, l'esecuzione della funzione principale è ripresa. Quando l'utente digita Ctrl+\, il processo viene chiuso.

Ignora segnali esempio

#includere
#includere
#includere
int main()
segnale(SIGINT,SIG_IGN); // Registra il gestore del segnale per ignorare il segnale
for(int i=1;;i++)    //Ciclo infinito
printf("%d : All'interno della funzione principale\n",i);
dormire (1); // Ritardo di 1 secondo

restituisce 0;

Qui la funzione del gestore è registrata su SIG_IGN() funzione per ignorare l'azione del segnale. Quindi, quando l'utente ha digitato Ctrl+C,  SIGINT il segnale sta generando ma l'azione viene ignorata.

Esempio di nuova registrazione del gestore del segnale

#includere
#includere
#includere
void sig_handler(int signum)
printf("\nFunzione gestore interna\n");
segnale(SIGINT,SIG_DFL); // Registra nuovamente il gestore del segnale per l'azione predefinita

int main()
signal(SIGINT,sig_handler); // Registra il gestore del segnale
for(int i=1;;i++)    //Ciclo infinito
printf("%d : All'interno della funzione principale\n",i);
dormire (1); // Ritardo di 1 secondo

restituisce 0;

Nello screenshot dell'output di Esempio3.c, possiamo vedere che quando l'utente ha digitato per la prima volta Ctrl+C, la funzione del gestore è stata invocata. Nella funzione gestore, il gestore del segnale si registra nuovamente su re SIG_DFL per l'azione predefinita del segnale. Quando l'utente ha digitato Ctrl+C per la seconda volta, il processo viene terminato, che è l'azione predefinita di SIGINT segnale.

Invio di segnali:

Un processo può anche inviare esplicitamente segnali a se stesso o ad un altro processo. Le funzioni raise() e kill() possono essere utilizzate per inviare segnali. Entrambe le funzioni sono dichiarate in signal.file di intestazione h.

int rilancio(int signum)

La funzione raise() usata per inviare il segnale signum al processo di chiamata (stesso). Restituisce zero se ha successo e un valore diverso da zero se fallisce.

int kill(pid_t pid, int signum)

La funzione kill utilizzata per inviare un segnale signum a un processo o gruppo di processi specificato da pid.

Esempio di gestore di segnale SIGUSR1

#includere
#includere
void sig_handler(int signum)
printf("Funzione del gestore interno\n");

int main()
signal(SIGUSR1,sig_handler); // Registra il gestore del segnale
printf("Dentro la funzione principale\n");
alzare(SIGUSR1);
printf("Dentro la funzione principale\n");
restituisce 0;

Qui, il processo invia il segnale SIGUSR1 a se stesso usando la funzione raise().

Rilancio con Kill Esempio di programma

#includere
#includere
#includere
void sig_handler(int signum)
printf("Funzione del gestore interno\n");

int main()
pid_t pid;
signal(SIGUSR1,sig_handler); // Registra il gestore del segnale
printf("Dentro la funzione principale\n");
pid=getpid(); //ID processo di se stesso
kill(pid,SIGUSR1); // Invia SIGUSR1 a se stesso
printf("Dentro la funzione principale\n");
restituisce 0;

Qui, il processo invia SIGUSR1 segnale a se stesso usando uccidere() funzione. getpid() viene utilizzato per ottenere l'ID del processo di se stesso.

Nel prossimo esempio vedremo come i processi padre e figlio comunicano (Inter Process Communication) usando uccidere() e funzione di segnale.

Comunicazione genitore-figlio con i segnali

#includere
#includere
#includere
#includere
void sig_handler_parent(int signum)
printf("Genitore: ricevuto un segnale di risposta dal figlio \n");

void sig_handler_child(int signum)
printf("Figlio: ricevuto un segnale dal genitore \n");
dormire (1);
kill(getppid(),SIGUSR1);

int main()
pid_t pid;
if((pid=forchetta())<0)
printf("Fork fallito\n");
uscita(1);

/* Processo figlio */
altrimenti if(pid==0)
signal(SIGUSR1,sig_handler_child); // Registra il gestore del segnale
printf("Bambino: in attesa del segnale\n");
pausa();

/* Processo padre */
altro
signal(SIGUSR1,sig_handler_parent); // Registra il gestore del segnale
dormire (1);
printf("Genitore: invio segnale al figlio\n");
kill(pid,SIGUSR1);
printf("Genitore: in attesa di risposta\n");
pausa();

restituisce 0;

Qui, forchetta() la funzione crea un processo figlio e restituisce zero al processo figlio e l'ID del processo figlio al processo padre. Quindi, pid è stato controllato per decidere il processo genitore e figlio. Nel processo genitore, viene sospeso per 1 secondo in modo che il processo figlio possa registrare la funzione del gestore del segnale e attendere il segnale dal genitore. Dopo 1 secondo processo genitore invia SIGUSR1 segnale al processo figlio e attendi il segnale di risposta dal bambino. Nel processo figlio, prima è in attesa del segnale dal genitore e quando il segnale viene ricevuto, viene richiamata la funzione del gestore. Dalla funzione handler, il processo figlio ne invia un altro SIGUSR1 segnale al genitore. Qui getppid() la funzione viene utilizzata per ottenere l'ID del processo padre.

Conclusione

Il segnale in Linux è un grande argomento. In questo articolo abbiamo visto come gestire il segnale dalle basi e anche conoscere come il segnale viene generato, come un processo può inviare un segnale a se stesso e ad altri processi, come il segnale può essere utilizzato per la comunicazione tra processi.

Le 5 migliori carte di acquisizione del gioco
Abbiamo tutti visto e amato i giochi in streaming su YouTube. PewDiePie, Jakesepticye e Markiplier sono solo alcuni dei migliori giocatori che hanno g...
Come sviluppare un gioco su Linux
Un decennio fa, non molti utenti Linux avrebbero previsto che il loro sistema operativo preferito un giorno sarebbe diventato una piattaforma di gioco...
Porte open source di motori di gioco commerciali
Ricreazioni del motore di gioco gratuite, open source e multipiattaforma possono essere utilizzate per riprodurre titoli di giochi vecchi e abbastanza...