Programación en C

Cómo usar manejadores de señales en lenguaje C?

Cómo usar manejadores de señales en lenguaje C?
En este artículo, le mostraremos cómo usar controladores de señales en Linux usando lenguaje C. Pero primero discutiremos qué es la señal, cómo generará algunas señales comunes que puede usar en su programa y luego veremos cómo varias señales pueden ser manejadas por un programa mientras el programa se ejecuta. Entonces, comencemos.

Señal

Una señal es un evento que se genera para notificar a un proceso o hilo que ha llegado alguna situación importante. Cuando un proceso o subproceso ha recibido una señal, el proceso o subproceso detendrá lo que está haciendo y tomará alguna acción. La señal puede ser útil para la comunicación entre procesos.

Señales estándar

Las señales se definen en el archivo de encabezado señal.h como una macro constante. El nombre de la señal ha comenzado con un "SIG" y seguido de una breve descripción de la señal. Entonces, cada señal tiene un valor numérico único. Su programa siempre debe usar el nombre de las señales, no el número de señales. El motivo es que el número de señal puede diferir según el sistema, pero el significado de los nombres será estándar.

La macro NSIG es el número total de señales definidas. El valor de NSIG es uno mayor que el número total de señales definidas (todos los números de señales se asignan consecutivamente).

A continuación se muestran las señales estándar:

Nombre de la señal Descripción
SIGHUP Cuelga el proceso. La señal SIGHUP se utiliza para informar de la desconexión del terminal del usuario, posiblemente porque una conexión remota se pierde o se cuelga.
SIGINT Interrumpir el proceso. Cuando el usuario escribe el carácter INTR (normalmente Ctrl + C), se envía la señal SIGINT.
SIGQUIT Salir del proceso. Cuando el usuario escribe el carácter QUIT (normalmente Ctrl + \), se envía la señal SIGQUIT.
SIGILL Instrucción ilegal. Cuando se intenta ejecutar una instrucción basura o privilegiada, se genera la señal SIGILL. Además, SIGILL se puede generar cuando la pila se desborda o cuando el sistema tiene problemas para ejecutar un controlador de señales.
SIGTRAP Trampa de rastreo. Una instrucción de punto de interrupción y otra instrucción de captura generarán la señal SIGTRAP. El depurador usa esta señal.
SIGABRT Abortar. La señal SIGABRT se genera cuando se llama a la función abort (). Esta señal indica un error que es detectado por el propio programa y reportado por la llamada a la función abort ().
SIGFPE Excepción de punto flotante. Cuando ocurre un error aritmético fatal, se genera la señal SIGFPE.
SIGUSR1 y SIGUSR2 Las señales SIGUSR1 y SIGUSR2 se pueden utilizar como desee. Es útil escribir un manejador de señales para ellos en el programa que recibe la señal para una comunicación simple entre procesos.

Acción predeterminada de las señales

Cada señal tiene una acción predeterminada, una de las siguientes:

Término: El proceso terminará.
Centro: El proceso terminará y producirá un archivo de volcado de núcleo.
Ign: El proceso ignorará la señal.
Detener: El proceso se detendrá.
Cont: El proceso continuará desde que se detenga.

La acción predeterminada se puede cambiar usando la función del controlador. La acción predeterminada de algunas señales no se puede cambiar. SIGKILL y SIGABRT La acción predeterminada de la señal no se puede cambiar ni ignorar.

Manejo de señales

Si un proceso recibe una señal, el proceso tiene una opción de acción para ese tipo de señal. El proceso puede ignorar la señal, puede especificar una función de controlador o aceptar la acción predeterminada para ese tipo de señal.

Podemos manejar la señal usando señal o sigaction función. Aqui vemos como el mas simple señal() La función se utiliza para manejar señales.

int señal () (int signum, void (* func) (int))

La señal() llamará al func función si el proceso recibe una señal signum. La señal() devuelve un puntero a la función func si tiene éxito o devuelve un error a errno y -1 en caso contrario.

La func puntero puede tener tres valores:

  1. SIG_DFL: Es un puntero a la función predeterminada del sistema SIG_DFL (), declarado en h archivo de cabecera. Se utiliza para realizar una acción predeterminada de la señal.
  2. SIG_IGN: Es un puntero a la función de ignorar del sistema SIG_IGN (),declarado en h archivo de cabecera.
  3. Puntero de función de controlador definido por el usuario: El tipo de función del controlador definido por el usuario es vacío (*) (int), significa que el tipo de retorno es nulo y un argumento de tipo int.

Ejemplo de controlador de señal básico

#incluir
#incluir
#incluir
void sig_handler (int signum)
// El tipo de retorno de la función del controlador debe ser nulo
printf ("\ nFunción de controlador interno \ n");

int main ()
señal (SIGINT, sig_handler); // Registrar manejador de señales
for (int i = 1 ;; i ++) // Bucle infinito
printf ("% d: Dentro de la función principal \ n", i);
dormir (1); // Retraso de 1 segundo

return 0;

En la captura de pantalla de la salida de Example1.c, podemos ver que en la función principal se está ejecutando un bucle infinito. Cuando el usuario escribe Ctrl + C, se detiene la ejecución de la función principal y se invoca la función de controlador de la señal. Después de completar la función de controlador, se reanudó la ejecución de la función principal. Cuando el usuario escribe Ctrl + \, el proceso se cierra.

Ejemplo de ignorar señales

#incluir
#incluir
#incluir
int main ()
señal (SIGINT, SIG_IGN); // Registrar manejador de señales para ignorar la señal
for (int i = 1 ;; i ++) // Bucle infinito
printf ("% d: Dentro de la función principal \ n", i);
dormir (1); // Retraso de 1 segundo

