Manejar la señal SIGINT en C
-
Utilice la función
signal
para registrar la rutina del manejador de señalesSIGINT
-
Utilice la función
sigaction
para registrar la rutina del manejador de señalesSIGINT
Este artículo demostrará varios métodos sobre cómo manejar la señal SIGINT
en C.
Utilice la función signal
para registrar la rutina del manejador de señales SIGINT
SIGINT
es una de las señales predefinidas que está asociada con el carácter de interrupción del terminal (comúnmente Ctrl+C). Hace que el shell detenga el proceso actual y regrese a su bucle principal, mostrando un nuevo símbolo del sistema al usuario. Tenga en cuenta que las señales son solo pequeñas notificaciones enviadas entre procesos dentro del kernel y el espacio de usuario. A veces se denominan interrupciones de software, ya que normalmente detienen la ejecución normal del programa y ejecutan la acción especial para el tipo de señal dado. Las acciones se definen principalmente como predeterminadas en todos los sistemas, pero el usuario puede implementar una función especial y registrarla como una nueva acción para la señal.
Sin embargo, tenga en cuenta que algunas señales tienen un comportamiento estrictamente fijo asignado desde el sistema operativo y no se pueden anular, ya que el kernel las usa para hacer cosas críticas como terminar procesos que no responden.
SIGINT
, sin embargo, es el tipo de señal que se puede manejar, lo que significa que el usuario puede registrar una función personalizada que se ejecutará cuando el proceso reciba la señal. La acción predeterminada de la señal SIGINT
es que el proceso termine. En el siguiente código de ejemplo, implementamos un programa que ejecuta un bucle infinito while
, llamando a la función fprintf
continuamente dentro de él. Aunque, justo antes de que se inicie el bucle, llamamos a la función signal
para registrar la función SIGINT
sigintHandler
como manejador, que tiene solo una llamada de función, imprimiendo una cadena en el stdout
.
Tenga en cuenta que se usa write
en lugar de printf
porque el código del manejador de señales no debe llamar a funciones no reentrantes que modifiquen los datos globales del programa debajo del capó. Para demostrar el ejemplo, debe ejecutar el programa y luego enviar la señal SIGINT
desde el otro terminal para observar el comportamiento. Por lo general, debería dejar de ejecutar el bucle while
, imprimir la cadena "Caught SIGINT!"
Y salir con el código de estado de éxito.
#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);
}
Utilice la función sigaction
para registrar la rutina del manejador de señales SIGINT
Aunque las implementaciones modernas de la llamada a la función señal
en sistemas UNIX funcionan de manera confiable para casos de uso simples, se recomienda utilizar la función sigaction
para registrar los manejadores de señales. Ofrece muchas más opciones en comparación con la llamada de señal
, pero también proporciona funciones básicas que son necesarias para cualquier caso de uso serio de las señales. sigaction
toma argumentos especiales de struct sigaction
para especificar el puntero de la función del controlador y otros indicadores con él. En este caso, implementamos un escenario donde un proceso hijo ejecuta un bucle while
con una variable global shutdown_flag
como expresión de condición mientras el padre lo espera. Tenga en cuenta que la variable shutdown_flag
es de tipo sig_atomic_t
, un entero especial que se puede modificar de forma segura globalmente desde el código del manejador de señales. Entonces, una vez que el usuario envía la señal SIGINT
al proceso hijo, se invoca la función cleanupRoutine
que establece el valor de shutdown_flag
en 0
y el control regresa al bucle while
donde la expresión de condición se evalúa nuevamente y el cero lo obliga a separarse del bucle. El hijo sale y el padre obtiene su estado cuando vuelve la función 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