Cómo usar los manejadores de señal en el lenguaje C?

Cómo usar los manejadores de señal en el lenguaje C?
En este artículo le mostraremos cómo usar los controladores de señal en Linux usando el lenguaje C. Pero primero discutiremos qué es 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 una situación importante. Cuando un proceso o hilo ha recibido una señal, el proceso o el hilo 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. La razón 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 ¿Está definido el número total de señal?. El valor de Nsig es uno mayor que el número total de señal definida (todos los números de señal se asignan consecutivamente).

Las siguientes son las señales estándar:

Nombre de señal Descripción
Suspiro Colgando el proceso. La señal de suspiro se utiliza para informar la desconexión del terminal del usuario, posiblemente porque se pierde o cuelga una conexión remota.
Firme Interrumpir el proceso. Cuando el usuario escribe el personaje int (normalmente Ctrl + c) se envía la señal Sigint.
Sigquín Dejar el proceso. Cuando el usuario escribe el carácter de abandono (normalmente ctrl + \) se envía la señal de sigquit.
Sigillón Instrucción ilegal. Cuando se intenta ejecutar basura o instrucción privilegiada, se genera la señal Sigill. Además, se puede generar Sigill cuando la pila se desborda, o cuando el sistema tiene problemas para ejecutar un controlador de señal.
Siglo Trampa. Una instrucción de punto de interrupción y otras instrucciones de trampa 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 de abort (). Esta señal indica un error detectado por el programa mismo e informado por la llamada de función ABORT ().
Sigfpe Excepción de punto flotante. Cuando se produjo un error aritmético fatal, se genera la señal SIGFPE.
Sigusr1 y Sigusr2 Las señales Sigusr1 y Sigusr2 pueden usarse como desee. Es útil escribir un controlador de señal 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 opciones:

Término: El proceso terminará.
Centro: El proceso terminará y producirá un archivo de volcado central.
IGN: El proceso ignorará la señal.
Detener: El proceso se detendrá.
Cont: El proceso continuará por ser detenido.

La acción predeterminada se puede cambiar utilizando la función del controlador. La acción predeterminada de alguna señal no se puede cambiar. Sigkill y Sigabrt La acción predeterminada de la señal no se puede cambiar ni ignorar.

Manejo de la señal

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.

  • Si se ignora la acción especificada para la señal, entonces la señal se descarta inmediatamente.
  • El programa puede registrar una función de controlador utilizando la función como señal o siga. Esto se llama un manejador captura la señal.
  • Si la señal no ha sido manejada ni ignorada, su acción predeterminada tiene lugar.

Podemos manejar la señal usando señal o siga función. Aquí vemos cómo el más simple señal() la función se usa para manejar señales.

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

El señal() llamará al concurrido función si el proceso recibe una señal signo. El señal() Devuelve un puntero a la función concurrido Si tiene éxito o devuelve un error a errno y -1 de lo contrario.

