信号处理: Block Pending Handler 与 SIGKILL/SIGSTOP 实验

1. 信号处理机制的 "三张表"

kill -l :前 31 个信号为系统标准信号。

block pending handler 三张表保存在每个进程的进程控制块 ------ pcb 中,它们分别对应了某一信号的阻塞状态、待处理状态以及处理方式。

  • block :通过 sigset_t 类型实现,是一个位字段集合,每一位对应一个信号;如果某一位被设置,则对应的信号会被阻塞;信号的阻塞状态 与 该信号是否被发送到进程 无关

  • pending :与 block 相同,通过 sigset_t 类型实现,用于记录 已经发送给进程但未被处理 的信号。

  • handler :函数指针数组,指向各个信号的处理方法(函数)。

2. 介绍几个信号处理的系统调用
  • sigemptyset :用于初始化一个信号集为空,即将信号集中的所有信号位都清零。

    创建一个 sigset_t 类型的变量,在对该变量进行各种信号处理操作之前,先要使用 sigemptyset 对该变量初始化。

cpp 复制代码
	sigset_t sig;
    sigemptyset(&sig); // 对 sig 初始化, success -> 0, fail -> -1
  • sigaddset :用于在一个已存在的信号集中添加信号。
cpp 复制代码
	sigaddset(&sig, SIGINT); // success return 0, fail return -1
  • sigprocmask :用于更改当前进程的信号掩码,简单来说即设置哪些信号被阻塞。
cpp 复制代码
 sigprocmask(int how, const sigset_t* signal, sigset_t* old_signal);

how 的处理方式, SIG_BLOCK SIG_UNBLOCK SIG_SETMASK

SIG_BLOCK : 设置新的信号掩码,同时保留旧的信号掩码

SIG_UNBLOCK : 取笑新信号掩码中的信号阻塞,并保留旧的信号掩码

SIG_SETMASK : 设置新的信号掩码,并丢弃旧的信号掩码

cpp 复制代码
	sigset_t oldsig;
    sigemptyset(&oldsig);

    sigprocmask(SIG_SETMASK, &sig, &oldsig);
  • sigpending :用于获取当前进程中待处理的信号集。
cpp 复制代码
	sigset_t pending;
    sigemptyset(&pending);
    
    sigpending(&pending);
  • sigismember :用于测试一个信号是否在一个信号集中,存在返回 1,否则返回 0。
cpp 复制代码
	sigismember(&pending, SIGINT);
3. demo ------ 示范案例

demo 中主要完成以下四个动作:

  1. 屏蔽 2 号信号 ------ SIGINT
  2. 获取当前进程的 pending
  3. 打印 pending 表
  4. 取消对 2 号信号的屏蔽

其中,2.3. 是 "同时" 且 重复 进行的,意味着将出现这样一个现象:当前进程在接收 2 号信号之前,pending 表 2 号信号对应位置为 0;向当前进程发生 2 号信号后,pending 表 2 号信号位置被设置。

以及,取消对 SIGINT 的屏蔽后,2 号信号会被立即递达,进程结束。

1) 屏蔽 2 号信号
cpp 复制代码
#include <iostream>
using namespace std;
#include <signal.h>

int main()
{
    // 1. 屏蔽 2 号信号
    sigset_t block;
    sigset_t old_block;
    sigemptyset(&block);
    sigemptyset(&old_block);

    sigaddset(&block, SIGINT);
    sigprocmask(SIG_SETMASK, &block, &old_block);
    cout << "process pid: " << getpid() << endl;
    cout << "block SIGINT success ..." << endl;
    

    return 0;
}
2) 获取当前进程的 pending 表 | 3) 打印 pending 表
cpp 复制代码
#include <unistd.h>

void PrintSig(sigset_t pending)
{
    cout << "process pending: ";
    for (int i = 31; i >= 1; i--)
    {
        if (sigismember(&pending, i))
            cout << "1";
        else 
            cout << "0";
    }
    cout << endl;
}

int main()
{
    // ...
    
    while (1)
    {
        // 2. 获取当前 pending 表
        sigset_t pending;
        sigemptyset(&pending);
        sigpending(&pending);

        // 3. 打印 pending 表
        PrintSig(pending);
        
        sleep(1);
    }
    
}

将这部分代码先运行起来,并对该进程发送一个 SIGINT 信号,观察现象:

当前进程(3988092)pending 表的 2 号信号对应位置被设置,但进程没有退出!

4) 取消对 2 号信号的屏蔽
cpp 复制代码
int main()
{
    while (1) {
        // ...   
        // 4. 取消对 2 号信号对屏蔽
        ++cnt;
        if (cnt == 20)
        {
            sigprocmask(SIG_UNBLOCK, &block, &old_block);
        }
    }
}

为了使 2 号信号恢复且被递达后的观察效果更明显,我们对 PrintSig 函数 做一下优化,再 对 2 号信号的处理程序自定义

介绍一个新的系统调用接口:signal

signal() 允许我们为特定信号指定一个处理函数 ------ handler() 是一个参数类型为 int 返回值为 的函数, 当信号到达时,该函数将被调用。

