1. 信号处理机制的 “三张表”
kill -l
:前 31 个信号为系统标准信号。
block
pending
handler
三张表保存在每个进程的进程控制块 —— pcb
中,它们分别对应了某一信号的阻塞状态、待处理状态以及处理方式。
-
block
:通过sigset_t
类型实现,是一个位字段集合,每一位对应一个信号;如果某一位被设置,则对应的信号会被阻塞;信号的阻塞状态 与 该信号是否被发送到进程 无关 。 -
pending
:与block
相同,通过sigset_t
类型实现,用于记录 已经发送给进程但未被处理 的信号。 -
handler
:函数指针数组,指向各个信号的处理方法(函数)。
2. 介绍几个信号处理的系统调用
-
sigemptyset
:用于初始化一个信号集为空,即将信号集中的所有信号位都清零。创建一个
sigset_t
类型的变量,在对该变量进行各种信号处理操作之前,先要使用 sigemptyset 对该变量初始化。
sigset_t sig;
sigemptyset(&sig); // 对 sig 初始化, success -> 0, fail -> -1
sigaddset
:用于在一个已存在的信号集中添加信号。
sigaddset(&sig, SIGINT); // success return 0, fail return -1
sigprocmask
:用于更改当前进程的信号掩码,简单来说即设置哪些信号被阻塞。
sigprocmask(int how, const sigset_t* signal, sigset_t* old_signal);
how 的处理方式, SIG_BLOCK
SIG_UNBLOCK
SIG_SETMASK
SIG_BLOCK : 设置新的信号掩码,同时保留旧的信号掩码
SIG_UNBLOCK : 取笑新信号掩码中的信号阻塞,并保留旧的信号掩码
SIG_SETMASK : 设置新的信号掩码,并丢弃旧的信号掩码
sigset_t oldsig;
sigemptyset(&oldsig);
sigprocmask(SIG_SETMASK, &sig, &oldsig);
sigpending
:用于获取当前进程中待处理的信号集。
sigset_t pending;
sigemptyset(&pending);
sigpending(&pending);
sigismember
:用于测试一个信号是否在一个信号集中,存在返回 1,否则返回 0。
sigismember(&pending, SIGINT);
3. demo —— 示范案例
demo 中主要完成以下四个动作:
- 屏蔽 2 号信号 —— SIGINT
- 获取当前进程的 pending 表
- 打印 pending 表
- 取消对 2 号信号的屏蔽
其中,2.
和 3.
是 “同时” 且 重复 进行的,意味着将出现这样一个现象:当前进程在接收 2 号信号之前,pending 表 2 号信号对应位置为 0;向当前进程发生 2 号信号后,pending 表 2 号信号位置被设置。
以及,取消对 SIGINT 的屏蔽后,2 号信号会被立即递达,进程结束。
1) 屏蔽 2 号信号
#include <iostream>
using namespace std;
#include <signal.h>
int main()
{
// 1. 屏蔽 2 号信号
sigset_t block;
sigset_t old_block;
sigemptyset(&block);
sigemptyset(&old_block);
sigaddset(&block, SIGINT);
sigprocmask(SIG_SETMASK, &block, &old_block);
cout << "process pid: " << getpid() << endl;
cout << "block SIGINT success ..." << endl;
return 0;
}
2) 获取当前进程的 pending 表 | 3) 打印 pending 表
#include <unistd.h>
void PrintSig(sigset_t pending)
{
cout << "process pending: ";
for (int i = 31; i >= 1; i--)
{
if (sigismember(&pending, i))
cout << "1";
else
cout << "0";
}
cout << endl;
}
int main()
{
// ...
while (1)
{
// 2. 获取当前 pending 表
sigset_t pending;
sigemptyset(&pending);
sigpending(&pending);
// 3. 打印 pending 表
PrintSig(pending);
sleep(1);
}
}
将这部分代码先运行起来,并对该进程发送一个 SIGINT 信号,观察现象:
当前进程(3988092)pending 表的 2 号信号对应位置被设置,但进程没有退出!
4) 取消对 2 号信号的屏蔽
int main()
{
while (1) {
// ...
// 4. 取消对 2 号信号对屏蔽
++cnt;
if (cnt == 20)
{
sigprocmask(SIG_UNBLOCK, &block, &old_block);
}
}
}
为了使 2 号信号恢复且被递达后的观察效果更明显,我们对 PrintSig 函数 做一下优化,再 对 2 号信号的处理程序自定义。
介绍一个新的系统调用接口:signal
signal()
允许我们为特定信号指定一个处理函数 —— handler()
是一个参数类型为 int
返回值为 空
的函数, 当信号到达时,该函数将被调用。
#include <iostream>
using namespace std;
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void PrintSig(sigset_t pending, int count)
{
cout << "process pending: ";
for (int i = 31; i >= 1; i--)
{
if (sigismember(&pending, i))
cout << "1";
else
cout << "0";
}
cout << " , cnt: " << count << endl;
}
void handler(int signal)
{
cout << "2 号信号被递达" << endl;
exit(1);
}
int main()
{
signal(SIGINT, handler); // 对 SIGINT 的处理方式重定义
// 1. 屏蔽 2 号信号
sigset_t block;
sigset_t old_block;
sigemptyset(&block);
sigemptyset(&old_block);
sigaddset(&block, SIGINT);
sigprocmask(SIG_SETMASK, &block, &old_block);
cout << "process pid: " << getpid() << endl;
cout << "block SIGINT success ..." << endl;
int cnt = 0;
while (1)
{
// 2. 获取当前 pending 表
sigset_t pending;
sigemptyset(&pending);
sigpending(&pending);
// 3. 打印 pending 表
PrintSig(pending, cnt);
sleep(1);
// 4. 取消对 2 号信号对屏蔽
++cnt;
if (cnt == 20)
{
sigprocmask(SIG_UNBLOCK, &block, &old_block);
}
}
return 0;
}
进程运行 20 秒后,SIGINT 的屏蔽被取消,SIGINT 被递达,进程终止!
4. 对 SIGKILL 和 SIGSTOP 的验证
pid 为 “当前进程” 的 pid
while :; do for i in {1..8}; do kill -$i pid; done; sleep 1; done # 对进程发送 1~8 号信号
while :; do for i in {10..18}; do kill -$i pid; done; sleep 1; done # 对进程发送 10~18 号信号
while :; do for i in {20..31}; do kill -$i pid; done; sleep 1; done # 对进程发送 20~31 号信号
对原代码做出修改:使用 for 循环,让进程阻塞 1~31 号信号;
随后,在另一窗口使用 bash 脚本,分别向当前进程发送 1~8 、 10~18 、 20~31 号信号,观察进程的 pending 表;
在过程中,对当前进程发送 9 号信号 和 19 号信号,观察现象。
#include <iostream>
using namespace std;
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void PrintSig(sigset_t pending)
{
cout << "process pending: ";
for (int i = 31; i >= 1; i--)
{
if (sigismember(&pending, i))
cout << "1";
else
cout << "0";
}
cout << endl;
}
void handler(int signal)
{
cout << "2 号信号被递达" << endl;
exit(1);
}
int main()
{
// 1. 屏蔽 1~31 号信号
sigset_t block;
sigset_t old_block;
sigemptyset(&block);
sigemptyset(&old_block);
for (int i = 1; i <= 31; i++)
{
sigaddset(&block, i);
}
sigprocmask(SIG_SETMASK, &block, &old_block);
cout << "process pid: " << getpid() << endl;
cout << "block signal success ..." << endl;
int cnt = 0;
while (1)
{
// 2. 获取当前 pending 表
sigset_t pending;
sigemptyset(&pending);
sigpending(&pending);
// 3. 打印 pending 表
PrintSig(pending);
sleep(2);
}
return 0;
}
- 对当前进程发送 1~8 号信号 和 9 号信号
对当前进程发送 1~8 号信号,进程没有结束。
对当前进程发送 9 号信号,进程终止!
- 对当前进程发送 10~18 号信号 和 19 号信号
对当前进程发送 10~18 号信号,进程没有结束。
对当前进程发送 19 号信号,进程终止!
- 对当前进程发送 20~31 号信号
对当前进程发送 20~31 号信号,进程没有结束。