文章目录
- 前言
- 一、信号是如何保存的?
- 二、学习步骤
前言
上节课我们学习了信号产生到处理过程的现象以及信号的捕捉,这节课主要学习信号的保存。
我们需要熟练使用以下函数
#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);
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
int sigpending(sigset_t *set);
一、信号是如何保存的?
上节课我们说过,信号是在进程的PCB中存储 信号的位图,而实际上,可不止有一个表(位图)。
block信号集是用以保存被block阻塞的信号,一般被称为信号屏蔽字(阻塞信号集),pending信号集就是我们上节课所说的那个收到信号即相对位由0置1的位图,一般被称为未决信号集。
对于阻塞,这是提前预设好的,目的是为了让该进程屏蔽该信号,所以如果一个信号被阻塞,而又收到了该信号,虽然pending信号集由0置1,但是不进行delivery(信号递达:信号递达操作即信号的处理)操作。
而这两个信号集,在我们看来理解其实就是位图,但是OS将他们封装成了自定义类型sigset_t,通过这样的封装,对于我们用户就无法随心所欲地更改其信号集的数据,只能通过OS提供给我的系统接口函数。
现在来认识一下系统交给我们的一些接口函数。
int sigemptyset(sigset_t *set);
函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有
效信号。
成功返回0,出错返回-1
int sigfillset(sigset_t *set);
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系
统支持的所有信号。
成功返回0,出错返回-1
int sigaddset (sigset_t *set, int signo);
函数sigaddset用于将set所指向的信号集的signo信号置位。
成功返回0,出错返回-1
int sigdelset(sigset_t *set, int signo);
函数sigdelset用于将set所指向的信号集的signo信号置零。
成功返回0,出错返回-1
int sigismember(const sigset_t *set, int signo);
函数sigismember用于判断set所指向信号集的signo信号是否为1。
是则返回1,否则为0。
int sigpending(sigset_t *set);
函数sigpending用于读取当前进程的未决信号集,通过set参数传出。
调用成功则返回0,出错则返回-1。
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
函数sigprocmask用于更改block信号集,参数how为选项,该函数提供三种选项
1.SIG_BLOCK
该选项是希望在目前的信号屏蔽字添加set信号集的有效信号,相当于mask=mask|set
2.SIG_UNBLOCK
该选项是希望在目前的信号屏蔽字去除set信号集的有效信号,相当于mask=mask&(~set)
3.SIG_SETMASK
该选项就比较暴力些,相当于mask=set
而参数oset为输出型参数,用于保存老信号集。
需要注意的,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的
状态。
二、学习步骤
我们提出三个问题并对问题进行一一解答来进行我们的学习
问题1. 如果我们将一个死循环进程的所有信号都捕捉,是不是该进程无法被退出?
问题2. 如果我们将一个信号block阻塞,并发送对应信号,pending表的对应该信号bit位是不是由0置1?
问题3. 如果我们将一个死循环进程的所有信号都阻塞,是不是该进程也无法被退出?
1.解答问题1
代码如下(示例):
#include<stdio.h>
#include<unistd.h>
#include<iostream>
#include<signal.h>
void catchSig(int signum)
{
std::cout << "pid " << getpid() <<" :捕捉到信号 " << signum << std::endl;
}
int main()
{
for(int i = 1; i <= 31 ; i++)
{
signal(i,catchSig);
}
while(1)
{
std::cout << "pid " << getpid() << " :进程运行中... " <<std::endl;
sleep(1);
}
return 0;
}
这个程序会在运行时捕捉1-31号的所有信号,那么是不是这个进程就无法退出了呢?
我们通过命令行依次输入kill命令来进行对该进程发送信号,发现在发送9号信号的时候,进程还是被终止了,说明进程的9号信号没有被捕捉!
所以这里的结论就是如果我们将一个死循环进程的所有信号都捕捉,该进程仍然可以通过9号信号退出,因为设计OS的人知道,如果有恶意程序真的将所有信号都可以捕捉,那么后果是十分严重的!
2.解答问题2
代码如下(示例):
#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <signal.h>
#include <assert.h>
void ShowSet(sigset_t &set)
{
for (int i = 1; i <= 31; i++)
{
// 通过sigismember来打印我们的pending信号集
std::cout << sigismember(&set, i);
}
std::cout << std::endl;
}
int main()
{
sigset_t set;
sigset_t block;
sigset_t oset;
// 1.进行初始化
sigemptyset(&set);
sigemptyset(&block);
sigemptyset(&oset);
// 2.阻塞2号信号
sigaddset(&block, 2);
sigprocmask(SIG_BLOCK, &block, &oset);
std::cout << "pid " << getpid() << std::endl;
// 3.循环打印未决信号集
while (1)
{
// 3.1 获取当前的未决信号集
sigpending(&set);
ShowSet(set);
sleep(1);
}
return 0;
}
我们很清楚的看到第二位由0置1了。
所以结论就是如果我们将一个信号block阻塞,并发送对应信号,pending表的对应该信号bit位是由0置1。
3.解答问题3
代码如下(示例):
#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <signal.h>
#include <assert.h>
void ShowSet(sigset_t &set)
{
for (int i = 1; i <= 31; i++)
{
// 通过sigismember来打印我们的pending信号集
std::cout << sigismember(&set, i);
}
std::cout << std::endl;
}
void SendSig(int signum)
{
if ((signum != 19) &&(signum != 9) && (signum <= 31))
raise(signum);
}
int main()
{
sigset_t set;
sigset_t block;
sigset_t oset;
// 1.进行初始化
sigemptyset(&set);
sigemptyset(&block);
sigemptyset(&oset);
// 2.阻塞所有信号
for (int i = 1; i <= 31; i++)
{
sigaddset(&block, i);
}
sigprocmask(SIG_SETMASK, &block, &oset);
// 3.循环打印未决信号集
int count = 1;
while (1)
{
// 3.1 获取当前的未决信号集
sigpending(&set);
ShowSet(set);
SendSig(count++);
sleep(1);
}
return 0;
}
我们看到这样的情况,可以得出看出9号信号和19号信号是不可屏蔽的!
所以这里的结论就是如果我们将一个死循环进程的所有信号都屏蔽,该进程仍然可以通过9号信号退出。