return 0;

Aquí la función del controlador se registra en SIG_IGN () función para ignorar la acción de la señal. Entonces, cuando el usuario escribió Ctrl + C,  SIGINT la señal se está generando pero la acción se ignora.

Volver a registrar el ejemplo del controlador de señales

#incluir
#incluir
#incluir
void sig_handler (int signum)
printf ("\ nFunción de controlador interno \ n");
señal (SIGINT, SIG_DFL); // Registre el controlador de señales para la acción predeterminada

int main ()
señal (SIGINT, sig_handler); // Registrar manejador de señales
for (int i = 1 ;; i ++) // Bucle infinito
printf ("% d: Dentro de la función principal \ n", i);
dormir (1); // Retraso de 1 segundo

return 0;

En la captura de pantalla de la salida de Example3.c, podemos ver que cuando el usuario escribió Ctrl + C por primera vez, la función del controlador se invocaba. En la función de manejador, el manejador de señales vuelve a registrarse en SIG_DFL para la acción predeterminada de la señal. Cuando el usuario escribió Ctrl + C por segunda vez, el proceso finaliza, que es la acción predeterminada de SIGINT señal.

Envío de señales:

Un proceso también puede enviarse señales explícitamente a sí mismo oa otro proceso. La función raise () y kill () se puede utilizar para enviar señales. Ambas funciones se declaran en señal.archivo de encabezado h.

int aumento (int signum)

La función raise () utilizada para enviar señal signum al proceso de llamada (en sí mismo). Devuelve cero si tiene éxito y un valor distinto de cero si falla.

int kill (pid_t pid, int signum)

La función de matar utilizada para enviar una señal signum a un proceso o grupo de procesos especificado por pid.

Ejemplo de controlador de señal SIGUSR1

#incluir
#incluir
void sig_handler (int signum)
printf ("Función de controlador interno \ n");

int main ()
señal (SIGUSR1, sig_handler); // Registrar manejador de señales
printf ("Dentro de la función principal \ n");
subir (SIGUSR1);
printf ("Dentro de la función principal \ n");
return 0;

Aquí, el proceso envía la señal SIGUSR1 a sí mismo usando la función raise ().

Programa de ejemplo de Raise with Kill

#incluir
#incluir
#incluir
void sig_handler (int signum)
printf ("Función de controlador interno \ n");

int main ()
pid_t pid;
señal (SIGUSR1, sig_handler); // Registrar manejador de señales
printf ("Dentro de la función principal \ n");
pid = getpid (); // ID de proceso de sí mismo
matar (pid, SIGUSR1); // Envía SIGUSR1 a sí mismo
printf ("Dentro de la función principal \ n");
return 0;

Aquí, el proceso envía SIGUSR1 señal a sí mismo usando matar() función. getpid () se usa para obtener el ID de proceso de sí mismo.

En el siguiente ejemplo veremos cómo los procesos padre e hijo se comunican (Comunicación entre procesos) usando matar() y función de señal.

Comunicación entre padres e hijos con señales

#incluir
#incluir
#incluir
#incluir
void sig_handler_parent (int signum)
printf ("Padre: Recibió una señal de respuesta del niño \ n");

void sig_handler_child (int signum)
printf ("Niño: Recibió una señal del padre \ n");
dormir (1);
matar (getppid (), SIGUSR1);

int main ()
pid_t pid;
si ((pid = fork ())<0)
printf ("Error de bifurcación \ n");
salida (1);

/ * Proceso hijo * /
más si (pid == 0)
señal (SIGUSR1, sig_handler_child); // Registrar manejador de señales
printf ("Niño: esperando señal \ n");
pausa();

/ * Proceso principal * /
demás
señal (SIGUSR1, sig_handler_parent); // Registrar manejador de señales
dormir (1);
printf ("Padre: enviando señal a Niño \ n");
matar (pid, SIGUSR1);
printf ("Padre: esperando respuesta \ n");
pausa();

return 0;

Aquí, tenedor() la función crea un proceso hijo y devuelve cero al proceso hijo y la ID del proceso hijo al proceso padre. Entonces, pid se ha verificado para decidir el proceso padre e hijo. En el proceso padre, se duerme durante 1 segundo para que el proceso hijo pueda registrar la función del controlador de señales y esperar la señal del padre. Después de 1 segundo proceso padre enviar SIGUSR1 señal al proceso del niño y esperar la señal de respuesta del niño. En el proceso hijo, primero está esperando la señal del padre y cuando se recibe la señal, se invoca la función del controlador. Desde la función del controlador, el proceso hijo envía otro SIGUSR1 señal a los padres. Aquí getppid () La función se utiliza para obtener la identificación del proceso principal.

Conclusión

Signal en Linux es un gran tema. En este artículo, hemos visto cómo manejar la señal desde lo más básico, y también obtener un conocimiento de cómo se genera la señal, cómo un proceso puede enviarse una señal a sí mismo y a otro proceso, cómo la señal se puede utilizar para la comunicación entre procesos.

Instale el último emulador de Dolphin para Gamecube y Wii en Linux
Dolphin Emulator te permite jugar los juegos de Gamecube y Wii que elijas en computadoras personales con Linux (PC). Al ser un emulador de juegos de ...
Cómo usar GameConqueror Cheat Engine en Linux
El artículo cubre una guía sobre el uso del motor de trucos GameConqueror en Linux. Muchos usuarios que juegan juegos en Windows a menudo usan la apli...
Los mejores emuladores de consola de juegos para Linux
Este artículo enumerará el software de emulación de consola de juegos más popular disponible para Linux. La emulación es una capa de compatibilidad de...