当前位置: 首页 > article >正文

信号处理: 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 对该变量初始化。

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

how 的处理方式, SIG_BLOCK SIG_UNBLOCK SIG_SETMASK

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

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

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

	sigset_t oldsig;
    sigemptyset(&oldsig);

    sigprocmask(SIG_SETMASK, &sig, &oldsig);
  • sigpending :用于获取当前进程中待处理的信号集。
	sigset_t pending;
    sigemptyset(&pending);
    
    sigpending(&pending);
  • sigismember :用于测试一个信号是否在一个信号集中,存在返回 1,否则返回 0。
	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 号信号
#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 表
#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 号信号的屏蔽
int main()
{
    while (1) {
        // ...   
        // 4. 取消对 2 号信号对屏蔽
        ++cnt;
        if (cnt == 20)
        {
            sigprocmask(SIG_UNBLOCK, &block, &old_block);
        }
    }
}

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

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

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

#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

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 号信号,观察现象。

#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 是特殊信号,不能被阻塞,因为它们具有重要作用!

http://www.kler.cn/a/323479.html

相关文章:

  • 深入解析 CentOS 7 上 MySQL 8.0 的最佳实践20241112
  • 移除元素(leetcode 27)
  • Android 6年经验面试总结 2024.11.15
  • CondaError: Run ‘conda init‘ before ‘conda activate‘解决办法
  • web与网络编程
  • 精华帖分享|浅谈金融时间序列分析与股价随机游走
  • 开关电源要做哪些测试?
  • Docker精讲:基本安装,简单命令及核心概念
  • ①无需编程 独立通道 Modbus主站EtherNet/IP转ModbusRTU/ASCII工业EIP网关串口服务器
  • 染色算法的简单概述
  • altera FPGA下载失败
  • MySQL之基础篇
  • 【bug fixed】hexo d的时候Spawn failed
  • c语言200例 066
  • Spring Boot实战:构建在线商城系统
  • PyQt5中关于QLineEdit的空输入报错的简单处理
  • 华为云发布全栈可观测平台AOM,以AI赋能应用运维可观测
  • Apache Cordova/PhoneGap
  • ConstructorParameters
  • 基于elasticsearch存储船舶历史轨迹
  • 基于SpringBoot+Vue的大学生勤工助学兼职管理系统
  • 2024最新【PyCharm】史上最全PyCharm安装教程,图文教程(超详细)
  • harmonyOS ArkTS最新跳转Navigation
  • 解决编译问题:undefined reference to `__aeabi_uidivmod‘
  • 20240924 行列式为1的矩阵不一定是正交矩阵
  • 昇思MindSpore进阶教程--数据处理管道支持Python对象