文章目录
- 前言
- 阻塞信号
- 1. 信号常见概念
- 2. 在内核中的表示
- 信号处理过程
- 3. sigset_t
- 4. 信号集操作函数
- sigprocmask
- sigpending
- 5. 测试与验证
- 实验一
- 实验二
- 实验三
前言
上篇文章(链接: _Linux 进程信号-基础篇)我们了解了信号的基础概念以及信号如何发送的。
阻塞信号
1. 信号常见概念
- 实际执行信号的处理动作称为信号递达(Delivery)
- 信号从产生到递达之间的状态,称为信号未决(Pending)。
- 进程可以选择阻塞 (Block )某个信号。
- 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
- 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
2. 在内核中的表示
上面一张图;我们知道信号OS需要三张位图才能对信号做到准确执行。
- block位图:表示信号是否被阻塞
- pending位图:表示信号未被处理,信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
- handler:表示递达;当信号未阻塞也未产生过(信号未被捕捉),当它递达时执行默认处理动作;就是跟我们上一篇文章学习到的信号捕捉的 函数—signal;我们知道它捕捉成功并不是立即生效。也就是信号捕捉其实就是把原来要执行的默认改成自定义动作。handler函数指针数组的下表代表的是信号的编号;里面内容((int)handler[signal]==0或1)
- 0:代表执行默认动作
- 1:代表执行忽略动作。
信号处理过程
- 一个信号处理过程(pending -> block -> handler)
- 当一个信号来时,先在pending位图中,由0置1;然后在在block位图中查找该信号是否被阻塞;
-
- 阻塞时信号就无法递达
-
- 未被阻塞时信号可以递达
-
3. sigset_t
从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。
4. 信号集操作函数
-
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。
#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初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
-
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
-
注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
-
sigismember函数用来测试参数signum 代表的信号是否已加入至参数set信号集里。
这四个函数都是成功返回0,出错返回-1。
sigprocmask
-
调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集);即对应这pcb位图中block(阻塞)那张位图。
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- 如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
- 如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
- 如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
-
假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
how的可选值 | 用法说明 |
---|---|
SIG_BLOCK | set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask l set |
SIG_UNBLOCK | set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于 mask=mask&~set |
SIG_SETMASK | 设置当前信号屏蔽字为set所指向的值,相当于mask=set |
- 如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
sigpending
#include <signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。
调用成功则返回0,出错则返回-1。
5. 测试与验证
实验一
-
1.如果我们对所有的信号都进行了自定义捕捉 — 我们是不是就写了一个不会被异常或者用户杀掉的送进程??
- 答案是:并不是,OS的设计者也考虑了! 例如:9号信号无法被捕捉。
-
代码 :
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void catchSig(int sigNum)
{
cout << "捕捉到一个信号..."
<< "编号--sig: " << sigNum << endl;
}
int main()
{
for (int i = 1; i <= 31; ++i)
signal(i, catchSig);
while (1)
sleep(1);
return 0;
}
- 当然了 除了9 号(杀死程序) 还有19 号(暂停程序)都是不可捕捉信号。
- 实验一结果图如下:
实验二
- 2.如果我们将2号信号block,并且不断的获取并打印当前进程的pending信号集,如果我们突然发送一个2号信号,我们就应该肉眼看到pending信号集中,有一个比特位0置1的过程;20s后我们解除对2号信号的block(阻塞)就有一个比特位1置10的过程。
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
using namespace std;
// 实验二
static void showPending(sigset_t &pending)
{
for (int i = 1; i <= 31; ++i)
{
if (sigismember(&pending, i))
cout << 1;
else
cout << 0;
}
cout << endl;
}
static void catchSig(int sigNum)
{
cout << "捕捉到一个信号..."
<< "编号--sig: " << sigNum << endl;
}
int main()
{
// 0. 方便测试,捕捉2号信号,不要退出
signal(2, catchSig);
// 1. 定义信号集对象
sigset_t bset, obset;
sigset_t pending;
// 2. 信号初始化
sigemptyset(&bset);
sigemptyset(&obset);
sigemptyset(&pending);
// 3. 添加要屏蔽的信号
sigaddset(&bset, 2);
// 4. 设置set到内核中对应的进程内部[默认情况进程不会对任何信号进行block]
int n = sigprocmask(SIG_BLOCK, &bset, &obset); // 返回屏蔽之前老的信号
assert(n == 0);
(void)n;
cout << "屏蔽2号信号成功..." << endl;
// 5. 重复打印当前进程的pending信号集
int count = 0;
while (true)
{
// 5.1 获取当前进程的pending信号集
sigpending(&pending);
// 5.2 显示pending信号集中的没有被递达的信号
showPending(pending);
sleep(1);
++count;
if (count == 20) // 20s后解除阻塞
{
int n = sigprocmask(SIG_UNBLOCK, &bset, &obset); // 返回屏蔽之前老的信号
assert(n == 0);
(void)n;
}
}
return 0;
}
- 实验二结果图如下:
实验三
-
3.如果我们对所有的都进行block(阻塞) — 我们是不是就写了一个不会被异常或者用户杀掉的进程??
-
答案是:并不是,OS的设计者也考虑了!
-
代码:
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
using namespace std;
static void showPending(sigset_t &pending)
{
for (int i = 1; i <= 31; ++i)
{
if (sigismember(&pending, i))
cout << 1;
else
cout << 0;
}
cout << endl;
}
static void blockSig(int sigNum)
{
sigset_t bset;
sigemptyset(&bset);
sigaddset(&bset, sigNum);
int n = sigprocmask(SIG_BLOCK, &bset, nullptr);
assert(n == 0);
(void)n;
}
// 实验三
int main()
{
for (int i = 1; i <= 31; ++i)
blockSig(i);
sigset_t pending;
sigemptyset(&pending);
while (true)
{
sigpending(&pending);
showPending(pending);
sleep(1);
}
return 0;
}
- 实验二结果图如下: