初步了解Linux中的信号捕捉

简单了解内核态和用户态

在我们的程序地址空间中,我们将0~3G这部分空间叫做我们的用户空间,3~4G这部分叫做我们的内核空间,在我们的Linux中,每个进程都会有一个进程地址空间,他们的用户空间存放着当前进程的代码和数据,但是我们的内核空间,永远是一个操作系统,他通过内核空间的虚拟地址转化为物理地址去寻找操作系统的代码和数据,所以我们的内核级页表 就只需要一个来转化就够了,用户级页表有多少个进程就需要多少个。

而当我们出现异常或者系统调用的时候,需要去执行我们操作系统的代码,所以我们的进程就需要从用户态变为内核态,这个操作使我们的cpu来帮助我们完成的,cpu中有一个ecs寄存器,他的最后两位二进程代表制我们不同的身份,当这两个二进制位00时,代表内核态,位11时代表着用户态。

信号的捕捉过程

根据下面这张图,我们可以清楚的认识到信号捕捉和处理的过程:

1、我们的进程在正常执行时,如果遇到异常或者系统调用,就需要进入内核,并且将身份转为内核态

2、在内核中处理完我们的异常或者完成系统调用之后,需要检查和处理信号,(这是就是我们所说的合适的时间)

3、如果信号的处理是自定义信号,就需要我们将身份切位用户态去执行我们的自定义函数(防止存在修改和获取内核信息等操作)

4、自定义处理执行完后,再次回到我们的内核态

5、回到内核态之后,返回到我们的用户态继续执行用户空间代码。

信号捕捉函数

signal

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

功能:进行信号捕捉,自定义信号操作(9号19号除外)

参数:signum:信号编号

handler:自定义函数

返回值:可以忽略

sigaction

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体如下:

sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

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

其他知识

1、可重入函数

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的时候,**因为硬件中断使进程切换到内核,**再次回用户态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点,而最后只有一个节点真正插入链表中了。

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。

可重入函数就是指在一个函数被多个执行流多次的重复调用的时候,不会改变我们的一起结果,也就是不会出现混乱,我们将这类函数就称为可重入函数,否则就是不可重入函数,我们的大部分STL函数都是不可重入的

2、volatile

volatile是一个关键字,他的意思是保持内存可见性,在我们的程序在被编译的时候,我们的编译器有可能会对我们的程序优化,比如:当我们的一个数据不会被运算的时候,它会被CPU放在一个寄存器内,而不会一直从内存中反复调用,而当我们在异常处理等其他情况的改变当前数据的时候,该数据被修改的值会写入内存,而CPU中的数据并不会去内存读取数据,所以就拿不到我们修改的值,而我们的volatile会明确告诉我们的编译器,每次取数据都需要去我们的内存中获取。

3、SIGCHLD信号

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

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

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

相关推荐
你撅嘴真丑3 小时前
第九章-数字三角形
算法
在路上看风景3 小时前
19. 成员初始化列表和初始化对象
c++
uesowys3 小时前
Apache Spark算法开发指导-One-vs-Rest classifier
人工智能·算法·spark
zmzb01033 小时前
C++课后习题训练记录Day98
开发语言·c++
ValhallaCoder3 小时前
hot100-二叉树I
数据结构·python·算法·二叉树
董董灿是个攻城狮3 小时前
AI 视觉连载1:像素
算法
wdfk_prog3 小时前
[Linux]学习笔记系列 -- [drivers][input]input
linux·笔记·学习
念风零壹4 小时前
C++ 内存避坑指南:如何用移动语义和智能指针解决“深拷贝”与“内存泄漏”
c++
智驱力人工智能4 小时前
小区高空抛物AI实时预警方案 筑牢社区头顶安全的实践 高空抛物检测 高空抛物监控安装教程 高空抛物误报率优化方案 高空抛物监控案例分享
人工智能·深度学习·opencv·算法·安全·yolo·边缘计算
七夜zippoe4 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann