承接上文

在这里插入图片描述

信号处理动作用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号处理函数sighandler当前正在执行main函数,这时发生中断异常切换内核态。在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandlermain函数使用不同的堆栈空间,它们之间不存在调用和被调用关系,是 两个独立的控流程sighandler函数返回自动执行特殊系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

一.sigaction

1.基本使用

前面已经聊过signal函数了,这里就不再赘述。

+

在这里插入图片描述

第一个参数是传入的信号种类。

第二个第三参数都是struct sigaction类型结构体,前面一个输入参数代表你要执行的动作;后一个输出参数,会把原本的信息带出,方便之后恢复

返回值:成功返回0,失败返回-1.

例子

在这里插入图片描述

在这里插入图片描述

前面我们说过信号在发送后,操作系统会把pending位图的该信号位置置1,当处理该信号时将位图置零,然后执行方法。那么究竟是先置零再执行方法,还是方法执行完成后再置零呢?

handler方法里打印pending图,即可看出先后顺序

在这里插入图片描述

在这里插入图片描述

结果看出,操作系统是先将pending位图置零,再调用方法。

2.sa_mask字段

当某个信号的处理函数调用时,内核自动当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项,本章的代码都把sa_flags设为0,sa_sigaction实时信号的处理函数。

总之就是防止信号进行嵌套调用。

对比实验,一般情况下如果我们一直发2号信号,那么在处理2号信号时,其他2号信号一直会阻塞pending图对应位置为1

在这里插入图片描述

在这里插入图片描述

在调用2号信号时同时屏蔽3号信号

在这里插入图片描述

在这里插入图片描述

二.可重入函数

例子链表头插

在这里插入图片描述

一个链表进行头插时(不带哨兵节点)分为两步,先是p->next=head,接着是head=p。那么当代码执行到第一步时,突然接收到信号,而跑去进行信号中断了(这里并没有调用系统函数为什么能够实现内核态和用户态的转变从而进行信号中断呢?因为操作系统同时会执行多个进程,而为了让这些进程同时都被执行,操作系统会来回切换这些进程,从而不断的进行用户态和内核态的转变)。正巧我们对该信号的捕捉方法也是使用insert进行头插,那么程序就会再让另一个节点指向head。接着信号处理完毕,再返回继续执行第二步head->p。对于这种一个函数被重复调用的情况被称为函数重入

以上就出现了问题,虽然node1节点插入成功了,但我们丢失node2节点,从而导致了内存泄漏

如果一个函数,被重复进入的情况下可能出错,那么就被叫做不可重入函数。否则就被叫做可重入函数。(目前的大部分函数都是不可重入的)

三.volatile

核心作用:防止编译器过度优化保存内存可见性。

一个例子

在这里插入图片描述

在这里插入图片描述

这里的原理简单发送2信号后改变flag的值就不再死循环,但由于我们并未对flagj进行其他使用,如果我们启用编译器的优化功能会发生不同的现象。

在这里插入图片描述

常见优化是O0~O3,这里使用O1优化。

在这里插入图片描述

在这里插入图片描述

可以看到我们发送了信号2后程序也并没有结束循环。这是为什么呢?

在这里插入图片描述

为了防止出现变量的过度优化,我们可以使用volatile

在这里插入图片描述

在这里插入图片描述

四.SIGCHLD信号

进程一章讲过用waitwaitpid函数清理僵尸进程,父进程可以阻塞等待进程结束,也可以阻塞查询是否有子进程结束等待清理(也就是轮询方式)。采用第一种方式,父进程阻塞了就不能处理自己工作了;采用第二种方式,父进程在处理自己工作的同时还要记得时不时地轮询一下,程序实现复杂。

其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程 终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可

在这里插入图片描述

验证

在这里插入图片描述

在这里插入图片描述

所以在进行进程等待时,我们可以采用基于信号的方式等待

在这里插入图片描述

事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认忽略动作和用户用sigaction函数自定义忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。

在这里插入图片描述

平常我们创建子进程后并没有主动释放但也并没有影响,因为Linux默认把17号信号设置成了SIG_IGN。

原文地址:https://blog.csdn.net/m0_73790767/article/details/134709208

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_25026.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注