Linux信号编程、signal函数范例详解( 4 ) -【Linux通信架构系列 】

news2024/11/15 7:38:20

系列文章目录

C++技能系列
Linux通信架构系列
C++高性能优化编程系列
深入理解软件架构设计系列
高级C++并发线程编程

期待你的关注哦!!!
在这里插入图片描述

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

Linux信号编程、signal函数范例详解

  • 系列文章目录
  • 一、signal 函数初识
  • 二、引申出的思考问题 - 可重入函数概念
  • 三、信号集(信号屏蔽字)
  • 四、信号相关函数
  • 五、sigprocmask等信号函数的范例演示
  • 六、小结

一、signal 函数初识

收到一个信号之后,可以使用signal函数来忽略或者捕捉,看如下范例:

#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;
}

通过两次调用signal函数,分别注册信号SIGUSR1和信号SIGUSR2对应的信号处理函数(sig_usr),当收到这两个信号时,sig_usr就会被调用。在sig_usr信号处理函数过程中,只是做了一些信息输出的工作。

编译运行,然后用kill命令发送两个信号:

kill -usr1 4155
kill -usr2 4155

查看进程:
在这里插入图片描述

图1.1 向nginx进程分别发送了USR1和USR2的信号,输出窗口出现一些提示

可以看出,nginx进程收到两个信号,并且还能继续运行不受影响。还可以看到kill命令的另外一种形式:直接用信号名的方式向进程发送信号。

通过这个范例,应该认识到两个问题:

(1)signal函数捕捉了系统的SIGUSR1和SIGUSR2信号,并用自己的函数来处理,获得了成功。

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

答案:当然是终止进程,因为这两个信号的系统默认动作是终止进程。(可以自行测试下)

(2)信号可能是某个进程发出的,也可能是内核发出的,但不管是怎么发出的,总之目标进程(nginx)收到了这个信号。目标进程收到信号这件事,会被内核注意到,这时内核就有动作了。内核动作是什么呢?
如图:

在这里插入图片描述

图1.1 突然到来的信号导致进程从用户态切换到内核态,处理完毕再切换回用户态

二、引申出的思考问题 - 可重入函数概念

我们看下如下代码,会出现什么问题?

#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;
}

请思考一下,这样写代码会出现什么问题?(当然这种问题是在极端的情况下出现,平时不出现,不太容易看出)

本来期望每次输出的g_mysign的值是15,但偏偏收到一个信号,信号处理程序中改变了g_mysign的值,导致printf输出的g_mysign变成了22。这个结果是不是出乎意料呢!

所以,引出一个概念,叫做“可重入函数”。

可重入函数又称可重入的函数异步信号安全的函数,指在信号处理函数中调用是安全的函数。(显然muNEfunc是不安全的)

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

根据分析,得到一些结论和处理方法:

(1)在信号处理函数中,应尽量使用简单的语句做简单的事情,尽量不要调用系统函数,以免引起麻烦。
(2)如果必须在信号处理函数中调用系统函数,只调用可重入函数,不要调用不可重入的系统函数。
(3)如果必须在信号处理函数中调用那可能修改errno的值的可重入系统函数,应考虑事先备份errno的值,事后再从信号处理函数返回之前恢复errno的值。(errno的值的系统函数被认为是可重入的系统函数

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

三、信号集(信号屏蔽字)

思考一个问题:收到一个SIGUSR1信号,开始执行信号处理函数sig_usr,尚未执行完成时,突然又收到一个SIGUSR1信号,系统会不会再次触发sig_usr函数开始执行呢? 一般不会,也就是说,当收到某个信号,启动执行信号处理函数的时候,通常会”屏蔽/阻塞“其后相同的信号,直到信号处理函数执行结束(系统自动处理)。

一个进程必须记住当前阻塞了哪些信号。如收到信号SIGUSR1时,系统将标记正在处理的该信号的标志设置为1,然后去执行信号处理函数,如果信号处理函数未执行完成时再次收到该信号,系统检测到该信号的标志已经为1,后来的SIGUSR1信号就需要排队等候(等待调用信号处理函数来处理)或直接被忽略(丢失)。当信号处理函数执行完毕,再把信号SIGUSR1信号对应的标志设置回0,此时如果有排队的SIGUSR信号或者新收到的SIGUSR1信号,就可以继续调用信号处理函数处理了。

这时候引入了信号集的概念,一种叫做信号集的数据类型,这种数据类型能把60个信号的状态(0或者1)都保存下来。用0表示没收到某个信号,用1表示收到某个信号并正在处理中。

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

信号集这种数据类型用 sigset_t 来表示。
sigset_t 结构大概这样:

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

四、信号相关函数

有了这些信号集类型就可以介绍 sigempty、sigfillset、sigaddset、sigdest、sigprocmask、sigismember 等几个函数了。

(1)sigemptyset。把信号集中所有的信号清零,表示这60多个信号都没来。

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

(2)sigfillset。把信号集中的所有信号都设置为1,与sigemptyset功能正好相反。会导致到来任何信号都会排队等候或者被忽略。

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

(3)信号集中支持60多个信号,可以向信号集中增加(信号标志设置为1)或删除(信号标志设置为0)特定的信号,用sigaddset和sigdelset就可以做到。

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

(4)sigprocmask、sigismember。

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

五、sigprocmask等信号函数的范例演示

范例如下:

#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;
}

