Linux信号——保存信号

目录

一信号其他相关常见概念

二信号在内核中的表示

三sigset_t

[3.1 总结](#3.1 总结)

四信号集操作函数

五sigprocmask

六sigpending

七演示代码

Linux 信号是系统用来通知进程事件的异步方式。很多时候进程暂时没空处理信号,系统就需要先把信号暂时"存起来",避免丢失或乱序。日常开发中遇到的信号不生效、延迟触发、莫名丢失等问题,大多都出在信号的保存和状态管理上。本文就聚焦信号如何被内核保存、如何暂存、状态如何切换,通俗讲清未决、阻塞、递达的区别,以及内核位图、信号集和信号屏蔽的底层逻辑,并结合代码演示完整过程。

当前内容:

一信号其他相关常见概念

• 实际执行信号的处理动作称为信号递达(Delivery)

• 信号从产生到递达之间的状态,称为信号未决(Pending)。

• 进程可以选择阻塞 (Block )某个信号。

• 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.

• 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动
作。

二信号在内核中的表示

在 Linux 内核中,每一个运行的进程(无论是 bash、nginx 还是你的 hello_world)都对应一个名为 task_struct 的庞大结构体。可以把它想象成进程的"身份证"和"档案袋"。

这张图的核心在于,它提取了 task_struct 中关于信号管理的三个最关键的数据成员:

  1. block (阻塞位图) 这是一个位图(bitmap),每一位对应一个信号编号。例如位 2 对应 SIGINT。如果该位为 1,表示进程屏蔽/阻塞了这个信号。内核收到这个信号后,不会立即处理它,而是会将其"挂起"。

  2. pending (待处理位图) 这也是一个位图,记录了已经发给进程,但尚未被处理 的信号。当内核收到一个信号时,首先查看 block 位图:

    • 如果 block 位为 0 :直接将对应的 pending 位设为 1。

    • 如果 block 位为 1 :将对应的 pending 位设为 1,然后将信号丢入"等待队列"。

  3. handler (函数处理映射表)

    这是信号处理流程的最终出口。当进程决定真正处理 一个信号时(即 pending 中有信号,且对应的 block 被解除),内核会查找这张表。

    表中每一项(对于每个信号)提供了三种处理选项:SIG_DFL (Default) :执行系统默认动作。对于大多数信号,默认动作就是 终止进程SIG_IGN (Ignore)彻底忽略 这个信号。即便 pending 位为 1,内核也会直接清空它,不执行任何动作。用户自定义函数 :此时指向一个内存地址。当信号处理时,内核会从内核态切入到用户空间 ,跳转到这个地址,执行用户写的 void sighandler(int signo) { ... } 代码。

这三者就像三道关卡,决定了信号从产生到最终执行的命运。

源码:

struct sighand_struct定义

sigset_t定义:

struct sigpending定义:

三sigset_t

定义:

cpp 复制代码
typedef struct {
    unsigned long sig[_NSIG_WORDS];
} sigset_t;

其中:

  • _NSIG_WORDS 是一个宏,定义为 (_NSIG / _NSIG_BPW)

  • _NSIG 是信号总数(通常是 64,对应 1~64 号信号)。

  • _NSIG_BPWunsigned long 的位数(在 32 位系统上是 32)。

  • 所以 sigset_t 本质就是一个 unsigned long 类型的数组

对于 32 位系统,sigset_t 展开后就是:

cpp 复制代码
typedef struct {
    unsigned long sig[2];  // 64 / 32 = 2
} sigset_t;
  • sig[0] 存储信号 1~32 的位图。

  • sig[1] 存储信号 33~64 的位图。

每一位(bit)对应一个信号:

  • 0:信号未产生/未阻塞。

  • 1:信号已产生(pending)或被阻塞(block)。

从上图来看,每个信号只有一个bit的未决标志, 非0即1, 不记录该信号产生了多少次,阻塞标志也是这样表示的。因此, 未决和阻塞标志可以用相同的数据类型sigset_t来存储, , 这个类型可以表示每个信号的"有效"或"无效"状态, 在阻塞信号集中"有效"和"无效"的含义是该信号是否被阻塞, 而在未决信号集中"有 效"和"无效"的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。塞信号集也叫做当前进程的 这里的"屏蔽"应该理解为阻塞而不是忽略。sigset_t称为信号集信号屏蔽字(Signal Mask),

3.1 总结

  1. 本质sigset_t 是一个位图 ,用数组 unsigned long sig[N] 实现,每个 bit 代表一个信号。

  2. 作用 :它被用于 task_struct 中的 blocked(阻塞)和 pending(待处理)位图。

四信号集操作函数

sigset_t类型对于每种信号用一个bit表示"有效"或"无效"状态, 至于这个类型内部如何存储这些

bit则依赖于系统实现, 从使用者的角度是不必关心的, 使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释, 比如用printf直接打印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);

