【Linux笔记】——进程信号的保存

🔥个人主页🔥:孤寂大仙V

🌈收录专栏🌈:Linux

🌹往期回顾🌹:【Linux笔记】------进程信号的产生

🔖流水不争,争的是滔滔不


一、信号的相关概念

  1. 实际执行信号的处理动作称为信号递达(Delivery)。
  2. 信号从产生到递达之间的状态,称为信号未决(Pending)。
  3. 进程可以选择**阻塞(Block)**某个信号。
  4. 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才能执行递达的动作。
  5. 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

这些概念都是内核完成的

如图,block就是阻塞,pending就是未决,handler是信号处理函数就是信号来了以后"干具体事情的地方",它才是最终决定信号怎么被响应的"执行动作"。

对block和pending进行一个比喻,block可以理解为拦着不让外卖员敲门(不想收外卖时挂个禁止通行的牌子),而pending可以理解为外卖员到了,但是被门外拦着(外卖员在门口堵着)。

简单梳理一下handler触发的整个流程:

  1. 信号来了(内核识别到,不管是硬件还是软件发来的信号)
  2. 判断block:block是一个信号信号阻塞位图,每一位对应一个信号标号1表示阻塞(block了,不让送达)0表示不阻塞(可以送达)。
  3. 检测block位图中,信号对应的那一位,如果是1,则信号是挂到pending位图中,若是0,则理解开始信号处理流程,调用用户注册的handler函数。

注意:
信号来了。不管block了没有,这个信号都会被挂到pending位图中(置为1),也就是说信号来了就记下来了pending代表"来了"。
然后才会去看block位图,如果这个信号在block中被阻塞,信号就会一直挂在pending中,等待解封。如果block没有阻塞,就交个handler执行啦。

还是看上图,pending位图保存收到的信号位图,比特位的位置:表示的是第几个信号,比特位的内容:表示是否收到。block位图表示是否阻塞,比特位的位置:表示的是第几个信号,比特位的内容表示:是否阻塞。

二、信号集操作函数

sigset_t

从上图来看,每个信号只有一个bit的未决标志, 非0即1, 不记录该信号产生了多少次,阻塞标志也是这样

表示的。因此, 未决和阻塞标志可以勇相同的数据类型sigset_t来存储, , 这个类型可以表示每个信号的"有效"或"无效"状态, 在阻塞信号集中"有效"和"无效"的含义是该信号是否被阻塞, 而在未决信号集中"有 效"和"无效"的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的 这里的"屏蔽"应该理解为阻塞⽽不是忽略。

信号集操作函数

cpp 复制代码
#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)

操作信号集(sigset_t) 的一堆函数,干的都是"组装、修改、判断信号集"的活。

这些信号集其实是用户态的临时数据结构,本质上是位图,跟内核里block位图的存储形式类似。

典型函数:

sigemptyset(清空信号集,全是0)

sigfillset(全填1)

sigaddset(添加某个信号)

sigdelset(删除某个信号)

sigismember(判断某信号在不在信号集中)

这四个函数都是成功返回0,出错返回-1。sigismember是⼀个布尔函数,⽤于判断⼀个信号集的有效信

号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

sigprocmask

调用函数 sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)。 这个系统调用才是真正去修改内核中当前进程的 block 位图。
它会用你准备好的信号集(sigset_t)去 "替换、添加、删除" block 位图中对应的位。所有这里要注意上面的信号集操作函数只是工具,你拿着信号集草稿去找 sigprocmask 办手续,sigprocmask 帮你真正去 kernel 里改。

cpp 复制代码
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

参数有:

how:表示是"添加(阻塞)"、"删除(解除阻塞)"、"直接替换成新的"。

set:你准备好的信号集。

oldset:可以把修改前的block值保存下来。

这些都是修改block位图。

sigpending

cpp 复制代码
#include <signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。
调⽤成功则返回0,出错则返回-1

不管你爱不爱搭理(阻塞不阻塞),信号一到,pending 位图对应的位置就会被置 1。

不会因为 block、不会因为 handler 正忙,就不记。→ 来一个挂一个, pending 只负责"有信号来了"的事实,不负责"要不要执行"。

那sigpending() 是干嘛的?

