信号是进程之间事件异步通知的一种方式,属于软中断。
在上面这句话中,解释了信号这个词,但是又引入了两个新的概念:异步 ,软中断。
异步
异步 比较容易解释,我们日常生活中经常使用到同步这个词,而异步,正是和其相反的概念。不同人在同一时间做相同的事,即为同步 。那么不同人在同一时间做不同的事,即为异步。对于计算机,我们只需要把 "人" 换成 "进程" ,就可以解释计算机中的异步是何种意思了。
那么什么是软中断呢?这个涉及到的内容在我们学习完信号之后再讲解会比较容易理解,现阶段,我们先这样理解: "信号" 可以让进程 "中断" 当前行为,去执行其他行为。
信号产生
产生信号的东西叫做信号源,它可以是硬件,比如:键盘,鼠标等;也可以是软件,比如:系统调用函数等。
我们或已经知道,按下键盘的 ctrl+C ,可以终止一个进程,这就是硬件作为信号源的例子。这时我们引入一个问题:操作系统是怎么知道键盘有数据的?这个问题,我们留到讲中断时再讲解。还有硬件异常也会产生信号,如:除0,野指针。
我们也可以使用系统调用函数来向进程发送一个信号,最典型的就是kill 了,它可以向指定的一个进程发送指定的信号(自己给别人发),比如 kill -9 可以杀死进程。
raise ,给当前进程发指定的信号(自己给自己发)。
abort,使当前进程接收到信号而异常终止,并且会让进程产生核心转储(core dump),方便调试。
有哪些信号
信号有很多,我们可以在 Linux 中使用 kill -l 查看:

其中 1-31 号信号为标准信号 ,34-64号信号为实时信号。
对于上图,有三个点需要解释:
1.为什么没有 32,33 号?这两个信号被 NPTL 线程库预留内核自用,用户不能使用。
2.标准信号中的 9,19 号信号无法被捕捉。
3.kill 只能发送标准信号。
信号保存
对于信号保存,我们先引入三个概念:递达 ,未决 ,阻塞。
递达:实际执行信号的处理动作,有三种情况:自定义,默认,忽略。
未决:信号从产生到递达之间的状态。
阻塞:信号被保持在未决状态,即被阻塞。
进程的 task_struct 结构体中有三张表,它们相互配合完成对信号的保存与识别任务。
、
信号的号数 -1 就是对应数据结构的下标。block(阻塞信号屏蔽字) 表示信号是否被阻塞,pending(未决信号位图) 表示是否接收到该信号,handler(信号处理函数指针数组) 表是对该信号的处理方法。
block 和 pending 都是位图。
信号处理
前面说到递达时,说到信号处理的三种情况:自定义,默认,忽略。我们从简单到复杂来谈谈:
默认 (SIG_DFL):默认就是内核预设的行为,比如 2 号信号是默认杀死进程。
忽略 (SIG_IGN):进程接收到信号后直接丢弃,完全无响应。
自定义:使用函数捕获信号,暂停主程序,跳转到你自己定义的 handler 执行相应任务。
进程处理信号的流程简单来说就是:进程接收到信号,对应 pending 表的位置由 0 变 1,如果 block 对应位置为0,则可以执行 handler 对应位置的函数,否则等待。
对于自定义捕获信号,有两个函数:
signal:

sigaction:
signal 的第一个参数是具体信号的宏,第二个参数是你自定义的函数。它简单,易用,但不稳定。
sigaction 就比较可靠了,第一个函数依然是具体信号的宏,但是第二个和第三个参数都是一个结构体指针,这是什么呢?
首先我们得知道那个结构体是什么:

这是一个用来存放 "信号处理方案" 的容器,其实进程对信号捕获再处理,都是通过这个容器来实现的,所以 signal 本质是对 sigaction 的简易封装。
对于 sigaction 我们创建一个 struct sigaction 对象,修改里面的成员来对信号做出处理,两个结构体指针参数,一个指向新修改出的结构体,另一个指向旧的结构体,"备份" 原来的处理方式。
