(。・∀・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~https://blog.csdn.net/ky233?type=blog
点个关注不迷路⌯'▾'⌯
目录
一、阻塞信号
1.信号递达、未决、阻塞
2.内核表示图
3.sigset_t
4.sigpending
5.sigprocmask
二、验证问题
1.问题一
2.问题二
3.问题三
一、阻塞信号
1.信号递达、未决、阻塞
-
实际执行信号的处理动作称为信号递达(Delivery)。
- 信号从产生到递达之间的状态,称为信号未决(Pending)。
- 进程可以选择阻塞 (Block )某个信号。
- 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
- 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
2.内核表示图
在内核中其实一共有两个位图结构,第二个就是我们的pending位图,也就是OS修改的位图结构,第三个则是代表的信号,是一个函数指针数组,里边存放的是所对应的函数地址
而在handler里面会先判断是否是等0还是1,0则执行默认的动作,1则直接忽略的动作,都不是才会执行我们自己写的动作!
第一个block则代表的是我们的阻塞位图,OS先把对应的信号写入pending里,然后回来查看,block里面是否阻塞,如果阻塞则不进行调用,等到不阻塞时候才回去handler里进行下一步操作!
3.sigset_t
这是一个位图结构,但是不允许用户自己进行位操作,所以OS给我们提供了对应的操作方法
用户可以直接使用该类型,和用内置类型以及自定义类型是没有任何差别的
每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号 的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。
总的来说就是用来表示block和pending的
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);
上面就是OS所提供的五个可以对位图本身修改的接口
- 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有 效信号。
- 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系 统支持的所有信号。
- 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的 状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号
- sigismember:最后一个是判定一个信号时候再该信号集之中
4.sigpending
int sigpending(sigset_t *set)
通过该函数可以获取当前调用进程的pending信号集,也就是把操作系统内核中的数据拿给用户
- 返回值:成功为0,失败为-1
5.sigprocmask
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset)
检查并更改我们的阻塞信号集
- 参数一:设定特定的操作
- 参数二:设定特定的位图
- 参数三:输出型参数,返回老的信号屏蔽图(阻塞位图block),如果需要的话
- 返回值:若成功则为0,若出错则为-1
how:
- SIG_BLOCK:set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
- SIG_UNBLOCK:set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
- SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask=set
二、验证问题
1.问题一
如果我们对所有的信号都进行了自定义不做,我们是不是就写了一个不会被一场或者用户杀掉的进程?
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void catchSig(int signum)
{
cout << "获得了一个信号:" << signum << endl;
}
int main()
{
for (int i = 1; i <= 31; i++)
{
signal(i, catchSig);
}
while (1)
{
sleep(1);
}
return 0;
}
虽然我们看到的好像就是这样,可其实并不是,OS设计者也考虑过,我们的9号信号(SIGKILL)是不可以被捕捉的!
2.问题二
如果我们将二号信号阻塞,并且不断地获取并打印当前进程的pending信号,如果我们突然发送一个二号信号,我们应该肉眼看到pending信号集中,有一个比特位从0变为1
static void shouPending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
int main()
{
// 定义信号集
sigset_t bset, obset;
sigset_t pending;
// 初始化
sigemptyset(&bset);
sigemptyset(&obset);
sigemptyset(&pending);
// 添加要进行屏蔽的信号
sigaddset(&bset, 2);
// 设置set到内核中
int n = sigprocmask(SIG_BLOCK, &bset, &obset);
assert(n == 0);
cout << "2号信号以阻塞" << endl;
(void)n;
// 重复打印当前的pending信号集
while (1)
{
// 获取当前进程的pending信号集
sigpending(&pending);
// 显示pending中的没有被递达的信号
shouPending(pending);
sleep(1);
}
return 0;
}
首先我们看到当前进程的pending位图都是0,这是因为虽然我们阻塞了,但是不代表这个进程收到了2号信号,当我们发完之后,确实如我们所说,有一个比特位由0变1了
可是如果我们要解除2号信号的阻塞呢?那我们的pending信号集就应该从1变成0!
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
using namespace std;
static void shouPending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
int main()
{
// 定义信号集
sigset_t bset, obset;
sigset_t pending;
// 初始化
sigemptyset(&bset);
sigemptyset(&obset);
sigemptyset(&pending);
// 添加要进行屏蔽的信号
sigaddset(&bset, 2);
// 设置set到内核中
int n = sigprocmask(SIG_BLOCK, &bset, &obset);
assert(n == 0);
cout << "2号信号以阻塞" << endl;
(void)n;
// 重复打印当前的pending信号集
int count = 0;
while (1)
{
// 获取当前进程的pending信号集
sigpending(&pending);
// 显示pending中的没有被递达的信号
shouPending(pending);
sleep(2);
count++;
if (count == 10)
{
int n = sigprocmask(SIG_SETMASK, &obset, nullptr);
assert(n == 0);
(void)n;
cout << "解除2号信号的阻塞" << endl;
}
}
return 0;
}
可是我们并没有看到从1到0,这是因为进程直接终止了,在默认情况下,解除阻塞2号信号直接递达了,需要捕捉一下!
static void shouPending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
static void handler(int signum)
{
cout<<"捕捉信号:"<<signum<<endl;
}
int main()
{
//测试捕捉
signal(2,handler);
// 定义信号集
sigset_t bset, obset;
sigset_t pending;
// 初始化
sigemptyset(&bset);
sigemptyset(&obset);
sigemptyset(&pending);
// 添加要进行屏蔽的信号
sigaddset(&bset, 2);
// 设置set到内核中
int n = sigprocmask(SIG_BLOCK, &bset, &obset);
assert(n == 0);
cout << "2号信号以阻塞" << endl;
(void)n;
// 重复打印当前的pending信号集
int count = 0;
while (1)
{
// 获取当前进程的pending信号集
sigpending(&pending);
// 显示pending中的没有被递达的信号
shouPending(pending);
sleep(2);
count++;
if (count == 10)
{
int n = sigprocmask(SIG_SETMASK, &obset, nullptr);
assert(n == 0);
(void)n;
cout << "解除2号信号的阻塞" << endl;
}
}
return 0;
}
这时我们就看到了当前进程的pending信号集从1变成了0!
还有一个小问题,貌似没有一个接口用来设置我们的pending位图,这是因为我们的所有发送信号的方式,都是修改pending位图的过程
3.问题三
如果我们对所有的信号都阻塞,我们是不是就写了一个不会被一场或者用户杀掉的进程?
static void shouPending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig))
{
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 sig=1;sig<=31;sig++)
{
blockSig(sig);
}
sigset_t pending;
while(1)
{
sigpending(&pending);
shouPending(pending);
sleep(1);
}
return 0;
}
和第一个问题是一样的,9号信号同样也不可以被阻塞!
同样的还有我们的19号暂停命令和20号命令!
所以最终结论,无论是9号信号就不可被捕捉和屏蔽的