运行结果如下:
在这里插入图片描述

图5.1 案例运行结果分析

六、小结

还有一个sigaction函数,用来取代signal函数。商业代码中只用sigaction。可以了解下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/675393.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python求1是什么?Python求1在SEO中的应用

Python求1是什么&#xff1f;Python求1在SEO中的应用 介绍Python求1 Python求1&#xff0c;也叫做1-bit计数器&#xff0c;是一种用来统计网页浏览量的技术。在Web开发中&#xff0c;我们需要记录网页的浏览次数&#xff0c;以便了解网站的流量和用户的使用情况。传统的做法是…

chatgpt赋能python:Python求绝对值:从初学者到高级工程师的必备知识

Python求绝对值&#xff1a;从初学者到高级工程师的必备知识 Python是一种有趣且功能强大的编程语言。它非常易于学习&#xff0c;同时又具有广泛的应用领域&#xff0c;比如Web开发、数据分析、机器学习和人工智能等。在Python的数学运算中&#xff0c;求绝对值是一个常见的需…

chatgpt赋能python:Python浮点型的两种表示方法

Python浮点型的两种表示方法 Python是一种解释型的动态语言&#xff0c;可以处理多种数据类型。其中&#xff0c;浮点型是其中一种数据类型&#xff0c;它包括十进制和科学计数法两种表示方法。 十进制表示法 十进制浮点数是Python的基本浮点类型&#xff0c;可以表示实数。…

2023 hnust 湖南科技大学 大数据技术与应用 期末考试 复习资料

前言 感谢&#xff1a;lqx&#xff08;主要内容来源&#xff09;&#xff0c;hqh 有自己的理解和魔改 可以参考的资料 课后题答案我爬取的老师布置的学习通课后题往年资料csdn里面找到的&#xff1a;1、2老师ppt上课划重点录音 不提供pdf文件&#xff0c;方便修改&#xff0…

探索技术极致,未来因你出‘粽’

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

chatgpt赋能python:Python游戏——为什么它成为未来最热门的游戏开发工具

Python游戏——为什么它成为未来最热门的游戏开发工具 在游戏开发中&#xff0c;Python一直是非常强大和受欢迎的语言。Python具有很多吸引人的特点和实用功能&#xff0c;它为游戏开发者提供了多种可能&#xff0c;我们在本文中将介绍Python游戏以及为什么它越来越受欢迎。 …

【从零开始学习JAVA | 第十一篇】ArrayList集合

目录 前言&#xff1a; ArrayList&#xff1a; 常用成员方法&#xff1a; 案例练习&#xff1a; 1.集合的遍历方式&#xff08;引用数据类型&#xff09;&#xff1a; 2&#xff1a;集合的遍历方式&#xff08;基本数据类型&#xff09;&#xff1a; 总结&#xff1a;…

一面、二面、三面有什么区别?

很多公司面试都分一面、二面、三面甚至更多&#xff0c;大家可能会好奇&#xff0c;为什么要面这么多面&#xff0c;每一面又有啥区别呢&#xff1f; 首先我来回答下为什么要这么多面&#xff0c;最核心的是最后3点&#xff1a; 如果光是一个人面&#xff0c;担心会看走眼&…

python基础学习6【DatatimeIndex与PeriodIndex函数+Timedelta类+连接数据库+agg()函数和aggregate()函数】

