Programación de señales de Linux, explicación detallada de ejemplos de funciones de señales (4) -【Serie de arquitectura de comunicación de Linux】

Directorio de artículos de la serie

Serie de habilidades C++
Serie de arquitectura de comunicación Linux
Serie de programación de optimización de alto rendimiento C++
Comprensión profunda de la serie de diseño de arquitectura de software
Programación avanzada de subprocesos concurrentes en C++

Esperamos su atención! ! !
inserte la descripción de la imagen aquí

现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。
Now everything is for the future of dream weaving wings, let the dream fly in reality.

1. Introducción a la función de señal

Después de recibir una señal, puede usar la función de señal para ignorarla o capturarla, vea el siguiente ejemplo:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sig_usr(int signo)
{
    
    
	if(signo == SIGUSR1)
	{
    
    
		printf("收到了SIGUSR1信号!\n");
	}else if(signo == SIGUSR2){
    
    
		printf("收到了SIGUSR2信号!\n");
	}else{
    
    
		printf("收到了未捕捉的信号%d!\n", signo);
	}
}
int main(int argc, char *const *argv)
{
    
    
	if(signal(SIGUSR1, sig_usr) == SIG_ERR)
	{
    
    
		printf("无法捕捉SIGUSR1信号!\n");
	}
	//系统函数。参数1是个信号,参数2是个函数指针,代表一个针对该信号的捕捉处理函数
	if(signal(SIGUSR2, sig_usr) == SIG_ERR)
	{
    
    
		printf("无法捕捉SIGUSR2信号!\n");
	}
	for(;;)
	{
    
    
		sleep(1); //休息1s
		printf("休息1s\n");
	}
	return 0;
}

Al llamar dos veces a la función de señal, se registran las funciones de procesamiento de señal (sig_usr) correspondientes a la señal SIGUSR1 y la señal SIGUSR2 respectivamente, y cuando se reciban estas dos señales, se llamará a sig_usr. Durante la función de procesamiento de señales sig_usr, solo se realiza algún trabajo de salida de información.

Compile y ejecute, luego use el comando kill para enviar dos señales:

kill -usr1 4155
kill -usr2 4155

Ver proceso:
inserte la descripción de la imagen aquí

Figura 1.1 Las señales USR1 y USR2 se envían al proceso nginx respectivamente y aparecen algunas indicaciones en la ventana de salida

Se puede ver que el proceso nginx ha recibido dos señales y puede continuar ejecutándose sin verse afectado. También puede ver otra forma del comando kill: use directamente el nombre de la señal para enviar una señal al proceso.

Con este ejemplo, se deben reconocer dos cuestiones:

(1) La función de señal captura las señales SIGUSR1 y SIGUSR2 del sistema y utiliza su propia función para procesarlas correctamente.

如果程序中不捕捉SIGUSR1或者SIGUSR2信号,用kill向改进程发送SIGUSR1或者SIGUSR2信号,进程会有什么表现呢?

Respuesta: Por supuesto que es para terminar el proceso, porque la acción predeterminada del sistema de estas dos señales es terminar el proceso. (Puedes probarlo tú mismo)

(2) La señal puede ser enviada por cierto proceso o por el núcleo, pero no importa cómo se envíe, el proceso de destino (nginx) ha recibido la señal. El kernel notará el hecho de que el proceso de destino recibe la señal, y el kernel tomará medidas en ese momento. ¿Qué es la acción del núcleo?
Como se muestra en la imagen:

inserte la descripción de la imagen aquí

Figura 1.1 Una señal repentina hace que el proceso cambie del modo de usuario al modo kernel y luego vuelva al modo de usuario después del procesamiento

2. La pregunta del pensamiento extendido: el concepto de función reentrante

Veamos el siguiente código, ¿cuál es el problema?

#include <stdio.h>
#include <unistd.h>
#include <signal.h>


int g_mysign = 0;
void muNEfunc(int value)
{
    
    
	//...其他处理
	g_mysign = value;//函数muNEfunc能够修改全局变量g_mysign的值
	//...其他处理
}

void sig_usr(int signo)
{
    
    
	muNEfunc(22);
	if(signo == SIGUSR1)
	{
    
    
		printf("收到了SIGUSR1信号!\n");
	}else if(signo == SIGUSR2){
    
    
		printf("收到了SIGUSR2信号!\n");
	}else{
    
    
		printf("收到了未捕捉的信号%d!\n", signo);
	}
}

int main(int argc, char *const *argv)
{
    
    
	if(signal(SIGUSR1, sig_usr) == SIG_ERR)
	{
    
    
		printf("无法捕捉SIGUSR1信号!\n");
	}
	//系统函数。参数1是个信号,参数2是个函数指针,代表一个针对该信号的捕捉处理函数
	if(signal(SIGUSR2, sig_usr) == SIG_ERR)
	{
    
    
		printf("无法捕捉SIGUSR2信号!\n");
	}
	for(;;)
	{
    
    
		sleep(1); //休息1s
		printf("休息1s\n");

		muNEfunc(15);
		printf("g_mysign = %d\n", g_mysign);
	}
	return 0;
}

