1. 阻塞信号
1.1 信号其他相关常见概念
- 实际执行信号的处理过程称为信号递达。
- 信号从产生到递达之间的状态称为信号未决。
- 进程可以选择阻塞(Block)某个信号。
- 被阻塞的信号保持在未决状态,直至进程解除对该信号的阻塞,才执行递达操作。
- 阻塞和忽略是不同的,阻塞是一种状态,忽略是一种处理信号的方法
1.2 信号在内核中的表示示意图
每个信号都有两个标志位,表示阻塞(block)和未决(pending),还有一个函数指针以表示信号的处理动作。
1.3 信号处理的过程
- 当进程接受到一个信号(2号信号)时,在其pcb结构体内的“信号标记图”中的pending位图中对应的比特位就会由0置1。
- 当人为设置了阻塞信号(2号信号和3号信号)时,“信号标记图”中的block位图中对应的比特位就会由0置1。
- 当进程收到信号,并且该信号并未被阻塞时,才会去找handler中对应的函数指针。SIG_DFL和SIG_IGN是两个宏。
(int)handler[signal]==0 ;//执行默认动作 处理完毕
(int)handler[signal]==1;//执行忽略动作,处理完毕
如果上述两个都不满足,才会去调用函数。
handler[signal]();
2. sigset_t 类型
由于信号需要的是标记位哦,非0即1。操作系统给了我们一个新的数据类型sigset_t,用户可以直接使用该类型与自定义类型和内置类型没有区别。它本质上就是一个位图,但是不允许用户进行位操作(~ / & / |),添加/修改位图的值需要通过OS系统提供的接口。
一个sigset_t 类型被称为信号集。
3.信号集操作函数
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
- sigemptyset() 初始化该信号集,全部置0。
- sigfillset() 初始化该信号集,全部置1。
- sigaddset() 将对应信号编号的比特位置1。
- sigdelset() 将对应信号编号的比特位置0 。
- sigismember() 查看在set中 signo编号信号状态是0还是1 。
3.1 sigprocmask
调用sigprocmask可以读取或者更改进程的阻塞信号集
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);//记得传 &set &oset
返回值:若成功则为0,若出错则为-1
set : 是我们自己通过sigaddset() 和 sigdelset() 设置好的 希望操作的信号形成的信号集
oset : 是一个输出型参数。方便我们需要恢复之前的信号屏蔽集。
how参数的选项及说明
SIG_BLOCK | set包含了我们希望添加到阻塞信号集的信号 mask=mask|set |
SIG_UNBLOCK | set包含了我们希望从阻塞信号集解除的信号 mask=mask&~mask |
SIG_SETMASK | 设置当前信号屏蔽字为set所指向的值,相当 于mask=set |
使用
int main()
{
sigset_t bset,obset;
sigset_t pending;
sigemptyset(&bset);
sigemptyset(&obset);
sigaddset(&bset,2);//添加想要操作的(2号)信号
int n=sigprocmask(SIG_BLOCK,&bset);//将2号信号设为阻塞状态
//obset里面保存的没修改之前的信号集
assert(n==0); //不为0直接报错
(void)n;
int m=sigprocmask(SIG_SETMASK,&obset,nullptr);
//因为obset 只设置了2号信号 所以可以接着用以解除2号信号的屏蔽
assert(m==0);
(void)m;
return 0;
}
3.2 sigpending
#include <signal.h>
int sigpending(sigset_t* set);
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
static void showPending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig))
std::cout << "1";
else
std::cout << "0";
}
std::cout << std::endl;
}
int main()
{
sigset_t pending;
sigemptyset(&pending);
sigaddset(&bset, 2);
int n = sigprocmask(SIG_BLOCK, &bset, &obset);
assert(n == 0);
(void)n;
while (true)
{
//获取当前进程的pending信号集
sigpending(&pending);
showPending(pending);
sleep(1);
}
return 0;
}
4.验证问题
4.1 对所有信号进行捕捉 如何终止程序
写一个for循环对每个信号都进行捕捉。
void catchSig(int signum)
{
std::cout << "获取一个信号: " << signum << std::endl;
}
int main()
{
for(int sig = 1; sig <= 31; sig++) signal(sig, catchSig);
while(true) sleep(1);
return 0;
}
结论 9号信号无法被捕捉
Bash: kill -9 pid
依然可以杀死程序
4.2 观察pending信号集的变化
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
//请放到Linux环境下执行代码
static void handler(int signum)
{
std::cout << "捕捉 信号: " << signum << std::endl;
// 不要终止进程,exit
}
static void showPending(sigset_t& pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig))
std::cout << "1";
else
std::cout << "0";
}
std::cout << std::endl;
}
int main()
{
//捕捉2号信号
signal(2, handler);
sigset_t bset, obset,pending;
//初始化
sigemptyset(&bset);
sigemptyset(&obset);
sigemptyset(&pending);
//添加要进行屏蔽的信号
sigaddset(&bset, 2 /*sigint*/);
//设置set到内核中
int n = sigprocmask(SIG_BLOCK, &bset, &obset);
assert(n == 0);
(void)n;
std::cout << "block 2 号信号成功...., pid: " << getpid() << std::endl;
// 打印当前进程的pending
int count = 0;
while (true)
{
//获取pending信号集
sigpending(&pending);
//显示pending信号集中
showpending(pending);
sleep(1);
count++;
if (count == 20)
{
std::cout << "解除对于2号信号的block" << std::endl;
int n = sigprocmask(SIG_SETMASK, &obset, nullptr);
assert(n == 0);
(void)n;
}
}
}
运行结果
4.3 阻塞所有信号 如何终止程序
结论 无法阻塞9号信号和19号信号