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

news2024/11/30 0:51:41
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.coloradmin.cn/o/2188959.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLO11改进 | 检测头 | 融合渐进特征金字塔的检测头【AFPN3】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 本文介绍了一个渐进特征金字塔网络&…

关于 S7 - 1200 通过存储卡进行程序更新

西门子S7-1200系列PLC可以通过存储卡进行程序的更新&#xff0c;固件版本的升级以及程序数据的存储多项功能。本例进行程序更新的操作。 存储卡的订货号以及存储容量 一&#xff1b;如何插入存储卡 在CPU断电下&#xff0c;将CPU上挡板向下掀开&#xff0c;可以看到右上角有一…

ai写作论文会被检测吗?分享市面上7款自动写论文网站

近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;AI写作工具在学术界引起了广泛关注。然而&#xff0c;这些工具的使用也引发了关于学术诚信和检测机制的讨论。根据多所高校的声明&#xff0c;为了应对AI代写论文的现象&#xff0c;许多高校已经开始引入论文检测工具…

Python入门:深入了解__init__.py 文件(如何实现动态导入子模块)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 `__init__.py` 的作用示例:📝 如何编写 `__init__.py`1. 空的 `__init__.py`2. 导入子模块3. 初始化代码4. 动态导入子模块📝 编写 `__init__.py` 的技巧和注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 在…

01:(寄存器开发)点亮一个LED灯

寄存器开发 1、单片机的简介1.1、什么是单片机1.2、F1系列内核和芯片的系统架构1.3、存储器映像1.4、什么是寄存器 2、寄存器开发模板工程3、使用寄存器点亮一个LED4、代码改进15、代码改进2 本教程使用的是STM32F103C8T6最小系统板&#xff0c;教程来源B站up“嵌入式那些事”。…

前缀和(6)_和可被k整除的子数组_蓝桥杯

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 前缀和(6)_和可被k整除的子数组 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 …

kubeadm部署k8s

1.1 安装Docker [rootk8s-all ~]# wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.huaweicloud.com/docker-ce/linux/centos/docker-ce.repo [rootk8s-all ~]# sed -i sdownload.docker.commirrors.huaweicloud.com/docker-ce /etc/yum.repos.d/docker-ce.repo [ro…

基于Keras的U-Net模型在图像分割与计数中的应用

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色&a…

Yocto - 使用Yocto开发嵌入式Linux系统_07 构建使用的临时文件夹

Detailing the Temporary Build Directory 在本章中&#xff0c;我们将尝试了解映像生成后临时构建目录的内容&#xff0c;并了解 BitBake 如何在烘焙过程中使用它。此外&#xff0c;我们还将了解这些目录中的某些内容如何在出现问题时作为有价值的信息来源来帮助我们。 In thi…

前缀和——从LeetCode题海中总结常见套路

目录 前缀和定义 截断前缀和DP&#xff1a;LeetCode53.最大子序和 经典左右指针&#xff1a;LeetCode209.长度最小的子数组 暴力求解&#xff1a;超时 优雅的双指针写法一&#xff1a; 优雅的双指针写法二&#xff1a; LeetCode.1588.所有奇数长度子数组的和 手速题&am…

springboot系列--web相关知识探索三

一、前言 web相关知识探索二中研究了请求是如何映射到具体接口&#xff08;方法&#xff09;中的&#xff0c;本次文章主要研究请求中所带的参数是如何映射到接口参数中的&#xff0c;也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、…

[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解

[大语言模型-算法优化] 微调技术-LoRA算法原理及优化应用详解 前言: 古人云: 得卧龙者&#xff0c;得天下。 然在当今大语言模型流行的时代&#xff0c;同样有一句普世之言: 会微调技术者&#xff0c;得私域大模型部署之道&#xff01; 在众多微调技术中&#xff0c;LoRA (…

单细胞scDist细胞扰动差异分析学习

scDist通过分析不同状态下细胞的距离来找到差异最大的细胞亚群(见下图的A)&#xff0c;然后再分析每一个细胞亚群的PCA通过线性的混合模型并结合最终的系数去预估不同干预方式下细胞群之间的距离。 Augur是通过对每一个细胞进行AUC评分并排序最终找到扰动最佳的细胞群&#xf…

等额本金和等额本息是什么意思?

等额本金和等额本息是两种常见的贷款还款方式&#xff0c;它们各自有着不同的特点和适用场景。下面我将用通俗易懂的语言来解释这两种还款方式&#xff1a; 等额本金 定义&#xff1a;等额本金指的是在贷款期限内&#xff0c;每月偿还相同数额的本金&#xff0c;而利息则随着剩…

FPGA远程烧录bit流

FPGA远程烧录bit流 Vivado支持远程编译并下载bit流到本地xilinx开发板。具体操作就是在连接JTAG的远程电脑上安装hw_server.exe。比如硬件板在实验室或者是其他地方&#xff0c;开发代码与工程在本地计算机&#xff0c;如何将bit流烧录到实验室或者远程开发板&#xff1f; vi…

Socket套接字(客户端,服务端)和IO多路复用

Socket套接字&#xff08;客户端&#xff0c;服务端&#xff09; 目录 socket是什么一、在客户端1. 创建套接字2. 设置服务器地址3. 连接到服务器4. 发送数据5. 接收数据6. 关闭连接 二、内核态与用户态切换三、系统调用与上下文切换的关系四、在服务端1. 创建 Socket (用户态…

【Linux】进程地址空间(初步了解)

文章目录 1. 奇怪的现象2. 虚拟地址空间3. 关于页表4. 为什么要有虚拟地址 1. 奇怪的现象 我们先看一个现象&#xff1a; 为什么父子进程从“同一块地址中”读取到的值不一样呢&#xff1f; 因为这个地址不是物理内存的地址 &#xff0c;如果是物理内存的地址是绝对不可能出…

C++【类和对象】(友元、内部类与匿名对象)

文章目录 1.友元2.内部类3.匿名对象结语 1.友元 友元提供了⼀种突破类访问限定符封装的方式&#xff0c;友元分为&#xff1a;友元函数和友元类&#xff0c;在函数声明或者类声明的前面加friend&#xff0c;并且把友元声明放到⼀个类的里面。外部友元函数可访问类的私有和保护…

【安全科普】从“微信文件助手隐私泄漏”看社交平台网络安全

随着互联网技术的飞速发展&#xff0c;社交平台已经成为了人们日常生活中不可或缺的一部分。人们通过社交平台与亲朋好友保持联系&#xff0c;分享生活点滴&#xff0c;获取资讯信息。然而&#xff0c;与此同时&#xff0c;社交平台上的网络安全风险也日益凸显。近期&#xff0…

简单的a+b-C语言

1.问题&#xff1a; 输入两个整数a和b&#xff0c;计算ab的和。 2.解答&#xff1a; scanf()函数是通用终端格式化输入函数&#xff0c;它从标准输入设备(键盘) 读取输入的信息。可以读入任何固有类型的数据并自动把数值变换成适当的机内格式。 scanf()函数返回值分为3种&…