发送信号
- 内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程
- 发送信号可以是如下原因之一:
- 内核检测到一个系统事件如除零错误 (SIGFPE)或者子进程终止 (SIGCHLD)
- 一个进程调用了 kill 系统调用,显式地请求内核发送一个信号到目的进程
- 一个进程可以发送信号给它自己
linux中几种如何向进程发送信号的机制
用 /bin/kill 程序发送信号
图中的这个命令是向进程号为 15213 的进程发送信号 9,信号 9 表示杀死进程。这条命令执行完毕后,该进程将终止运行

我们再来看另外一条命令,其中 -15213 表示向 进程组 15213 发送信号,这个信号 9 将会发送到该进程组中的每一个进程中

从键盘发送信号

输入 ctrl-c / ctrl-z 会导致内核发送一个 SIGINT / SIGTSTP 信号到前台进程组中的每个作业。
SIGINT - 默认情况是终止前台作业。
SIGTSTP - 默认情况是停止(挂起)前台作业。
在键盘上输入 control c 后,会发送一个中断信号到前台进程组中的所有进程中
前台进程组

当我们在 shell 中输入图中的命令时, shell 会创建一个前台作业,这个前台作业是由两个进程组成的,其中程序 ls 可以看成一个进程,程序 sort 可以看成另外一个进程。这两个进程是通过管道连接起来的。程序 ls 的输出结果将会作为程序 sort 的输入,shell 为每个作业创建了一个独立的进程组

图中展示了有一个前台作业和两个后台作业的 shell。前台作业的父进程 PID 为 20,进程组 ID 也为 20。父进程创建的两个子进程的 PID 分别为 21 和 22,这两个进程的进程组 ID 都是 20。在任何时刻最多有一个前台作业和 0 个或多个后台作业。当键盘上输入 control c 键时,默认是终止前台作业。类似的,输入 Ctrl 加 z 会挂起前台作业
用 kill 函数发送信号
进程可以通过调用函数 kill 发送信号给其他进程,当然也包括给自己发送信号函数 kill,具体如图所示

- 如果参数 PID 的值大于 0,那么函数 kill 发送信号给进程 PID
- 大于 0,杀进程
- 如果参数 PID 等于 0,那么函数 kill 发送信号给调用进程所在进程组中的所有进程,当然也包括调用进程自己
- 等于 0,杀自己也杀组
- 如果 PID 的值小于 0,函数 kill 发送信号给进程组 PID 中的每个进程。注意,这里的进程组 ID 是 PID 的绝对值
- 小于 0,指定杀哪个组
用 alerm 函数发送信号
unistd.h 中的 unsigned int alarm(unsigned int secs);
参数 secs 表示函数 alarm 安排内核在 secs 秒后发送一个信号给调用进程,如果 seconds 为 0,就不会调用新的闹钟了
接收信号
当目的进程被内核强迫以某种方式对发送来的信号做出反应时,它就接收了信号
对信号做出的反应

- 忽略这个信号
- 终止进程
- 通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号
- 类似于响应异步中断而调用的硬件异常处理程序
当内核把进程 p 从内核模式切换到用户模式时,此时会检查进程p的未阻塞状态的待处理的信号集合
- 如果这个集合为空,也就是说明没有statusp,那么内核将控制传递到进程 p 的逻辑控制流中的下一条指令。
- 如果集合是非空的:
- 那么内核选择集合中的一个信号 k 强制进程 p 接受信号 k
- 接收信号会触发控制转移到信号处理程序
- 在信号处理程序完成处理之后,它将控制返还给被中断的程序
每个信号都有一个预定义的对进程的默认行为(4种行为)



让进程终止
收到信号 SigKill 后接收进程终止运行
让进程终止并转储内存
转储内存的意思是把代码和数据的内存镜像写到磁盘上
让进程挂起 (stopped)
不过被挂起的进程还能够被 SigContinue 信号重启
让进程可以直接忽略该信号
进程忽略该信号,比如信号 SigChld 就是可以忽略的
信号处理程序捕获信号
信号处理程序 signal 函数


信号处理流程
调用信号处理程序
只要进程接收到类型为 signum 的信号就会调用信号处理程序
设置信号处理程序
使用 signal 函数,设定指定信号的处理程序(地址),从而改变默认行为
捕获信号
调用信号处理程序称为捕获信号
处理信号
执行信号处理程序称为处理信号
信号处理程序返回
当信号处理程序执行return时,控制会传递到控制流中被信号接收所中断的指令处
待处理信号

一个发出而没有被接收的信号叫做待处理信号,也称为未决信号。在任何时刻,非实时信号的一种类型的信号最多只会有一个待处理信号
不可靠信号不会排队等待
如果一个进程有一个类型为 k 的待处理信号,那么任何接下来发送到这个进程的类型为 k 的信号都不会排队等待,他们只会被简单的丢弃,所以一个待处理信号最多只能被接受一次。需要特别注意的是,信号处理程序可以被其他信号处理程序中断
阻塞信号
- 一个进程可以选择阻塞接受某种信号
- 阻塞的信号仍可以被发送,但不会被接收,直到进程取消对该信号的阻塞
隐式阻塞机制
- 内核默认阻塞与当前正在处理信号类型相同的待处理信号
- 如,一个 SIGINT 信号处理程序不能被另一个 SIGINT 信号中断 (此时另一个 SIGINT 信号被阻塞)
显示阻塞和解除阻塞机制
sigprocmask函数及其辅助函数可以明确地阻塞/解除阻塞
选定的信号:SIG_BLOCK/UNBLOCK/SETMASK- 辅助函数
- sigemptyset – 初始化 set 为空集合
- sigfillset – 把每个信号都添加到 set 中
- sigaddset – 把指定的信号 signum 添加到 set
- sigdelset – 从 set 中删除指定的信号 signum
作为并发流的信号处理程序
信号处理的注意事项
- 待处理信号是不排队的
- 对每种信号类型,pending 位向量只有1位与之对应
- 因此每种类型最多只能有1个未处理信号
- 如果存在一个未处理的信号 k,那表明至少有一个 k 信号到达了,实际上可能不止一个
- 不能用信号来对其他进程中发生的事件计数,如子程序的终止
信号接收处理的例子
嵌套的信号处理程序
信号处理程序可以被其他信号处理程序中断

当主程序接受到信号 s 时,该信号会中断主程序的执行,将控制转移到处理程序 s。当处理程序 s 在运行时,程序又接到了信号 t,该信号会中断。处理程序 s 将控制转移到处理程序 t,当 t 返回时,程序 s 从它被中断的地方继续执行。 s 执行完毕,返回后,控制传送回主程序,从它被中断的地方继续执行