查询"当前进程"哪些信号已经挂在 pending 位图上(那些"已经到了但是还没被响应"的信号)。
用于排查信号是否被 block 阻塞住了。
诊断信号处理的状态,是个"监测手段",不是"处理动作"。

实验

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

// 打印 pending 集
void PrintPending(sigset_t &pending) {
    std::cout << "curr process[" << getpid() << "] pending: ";
    for (int signo = 31; signo >= 1; signo--) {
        if (sigismember(&pending, signo)) {
            std::cout << 1;
        } else {
            std::cout << 0;
        }
    }
    std::cout << "\n";
}

// 信号处理函数
void handler(int signo) {
    std::cout << signo << " 号信号被递达!!!" << std::endl;
    std::cout << "-------------------------------" << std::endl;
    sigset_t pending;
    sigpending(&pending);
    PrintPending(pending);
    std::cout << "-------------------------------" << std::endl;
}

int main() {
    // 0. 捕捉 2 号信号
    signal(SIGINT, handler);  // 自定义捕捉 SIGINT (Ctrl+C)

    // 1. 屏蔽 2 号信号
    sigset_t block_set, old_set;
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    sigaddset(&block_set, SIGINT);

    // 1.1 设置进程的 Block 表
    sigprocmask(SIG_BLOCK, &block_set, &old_set);

    int cnt = 15;
    while (true) {
        // 2. 获取当前进程的 pending 信号集
        sigset_t pending;
        sigpending(&pending);

        // 3. 打印 pending 信号集
        PrintPending(pending);

        cnt--;

        // 4. 解除对 2 号信号的屏蔽
        if (cnt == 0) {
            std::cout << "解除对 2 号信号的屏蔽!!!" << std::endl;
            sigprocmask(SIG_SETMASK, &old_set, &block_set);
        }

        sleep(1);
    }

    return 0;
}

前面 15 秒,Ctrl+C 打了很多次,都不会触发 handler,因为 SIGINT 被 block 了,但 pending 位图记录了。

你看到的:

cpp 复制代码
curr process[448336] pending: 0000000000000000000000000000010

代表第 2 号信号 (SIGINT) 被挂在 pending 里。

cnt==0 时解除阻塞,pending 里的 SIGINT 会被马上递达,调用 handler,打印信号收到。

term 和 core

子进程退出时产生的 core 和 term 现象,本质上也是由信号触发的结果,不过它们不单纯是"信号"本身,而是信号带来的退出状态。

core会在当前路径下,形成一个文件,进程异常退出的时候,进程在内存中的核心数据,会从内存拷贝到磁盘,形成一个文件的核心转储,并且支持debug。然后在进程退出

trem就直接进程退出了。

子进程的 term / core 是"信号导致的退出状态",本质是信号事件的结果表现。term 代表被终止,core 代表还顺便扔了个尸体(core dump 文件)出来。

相关推荐
代码AC不AC3 分钟前
【Linux】NAT与代理
linux·nat·代理
andongni20312 分钟前
Spring Boot基础应用开发与部署
java·spring boot·后端
lupai17 分钟前
增值税发票 OCR 识别 API 新手接入指南
java·前端·ocr
TELL52119 分钟前
Sonar质量门禁
java
java修仙传22 分钟前
从网页禅道到 AI 能调用的工具:我的禅道 MCP 实现思路分享
java·人工智能·python·ai应用·mcp开发
HAYDENR22 分钟前
依赖数据迁移工具做增量同步有哪些易错点?调整数据迁移工具策略怎么保证断点续传可靠?
java·大数据·数据库
云边有个稻草人32 分钟前
复盘小站:用 Doubao-Seed-Evolving,把厚厚的笔记一键变成可复习、可自测、可批改的知识资产
笔记·doubao·ai开发实战·复盘小站
IT利刃出鞘34 分钟前
SpringBoot--解决@Valid放在接口的List上时无效的问题
java·spring
mqiqe39 分钟前
AgentScope Java Harness:2. 上下文压缩:让长期 Agent 永不“失忆“
java·开发语言
梦想的旅途242 分钟前
企业微信API实战:Python自动化消息推送
java·前端·python·自动化·企业微信