文章目录


前言

上节课我们学习了信号产生到处理过程的现象以及信号的捕捉,这节课主要学习信号的保存。
我们需要熟练使用以下函数

#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中存储 信号的位图,而实际上,可不止有一个表(位图)。
Linux-信号2-LMLPHP

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号信号没有被捕捉!
Linux-信号2-LMLPHP
所以这里的结论就是如果我们将一个死循环进程的所有信号都捕捉,该进程仍然可以通过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;
}

Linux-信号2-LMLPHP
我们很清楚的看到第二位由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;
}

Linux-信号2-LMLPHP
我们看到这样的情况,可以得出看出9号信号和19号信号是不可屏蔽的!

所以这里的结论就是如果我们将一个死循环进程的所有信号都屏蔽,该进程仍然可以通过9号信号退出。


03-03 09:12