Piénselo, ¿qué problemas surgirán al escribir código como este? (Por supuesto, este tipo de problema se da en casos extremos, no suele aparecer, y no es fácil de ver)

Se espera que el valor de la salida de g_mysign cada vez sea 15, pero se recibe una señal y el valor de g_mysign se cambia en el controlador de señal, lo que hace que la salida de g_mysign por printf se convierta en 22. ¿No es este resultado inesperado!

Por tanto, se introduce un concepto denominado “ función reentrante ”.

Una función reentrante , también conocida como función reentrante o función segura de señal asíncrona , se refiere a una función que es segura para llamar en una función de procesamiento de señal . (obviamente muNEfunc no es seguro)

⚠️有些周知的函数是不可重入的(在信号处理函数中不要调用的)如malloc分配内存的函数、printf屏幕输出函数等。(实际商业代码中避免在信号处理函数中调用printf函数)。

De acuerdo al análisis se obtienen algunas conclusiones y métodos de procesamiento:

(1) En la función de procesamiento de señales, intente usar declaraciones simples para hacer cosas simples e intente no llamar a las funciones del sistema para evitar problemas.
(2) Si las funciones del sistema deben llamarse en funciones de procesamiento de señales, solo llame funciones reentrantes y no llame funciones del sistema no reentrantes.
(3) Si la función del sistema reentrante que puede modificar el valor de errno debe llamarse en la función de procesamiento de señal, se debe considerar hacer una copia de seguridad del valor de errno por adelantado y luego restaurar el valor de errno antes de regresar de la señal. función de procesamiento. ( errno的值的系统函数被认为是可重入的系统函数)

#include <errno.h> //用到errno则需要包含此头文件
void sig_usr(int signo)
{
    
    
	int myerrno = errno; //备份errno值
	//......进行一系列处理,如调用可重入函数
	//......
	errno = myerrno; //还原errno值
}

3. Conjunto de señales (palabra de máscara de señal)

思考一个问题:收到一个SIGUSR1信号,开始执行信号处理函数sig_usr,尚未执行完成时,突然又收到一个SIGUSR1信号,系统会不会再次触发sig_usr函数开始执行呢? Generalmente no, es decir, cuando se recibe una señal y se comienza a ejecutar la función de procesamiento de señal, la misma señal posterior suele ser "blindada/bloqueada" hasta que se ejecuta la función de procesamiento de señal (procesada automáticamente por el sistema).

Un proceso debe recordar qué señales están actualmente bloqueadas. Por ejemplo, cuando se recibe la señal SIGUSR1, el sistema establecerá el indicador de la señal que se está procesando en 1 y luego ejecutará la función de procesamiento de la señal.Si la señal se recibe nuevamente cuando no se ha ejecutado la función de procesamiento de la señal, el sistema detecta que el indicador de la señal se ha completado.Si es 1, la siguiente señal SIGUSR1 debe ponerse en cola (esperando para llamar a la función de procesamiento de señales para procesar) o ignorarse directamente (perdida). Cuando se ejecuta la función de procesamiento de señales, establezca el indicador correspondiente a la señal SIGUSR1 nuevamente en 0. En este momento, si hay una señal SIGUSR en cola o una señal SIGUSR1 recién recibida, puede continuar llamando a la función de procesamiento de señales para su procesamiento. .

En este momento se introdujo el concepto de conjunto de señales , un tipo de datos llamado conjunto de señales, que puede guardar los estados (0 o 1) de 60 señales. Use 0 para indicar que cierta señal no ha sido recibida y use 1 para indicar que cierta señal ha sido recibida y está siendo procesada.

	1、例如如果约定第五个位置表示信号SIGUSR1,程序开始执行后,收到一个SIGUSR1信号,就立即把第5个位置标记1:
		 0000100000,0000000000,0000000000,.....
	2、然后,等待调用信号处理函数处理这个到来的信号;
	3、此时,如果再收到一个SIGUSR1信号,因为第5个位置已经被标记为1,后面的这个SIGUSR1信号就会排队等候或者忽略;
	4、调用完处理函数后,把信号集的第5个位置标记回0:
	     0000000000,0000000000,0000000000,......
	5、此时,如果有排队等候或者新收到的SIGUSR1信号,就会又可以继续调用信号处理函数来处理了。

信号集这种数据类型用 sigset_t 来表示。
La estructura de sigset_t es más o menos así:

typedef struct{
    
    
	unsigned long sig[2]; //long是4字节32位,两个就是64位,代表64个信号
}sigset_t;

4. Función de correlación de señal

Con estos tipos de conjuntos de señales, sigempty、sigfillset、sigaddset、sigdest、sigprocmask、sigismemberse pueden introducir varias funciones como .