cpp 复制代码
#include <iostream>
using namespace std;
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

void PrintSig(sigset_t pending, int count)
{
    cout << "process pending: ";
    for (int i = 31; i >= 1; i--)
    {
        if (sigismember(&pending, i))
            cout << "1";
        else
            cout << "0";
    }
    cout << " , cnt: " << count << endl;
}

void handler(int signal)
{
    cout << "2 号信号被递达" << endl;
    exit(1);
}

int main()
{
    signal(SIGINT, handler); // 对 SIGINT 的处理方式重定义

    // 1. 屏蔽 2 号信号
    sigset_t block;
    sigset_t old_block;
    sigemptyset(&block);
    sigemptyset(&old_block);

    sigaddset(&block, SIGINT);
    sigprocmask(SIG_SETMASK, &block, &old_block);
    cout << "process pid: " << getpid() << endl;
    cout << "block SIGINT success ..." << endl;

    int cnt = 0;
    while (1)
    {
        // 2. 获取当前 pending 表
        sigset_t pending;
        sigemptyset(&pending);
        sigpending(&pending);

        // 3. 打印 pending 表 
        PrintSig(pending, cnt);
        sleep(1);

        // 4. 取消对 2 号信号对屏蔽
        ++cnt;
        if (cnt == 20)
        {
            sigprocmask(SIG_UNBLOCK, &block, &old_block);
        }
    }
    return 0;
}

进程运行 20 秒后,SIGINT 的屏蔽被取消,SIGINT 被递达,进程终止!

4. 对 SIGKILL 和 SIGSTOP 的验证

pid 为 "当前进程" 的 pid

bash 复制代码
while :; do for i in {1..8}; do kill -$i pid; done; sleep 1; done    # 对进程发送 1~8 号信号
while :; do for i in {10..18}; do kill -$i pid; done; sleep 1; done  # 对进程发送 10~18 号信号
while :; do for i in {20..31}; do kill -$i pid; done; sleep 1; done  # 对进程发送 20~31 号信号

对原代码做出修改:使用 for 循环,让进程阻塞 1~31 号信号

随后,在另一窗口使用 bash 脚本,分别向当前进程发送 1~8 、 10~18 、 20~31 号信号,观察进程的 pending 表;

在过程中,对当前进程发送 9 号信号 和 19 号信号,观察现象。

cpp 复制代码
#include <iostream>
using namespace std;
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

void PrintSig(sigset_t pending)
{
    cout << "process pending: ";
    for (int i = 31; i >= 1; i--)
    {
        if (sigismember(&pending, i))
            cout << "1";
        else
            cout << "0";
    }
    cout << endl;
}

void handler(int signal)
{
    cout << "2 号信号被递达" << endl;
    exit(1);
}


int main()
{
    // 1. 屏蔽 1~31 号信号
    sigset_t block;
    sigset_t old_block;
    sigemptyset(&block);
    sigemptyset(&old_block);

    for (int i = 1; i <= 31; i++)
    {
        sigaddset(&block, i);
    }

    sigprocmask(SIG_SETMASK, &block, &old_block);
    cout << "process pid: " << getpid() << endl;
    cout << "block signal success ..." << endl;

    int cnt = 0;
    while (1)
    {
        // 2. 获取当前 pending 表
        sigset_t pending;
        sigemptyset(&pending);
        sigpending(&pending);

        // 3. 打印 pending 表
        PrintSig(pending);
        sleep(2);
    }
    return 0;
}
  • 对当前进程发送 1~8 号信号 和 9 号信号

对当前进程发送 1~8 号信号,进程没有结束。

对当前进程发送 9 号信号,进程终止!

  • 对当前进程发送 10~18 号信号 和 19 号信号

对当前进程发送 10~18 号信号,进程没有结束。

对当前进程发送 19 号信号,进程终止!

  • 对当前进程发送 20~31 号信号

对当前进程发送 20~31 号信号,进程没有结束。

总结:9 号信号 --- SIGKILL 及 19 号信号 --- SIGSTOP 是特殊信号,不能被阻塞,因为它们具有重要作用!
相关推荐
艾伦~耶格尔几秒前
Java Web 之 Session 详解
java·开发语言·前端·后端·session
艾伦~耶格尔2 分钟前
Java Web 之 Cookie 详解
java·开发语言·前端·后端·学习·cookie
2401_857610037 分钟前
足球青训俱乐部后台:Spring Boot开发策略
java·spring boot·后端
程序员大金15 分钟前
基于SpringBoot+Vue+MySQL的美食信息推荐系统
前端·vue.js·spring boot·后端·mysql·spring·美食
玄【学生党】23 分钟前
中国身份证号码校验
c++
程序员大金27 分钟前
基于SpringBoot+Vue+MySQL的旅游管理系统
javascript·vue.js·spring boot·后端·mysql·mybatis·旅游
飘零书剑34 分钟前
ArrayList源码实现(一)
java·开发语言·后端
碧海蓝天202239 分钟前
C++中,如何使你设计的迭代器被标准算法库所支持。
开发语言·c++
wacpguo1 小时前
Vmware虚拟机-Ubuntu扩展硬盘大小
linux·ubuntu