Gestire il segnale SIGINT in C
-
Utilizzare la funzione
signal
per registrare la routine di gestione del segnaleSIGINT
-
Utilizzare la funzione
sigaction
per registrare la routine di gestione del segnaleSIGINT
Questo articolo mostrerà diversi metodi su come gestire il segnale SIGINT
in C.
Utilizzare la funzione signal
per registrare la routine di gestione del segnale SIGINT
SIGINT
è uno dei segnali predefiniti associati al carattere di interruzione del terminale (comunemente Ctrl+C). Fa sì che la shell interrompa il processo corrente e ritorni al suo bucle principale, visualizzando un nuovo prompt dei comandi per l’utente. Nota che i segnali sono solo piccole notifiche inviate tra i processi all’interno del kernel e lo spazio utente. A volte sono chiamati interrupt software poiché di solito interrompono la normale esecuzione del programma ed eseguono l’azione speciale per il tipo di segnale dato. Le azioni sono per lo più definite come predefinite nei sistemi, ma l’utente può implementare una funzione speciale e registrarla come nuova azione per il segnale.
Tuttavia, alcuni segnali hanno un comportamento strettamente fisso assegnato dal sistema operativo e non possono essere sovrascritti, poiché il kernel li usa per fare cose critiche come terminare i processi che non rispondono.
SIGINT
, tuttavia, è il tipo di segnale che può essere gestito, il che significa che l’utente può registrare una funzione personalizzata da eseguire quando il processo riceve il segnale. L’azione predefinita del segnale SIGINT
è che il processo termini. Nel seguente codice di esempio, implementiamo un programma che esegue un bucle infinito while
, chiamando la funzione fprintf
continuamente al suo interno. Sebbene, appena prima che il bucle venga avviato, chiamiamo la funzione signal
per registrare la funzione SIGINT
sigintHandler
come gestore, che ha solo una chiamata di funzione, stampando una stringa nello stdout
.
Si noti che write
è usato al posto di printf
perché il codice del gestore del segnale non deve chiamare funzioni non rientranti che modificano i dati del programma globale sotto il cofano. Per dimostrare l’esempio, è necessario eseguire il programma e quindi inviare il segnale SIGINT
dall’altro terminale per osservare il comportamento. Di solito, dovrebbe interrompere l’esecuzione del cicli while
, stampare la stringa "Caught SIGINT!"
Ed uscire con il codice di stato di successo.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
static void sigintHandler(int sig) {
write(STDERR_FILENO, "Caught SIGINT!\n", 15);
}
int main(int argc, char *argv[]) {
if (signal(SIGINT, sigintHandler) == SIG_ERR) errExit("signal SIGINT");
while (1) {
fprintf(stderr, "%d", 0);
sleep(3);
}
exit(EXIT_SUCCESS);
}
Utilizzare la funzione sigaction
per registrare la routine di gestione del segnale SIGINT
Anche se le moderne implementazioni della chiamata di funzione signal
nei sistemi UNIX funzionano in modo affidabile per casi d’uso semplici, si consiglia di utilizzare la funzione sigaction
per registrare i gestori di segnali. Offre molte più opzioni rispetto alla chiamata signal
, ma fornisce anche funzionalità di base che sono necessarie per qualsiasi caso di utilizzo serio dei segnali. sigaction
accetta argomenti speciali struct sigaction
per specificare il puntatore alla funzione del gestore e altri indicatori con esso. In questo caso, implementiamo uno scenario in cui un processo figlio esegue un cicli while
con una variabile globale shutdown_flag
come espressione di condizione mentre il genitore lo attende. Notare che la variabile shutdown_flag
è di tipo sig_atomic_t
, un numero intero speciale che può essere modificato in modo sicuro a livello globale dal codice del gestore del segnale. Quindi, una volta che l’utente invia il segnale SIGINT
al processo figlio, viene invocata la funzione cleanupRoutine
che imposta il valore shutdown_flag
a 0
e il controllo ritorna al cicli while
dove l’espressione della condizione viene valutata di nuovo e lo zero lo costringe a rompere dal bucle. Il bambino esce e il genitore ottiene il suo stato quando ritorna la funzione waitpid
.
#define _POSIX_C_SOURCE 199309
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
volatile sig_atomic_t shutdown_flag = 1;
void cleanupRoutine(int signal_number) { shutdown_flag = 0; }
int main(void) {
int wstatus;
pid_t c_pid = fork();
if (c_pid == -1) errExit("fork");
if (c_pid == 0) {
printf("printed from child process - %d\n", getpid());
int count = 0;
struct sigaction sigterm_action;
memset(&sigterm_action, 0, sizeof(sigterm_action));
sigterm_action.sa_handler = &cleanupRoutine;
sigterm_action.sa_flags = 0;
// Mask other signals from interrupting SIGINT handler
if (sigfillset(&sigterm_action.sa_mask) != 0) errExit("sigfillset");
// Register SIGINT handler
if (sigaction(SIGINT, &sigterm_action, NULL) != 0) errExit("sigaction");
while (shutdown_flag) {
getpid();
}
printf("pid: %d exited\n", getpid());
exit(EXIT_SUCCESS);
} else {
printf("printed from parent process - %d\n", getpid());
int ret;
if (waitpid(c_pid, &wstatus, WUNTRACED) == -1) errExit("waitpid");
}
exit(EXIT_SUCCESS);
}
Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.
LinkedIn Facebook