转换与处理时间序列数据 转换字符串时间为标准时间: Timestamp类型&#xff1a;最基础最常用。 pd.to_datetime(data[lock_time])#转换 如果超出时间戳最大值&#xff0c;最小值&#xff0c;时间戳存储可能不成功 &#xff1a; DatatimeIndex与PeriodIndex函数【其实俺暂时…

【Spring Cloud Sleuth 分布式链路跟踪】 —— 每天一点小知识

&#x1f4a7; S p r i n g C l o u d S l e u t h 分布式链路跟踪 \color{#FF1493}{Spring Cloud Sleuth 分布式链路跟踪} SpringCloudSleuth分布式链路跟踪&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云…

scratch lenet(9): C语言实现tanh的计算

文章目录 1. 目的2. tanh ⁡ ( x ) \tanh(x) tanh(x) 的 naive 实现2.1 数学公式2.2 naive 实现 3. tanh ⁡ ( x ) \tanh(x) tanh(x) 的快速计算3.1 Maple 中的近似公式3.2 tan_c3()3.3 Gauss 连分数公式 (Continued Fraction) 4. 最终代码和运行结果代码运行结果 5. 其他Ref…

使用python制作常用图表

案例01 制作柱形图展示数据的对比关系——员工销售业绩统计表.xlsx import xlwings as xw app xw.App(visibleTrue, add_bookFalse) workbook app.books.open(员工销售业绩统计表.xlsx) # 打开要制作图表的工作簿 for i in workbook.sheets: # 遍历工作簿中的工作表chart i…

node.js安装及配置教程(win11)

node.js安装及配置教程&#xff08;win11&#xff09; 一、下载二、安装三、环境配置 一、下载 官网下载&#xff1a;点击下载 根据自己电脑的位数选择对应的版本即可 网盘下载&#xff1a;点击下载 二、安装 下载完成后&#xff0c;双击运行程序&#xff0c;点击next 勾…

iOS自动化环境搭建(超详细)

1.macOS相关库安装 libimobiledevice > brew install libimobiledevice 使用本机与苹果iOS设备的服务进行通信的库。 ideviceinstaller brew install ideviceinstaller 获取设备udid、安装app、卸载app、获取bundleid carthage > brew install carthage 第三方库…

【Redis】Redis最佳实践/经验总结

【Redis】Redis最佳实践/经验总结 文章目录 【Redis】Redis最佳实践/经验总结1. Redis键值设计1.1 优雅的key结构1.2 拒绝BigKey1.2.1 BigKey的危害1.2.2 如何发现BigKey1.2.3 如何删除BigKey 1.3 恰当的数据类型1.3.1 例11.3.2 例2 2. 批处理优化2.1 Pipeline2.1.1 单个命令的…

ROM和RAM的工作原理(DRAM和DROM)以及DRAM的刷新方法

只读存储器ROM: ROM和RAM都是支持随机存取的存储器&#xff0c;其中SRAM和DRAM均为易失性半导体存储器。而ROM中一旦有了信息&#xff0c;就不能轻易改变&#xff0c;即使掉电也不会丢失&#xff0c;它在计算机系统中是只供读出的存储器。ROM器件有两个显著的优点: 1)结构简单&…

RabbitMQ 消息丢失的场景,如何保证消息不丢失?

一.RabbitMQ消息丢失的三种情况 第一种&#xff1a;生产者弄丢了数据。生产者将数据发送到 RabbitMQ 的时候&#xff0c;可能数据就在半路给搞丢了&#xff0c;因为网络问题啥的&#xff0c;都有可能。 第二种&#xff1a;RabbitMQ 弄丢了数据。MQ还没有持久化自己挂了 第三种…

软考A计划-系统集成项目管理工程师--一般常识-中

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

【深度学习推荐系统 理论篇】一、Wide Deep Learning for Recommender Systems

前言 在搜广推业务做了3年工程&#xff0c;最近终于有空整理下&#xff0c;完整的梳理下自己的知识架构&#xff08;预计分为理论篇/工程篇&#xff09; Wide & Deep论文链接&#xff1a;https://arxiv.org/abs/1606.07792 另外王喆老师《深度学习推荐系统》中&#xff…

安装 Nginx 服务

一.安装 Nginx 服务 1.关闭防火墙 开机自启起 安全机制 systemctl stop firewalld systemctl disable firewalld setenforce 0 2、安装依赖包 yum -y install pcre-devel zlib-devel gcc gcc-c make 3、创建运行用户 useradd -M -s /sbin/nologin nginx 4、编译安装 cd …