(1) sigeemptyset. Borre todas las señales en el conjunto de señales, lo que significa que estas más de 60 señales no han llegado.

    0000000000,0000000000,000000000,......

(2) conjunto de relleno de signos. Establezca todas las señales en el conjunto de señales en 1, justo lo contrario de la función sigemptyset. Hará que cualquier señal entrante se ponga en cola o se ignore.

	111111111,1111111111,111111111, ......

(3) El conjunto de señales admite más de 60 señales, y puede agregar (establecer el indicador de señal en 1) o eliminar (establecer el indicador de señal en 0) señales específicas al conjunto de señales, lo que se puede hacer con sigaddset y sigdelset.

	sigaddset用于将某个信号设置为1,sigdelset用于将某个信号设置为0。

(4)sigprocmask、sigismember。

	sigpromask函数用于设置进程所对应的信号集(进程有默认的信号集,但可以用sigpromask函数设置其他信号集)。
	sigismember函数用于检测信号集的特定信号是否被置位。

5. Ejemplo de demostración de funciones de señal como sigprocmask

Los ejemplos son los siguientes:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

//信号处理函数
void sig_quit(int signo)
{
    
    
	printf("收到了SIGQUIT信号!\n");
}
int main(int argc, char *const *argv)
{
    
    
	//定义新的信号集和原有的信号集
	sigset_t newmask, oldmask, pendmask; 
	//注册信号对应的处理函数
	if(signal(SIGQUIT, sig_quit) == SIG_ERR)
	{
    
    
		printf("无法捕捉SIGUSR1信号!\n");
		//退出程序,参数是错误代码,0表示正常退出,非0表示错误,但具体什么错误,没有特别的规定
		exit(1);
	}
	//newmask信号集中所有的信号都清零(表示这些信号都没有来)
	sigemptyset(&newmask);
	//设置newmask信号集中的SIGQUIT信号位为1,再来SIGQUIT信号时进程就收不到
	sigaddset(&newmask, SIGQUIT);
	//设置该进程所对应的信号集
	//第1个参数用了SIG_BLOCK,表明设置进程新的信号屏蔽字为当前信号屏蔽字和第2个参数指向的信号集的并集。
	//一个进程的当前信号屏蔽字,开始全部为0,相当于把当前信号屏蔽字设置成newmask(屏蔽了SIGQUIT)。
	//第三个参数不为空,则进程老的(调用本sigprocmask()之前的)信号集会保存到第3个参数里,以备后续恢复用
	if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0){
    
    
		printf("sigprocmask(SIG_BLOCK)失败!\n");
		exit(1);
	}
	printf("我要开始休息10s了-------begin----,此时我无法接受SIGQUIT信号!\n");
	sleep(10);
	printf("我已经休息10s了-------end----!\n");
	//测试一个指定的信号位是否被置位,测试的是newmask
	if(sigismember(&newmask, SIGQUIT))
	{
    
    
		printf("SIGQUIT信号被屏蔽了!\n");
	}else{
    
    
		printf("SIGQUIT信号没有被屏蔽了!\n");
	}
	//测试一个指定的信号位是否被置位,测试的是newmask
	if(sigimember(&newmask,SIGHUP))
	{
    
    
		printf("SIGQUIT信号被屏蔽了!\n");
	}else{
    
    
		printf("SIGQUIT信号没有被屏蔽了!\n");
	}
	//现在取消SIGQUIT信号的屏蔽(阻塞)-- 把信号集还原回去
	//第一个参数用了SIG_SETMASK表明设置进程新的信号屏蔽字为第2个参数指向的信号集,第3个参数没用
	if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
	{
    
    
		printf("sigprocmask(SIG_SETMASK)失败!\n");
	}else{
    
    
		printf("sigprocmask(SIG_SETMASK)成功!\n");
	}
	//测试一个指定的信号位是否被置位,这里测试的是oldmask
	if(sigismember(&oldmask, SIGQUIT))
	{
    
    
		printf("SIGQUIT信号被屏蔽了!\n");
	}else{
    
    
		printf("SIGQUIT信号没有被屏蔽,您可以发送SIGQUIT信号了,我要睡10s!!!!!!!\n");
		int mysl = sleep(10);
		if(mysl > 0)
		{
    
    
			printf("sleep还没睡够,剩余%d\n", mysl);
		}
	}
	printf("再见了!\n");
	return 0;
}

El resultado de la operación es el siguiente:
inserte la descripción de la imagen aquí

Figura 5.1 Análisis de los resultados de la operación del caso

6. Resumen

También hay una sigactionfunción, utilizada para reemplazar signalla función. Sólo se utiliza en códigos comerciales sigaction. Tú puedes entender.

Supongo que te gusta

Origin blog.csdn.net/weixin_30197685/article/details/131343078
Recomendado
Clasificación