• 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含

任何有效信号。

• 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号

包括系 统支持的所有信号。

• 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。 初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删

除某种有效信号。这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

五sigprocmask

调用函数sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)。

cpp 复制代码
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
参数 说明 取值
how 指定如何修改信号屏蔽字 SIG_BLOCKSIG_UNBLOCKSIG_SETMASK
set 指向要设置的信号集 非空:用于修改;NULL:忽略
oldset 保存旧的信号屏蔽字 非空:保存旧值;NULL:不保存

how参数的可选值:

如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一

个信号递达。

六sigpending

sigpending可以读取当前进程的未决信号集,通过set参数传出。

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

七演示代码

1)这个实验演示了信号的阻塞、未决和递达三态转换

阻塞SIGINT后发送的信号会进入未决状态,解除阻塞后信号才被递达处理。

cpp 复制代码
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main() {
    sigset_t set;
    
    // 创建信号集并阻塞SIGINT
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigprocmask(SIG_BLOCK, &set, NULL);
    
    printf("SIGINT被阻塞,发送信号测试:\n");
    kill(getpid(), SIGINT);  // 发送信号,但会被阻塞
    
    // 检查未决信号
    sigset_t pending;
    sigpending(&pending);
    printf("未决信号中是否有SIGINT: %s\n", 
           sigismember(&pending, SIGINT) ? "是" : "否");
    
    // 解除阻塞
    sigprocmask(SIG_UNBLOCK, &set, NULL);
    printf("解除阻塞\n");
    
    return 0;
}

2)这个实验演示了信号屏蔽、未决状态和递达的过程

核心流程:

  1. 屏蔽2号信号(SIGINT)

  2. 期间发送的2号信号会处于未决状态(pending位图置1)

  3. 15秒后解除屏蔽 ,未决信号被递达,执行自定义处理函数

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

#include <cstdio>
#include <iostream>
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(2, handler);  // 自定义捕捉
  // signal(2, SIG_IGN); // 忽略一个信号
  // signal(2, SIG_DFL); // 信号的默认处理动作
  //  1. 屏蔽2号信号
  sigset_t block_set, old_set;
  sigemptyset(&block_set);
  sigemptyset(&old_set);
  sigaddset(&block_set,
            SIGINT);  // 我们有没有修改当前进行的内核block表呢???10
  // 1.1 设置进入进程的Block表中
  sigprocmask(
      SIG_BLOCK, &block_set,
      &old_set);  // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!
  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);
  }
}

八结语

简单来说,Linux 内核就是靠位图和信号集来统一管理信号的保存状态。通过阻塞位图、未决位图配合信号处理函数表,系统可以精准记住哪些信号被挡住、哪些信号已经产生但还没来得及处理,完美解决了异步信号容易丢失、混乱的问题。搞懂这套信号保存机制,就能彻底理解信号阻塞、延迟、未处理的本质,平时排查程序异常、信号失效问题也能更加得心应手。

相关推荐
2501_920047033 小时前
firewalld的使用
linux·运维
z202305083 小时前
以太网之VLAN介绍
linux·服务器·网络·人工智能·ai
孙高飞3 小时前
直播预告 - 周日晚 7 点半-AI 驱动 UI 自动化
运维·自动化
qq_196976173 小时前
硬核多语言办公方案:基于Gemini境像站的实时翻译与双语文档自动化生成管道(国内免费镜像实测)
运维·自动化
拉不拉斯4 小时前
Linux 性能调优实战指南:从 perf 实时监控到火焰图生成
linux·运维·服务器·perf
Agent手记4 小时前
环保排放数据自动上报全流程自动化—— 2026企业级智能体(Agent)落地全指南
运维·人工智能·ai·自动化
LCG元4 小时前
RAG工程指南:从基础检索到生产部署全解析
java·运维·数据库
小生迷途知返4 小时前
影响巨大!Linux 提权大洞速速复现!!!暂无补
linux·运维·服务器
FreeBuf_4 小时前
Claude Mythos Preview 实现自动化漏洞研究突破,可构建PoC漏洞利用链
运维·自动化