实际执行信号的处理动作称为信号递达(Delivery)
信号从产生到递达之间的状态,称为信号未决(Pending)
。
进程可以选择阻塞 (Block )
某个信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
每个信号都有两个标志位分别表示阻塞位图表(block)和未决位图表(pending),还有一个函数指针位图表(handler)表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号
一个信号被处理的流程:
简化:pending ----> block ------>handler
从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。
sigset_t
:基本上语言会给我们提供.h、.hpp且语言自定义类型,而sigset_t是OS提供的类型,sigset_t是位图结构
,但是不允许用户自己进行位操作,对应的操作位图的方法OS会提供接口。
sigset_t---->user是可以直接使用该类型和内置类型 && 自定义类型没有任何差别
sigset_t---->一定需要对应的系统接口来完成对应的功能,其中系统接口需要的参数,可能就包含了sigset_t定义的变量或者对象。
见一见系统接口:
参数解释
set
:输出型参数,可以获取当前进程的pending信号集
set
:传入要修改的目的信号集
oset
:输出型参数,获取当前进程旧的信号集
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。
如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
how:
编码:
1.如果我们对所有的信号都进行了自定义捕捉----->我们是不是就写了一个不会被异常或者用户杀掉的进程?
肯定不是的!
测试:
代码:
测试运行:
创建两个窗口:
窗口1:运行代码
窗口2:发送信号
那么我们能用信号杀掉进程吗?可以OS考虑到了这种情况,所以提供了9号信号,9号信号不可以被捕捉。
窗口2:发送9号信号
窗口:代码被终止
2.如果我们将2号信号,block并且不断的获取当前的pending信号集,如果我们突然发送一个2号信号,我们应该就可以肉眼看到pending信号集,有一个bit位由0----->1的过程。
代码:
#include
#include
#include
void showpending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig) == 1)
{
std::cout << "1";
}
else
{
std::cout << "0";
}
}
std::cout << std::endl;
}
int main()
{
// 0.捕捉2号信号
signal(SIGINT, SIG_DFL);
// 1.定义信号集对象
sigset_t bset;
sigset_t obset;
sigset_t pending;
// 2.初始化信号集
sigemptyset(&bset);
sigemptyset(&obset);
// 3.添加要进行屏蔽的信号
sigaddset(&bset, 2);
// 4.设置set到内核对应的进程内部(默认情况进程不会对任何信号进行block)
sigprocmask(SIG_BLOCK, &bset, &obset);
std::cout << "block 2 号信号成功...., pid: " << getpid() << std::endl;
// 5.重复打印当前进程的pending信号集
while (true)
{
sigpending(&pending);
showpending(pending);
sleep(1);
}
return 0;
}
下面我们看block解除后的效果。
代码:
#include
#include
#include
void showpending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig) == 1)
{
std::cout << "1";
}
else
{
std::cout << "0";
}
}
std::cout << std::endl;
}
int main()
{
// 0.捕捉2号信号
signal(SIGINT, SIG_DFL);
// 1.定义信号集对象
sigset_t bset;
sigset_t obset;
sigset_t pending;
// 2.初始化信号集
sigemptyset(&bset);
sigemptyset(&obset);
// 3.添加要进行屏蔽的信号
sigaddset(&bset, 2);
// 4.设置set到内核对应的进程内部(默认情况进程不会对任何信号进行block)
sigprocmask(SIG_BLOCK, &bset, &obset);
std::cout << "block 2 号信号成功...., pid: " << getpid() << std::endl;
// 5.重复打印当前进程的pending信号集
int count = 0;
while (true)
{
sigpending(&pending);
showpending(pending);
sleep(1);
if (count == 20)
{
std::cout << "解除对于2号信号的block" << std::endl;
int n = sigprocmask(SIG_SETMASK, &obset, nullptr);
if(n == -1)
{
perror("sigprocmask");
}
}
count++;
}
return 0;
}
运行结果
貌似没有一个接口用来设置pending位图表(因为所有的信号发送方式,都是修改pending位图的过程,这些过程由OS操作和维护)。
3.如果我们对进程的所有信号都进行block,那么我们是不是就写了一个不会被异常或者用户杀掉的进程
我们之前知道9号信号不会被阻塞,那么9号信号会被屏蔽吗?
代码:
#include
#include
#include
void showpending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig) == 1)
{
std::cout << "1";
}
else
{
std::cout << "0";
}
}
std::cout << std::endl;
}
int main()
{
sigset_t bset;
sigset_t obset;
sigset_t pending;
sigemptyset(&bset);
sigemptyset(&obset);
sigemptyset(&pending);
for(int sig = 1;sig <= 31;sig++)
{
sigaddset(&bset, sig);
}
sigprocmask(SIG_BLOCK, &bset, &obset);
while(true)
{
sigpending(&pending);
showpending(pending);
sleep(1);
}
return 0;
}
窗口2发送kill指令:
写个发送1-31的kill bash指令
#!/bin/bash
i=1
id=$(pidof mysignal)
while [ $i -le 31 ]
do
kill -$i $id
echo "kill -$i $id"
let i++
sleep 1
done
开始测试
我们看到遇到9号指令就停止了
更改bash跳过9号指令
#!/bin/bash
i=1
id=$(pidof mysignal)
while [ $i -le 31 ]
do
if [ $i -eq 9 ];then
let i++
continue
fi
kill -$i $id
echo "kill -$i $id"
let i++
sleep 1
done
测试:
我们看到遇到19号信号进程就被停止。
我们再更改bash屏蔽19号信号,看还有没有不会被block的信号。
#!/bin/bash
i=1
id=$(pidof mysignal)
while [ $i -le 31 ]
do
if [ $i -eq 9 ];then
let i++
continue
fi
if [ $i -eq 19 ];then
let i++
continue
fi
kill -$i $id
echo "kill -$i $id"
let i++
sleep 1
done