El concurrido El 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 usa para tomar medidas predeterminadas de la señal.
  2. Sig_ign: Es un puntero al sistema ignorar la función Sig_ign (),declarado en H archivo de cabecera.
  3. Puntero de la función del controlador definido por el usuario: El tipo de función del controlador definido por el usuario es void (*) (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 nula
printf ("\ Ninside Handler Función \ n");

int main ()
señal (Sigint, Sig_handler); // Registre el manejador de señal
para (int i = 1 ;; i ++) // loop infinito
printf ("%d: dentro de la función principal \ n", i);
dormir (1); // retraso por 1 segundo

regresar 0;

En la captura de pantalla de la salida del ejemplo1.c, podemos ver que en la función principal se está ejecutando el bucle infinito. Cuando el usuario escribió CTRL+C, se invoca la parada de ejecución de la función principal y la función del controlador de la señal. Después de completar la función del controlador, se reanudó la ejecución de la función principal. Cuando el tipo de usuario se escribe Ctrl+\, el proceso se renuncia.

Ignorar el ejemplo de señales

#incluir
#incluir
#incluir
int main ()
señal (Sigint, Sig_ign); // Registre el controlador de señal para ignorar la señal
para (int i = 1 ;; i ++) // loop infinito
printf ("%d: dentro de la función principal \ n", i);
dormir (1); // retraso por 1 segundo

regresar 0;

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

Ejemplo de controlador de señal de reinicio

#incluir
#incluir
#incluir
void sig_handler (int signum)
printf ("\ Ninside Handler Función \ n");
señal (Sigint, SIG_DFL); // Reyador de señal de registro de RE para la acción predeterminada

int main ()
señal (Sigint, Sig_handler); // Registre el manejador de señal
para (int i = 1 ;; i ++) // loop infinito
printf ("%d: dentro de la función principal \ n", i);
dormir (1); // retraso por 1 segundo

regresar 0;

En la captura de pantalla de la salida del ejemplo3.C, podemos ver que cuando el usuario escribió Ctrl+C, se invoca la función del controlador. En la función del controlador, el controlador de señal se registra en Sig_dfl Para una 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 Firme señal.

Envío de señales:

Un proceso también puede enviar explícitamente señales a sí misma o a otro proceso. La función Raise () y Kill () se puede usar para enviar señales. Ambas funciones se declaran en la señal.H Archivo de encabezado H.

int Raise (int Signum)

La función Raise () utilizada para enviar una señal signo al proceso de llamadas (en sí). 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 signo a un grupo de proceso o proceso especificado por pid.

Ejemplo de manejador de señal Sigusr1

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

int main ()
señal (Sigusr1, Sig_handler); // Registre el manejador de señal
printf ("Inside la función principal \ n");
elevar (Sigusr1);
printf ("Inside la función principal \ n");
regresar 0;

Aquí, el proceso se envía la señal Sigusr1 a sí misma usando la función Raise ().

Programa de ejemplo de criar con Kill

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

int main ()
pid_t pid;
señal (Sigusr1, Sig_handler); // Registre el manejador de señal
printf ("Inside la función principal \ n");
pid = getpid (); // Procesar ID de sí mismo
matar (pid, sigusr1); // Envía Sigusr1 a sí mismo
printf ("Inside la función principal \ n");
regresar 0;

Aquí, el proceso envía Sigusr1 Señalizar a sí mismo usando matar() función. getpid () se usa para obtener la identificación del proceso de sí mismo.

En el siguiente ejemplo, veremos cómo se comunican los procesos de los padres y el niño (comunicación entre procesos) utilizando matar() y función de señal.

Comunicación infantil de los padres con señales

#incluir
#incluir
#incluir
#incluir
void sig_handler_parent (int Signum)
printf ("Parent: 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);
Kill (Getppid (), Sigusr1);

int main ()
pid_t pid;
if ((pid = fork ())<0)
printf ("FORK FAINT \ n");
salida (1);

/ * Proceso infantil */
else if (pid == 0)
señal (Sigusr1, SIG_HANDLER_CHILD); // Registre el manejador de señal
printf ("Child: Waiting for Signal \ n");
pausa();

/ * Proceso principal */
demás
señal (Sigusr1, SIG_HANDLER_PARENT); // Registre el manejador de señal
dormir (1);
printf ("Parent: envío de señal al niño \ n");
matar (pid, sigusr1);
printf ("Parent: esperando respuesta \ n");
pausa();

regresar 0;

Aquí, tenedor() La función crea el proceso infantil y devuelve cero al proceso infantil y la identificación del proceso infantil al proceso principal. Entonces, PID ha sido revisado para decidir el proceso de los padres e hijos. En el proceso de los padres, se durme durante 1 segundo para que el proceso infantil pueda registrar la función del controlador de señal y esperar la señal del padre. Después de 1 segundo proceso de proceso de los padres, envíe Sigusr1 Señal al proceso del niño y espere la señal de respuesta del niño. En el proceso infantil, primero está esperando la señal de los padres y cuando se recibe la señal, se invoca la función del controlador. Desde la función del controlador, el proceso infantil envía otro Sigusr1 Señal al padre. Aquí getppid () La función se utiliza para obtener la identificación del proceso de los padres.

Conclusión

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