Linux之信号(二)
2026/8/10 18:18:48 网站建设 项目流程

信号保存

信号递达

实际执行信号的处理动作

信号未决

信号从产生到递达之间的状态

阻塞信号

不会立刻处理,会被缓存起来


上面这些概念不是凭空产生的,在linux里面有真实对应的

pending:未决信号集(已经到来、被阻塞卡住的信号)

位图每一位对应一个信号编号:

  • 位 =0:该信号没有待处理
  • 位 =1:该信号存在未决实例,等待递达


block:阻塞掩码(哪些信号要暂时挡住)

block 与 pending 的联动逻辑

  1. 内核发送信号给进程;
  2. 先查block位图:
    • 对应位 = 0(不阻塞):直接信号递达,执行 handler,不会进入 pending;
    • 对应位 = 1(阻塞):将pending对应位标记为 1,信号暂存等待;
  3. 调用sigprocmask()修改 block,把某信号位从 1 改为 0(解除阻塞);
  4. 内核立刻扫描 pending,若该信号 pending 位 = 1,执行信号递达,清空 pending 标记。

handler:每个信号编号对应一条处理规则,决定信号递达时程序该做什么。

数组handler[32]长度 32,对应 Linux 1~31 号标准信号; 下标规则:数组下标 = 信号编号 - 1例:SIGINT (2 号信号) → 存储在handler[1];SIGHUP (1 号) →handler[0]

函数指针数组数组里每一格存的是地址:可以是系统常量地址,也可以是用户自定义函数的地址。

三个问题

问题一

signal(2,myhandler)底层在做什么

将myhandler的函数指针传入handler列表

问题二

什么叫做忽略,默认

二者是内核用宏定义的特殊函数指针常量,强制转换成sighandler_t信号处理函数指针类型,存入handler[]函数指针数组。

数值标识:0代表默认处理,1代表忽略处理,和普通用户自定义函数地址区分开。

问题三

在信号还没有产生的时候,进程就能识别和处理信号???

在内核中就内置了信号的管理和处理方法


sigset_t叫做信号集,本质是一个位图(比特集合),每一个 bit 对应一个信号:

  • bit = 1:该信号标记为「有效」
  • bit = 0:该信号标记为「无效」 所有信号只用 1 个 bit 记录状态,不计数,同一个信号多次触发,bit 只会置 1,不会叠加次数。

前文提到,未决标志、阻塞标志都采用位图存储:每个信号仅占用 1 个比特位,状态只有 0/1 两种,不会记录信号触发的次数。 正因二者存储逻辑完全一致,Linux 统一使用sigset_t(信号集)类型承载这两类标记。

sigset_t本质是比特集合,用每一位标识单个信号的 “有效 / 无效” 状态,在不同集合中含义不同:

  1. 阻塞信号集(进程信号屏蔽字 Signal Mask)比特位为 1:代表该信号被阻塞; 比特位为 0:代表该信号允许递达。 ⚠️ 注意:此处的 “屏蔽” 指暂时阻塞,和直接忽略信号(SIG_IGN)是完全不同的逻辑。
  2. 未决信号集(pending)比特位为 1:代表该信号已产生、因阻塞暂未处理; 比特位为 0:代表无待处理的对应信号。

阻塞信号集也被称作进程的信号屏蔽字,属于进程独有属性。后续章节会详细讲解信号集的各类操作函数,用于增删、清空、判断集合内的信号。

系统当然也提供了对信号集修改的一些系统调用

函数原型功能参数说明返回值
int sigemptyset(sigset_t *set);清空信号集,将集合内所有比特置 0(不含任何信号)set:待操作的信号集指针成功返回 0,失败返回 - 1
int sigfillset(sigset_t *set);填满信号集,将所有信号对应比特置 1(包含全部信号)set:待操作的信号集指针成功返回 0,失败返回 - 1
int sigaddset(sigset_t *set, int signum);向信号集中添加单个信号,对应 bit 置 1set:信号集;signum:信号编号(如 SIGINT=2)成功返回 0,失败返回 - 1
int sigdelset(sigset_t *set, int signum);从信号集中删除单个信号,对应 bit 置 0set:信号集;signum:信号编号成功返回 0,失败返回 - 1
int sigismember(const sigset_t *set, int signum);判断指定信号是否存在于集合中set:只读信号集;signum:信号编号存在返回 1,不存在返回 0,出错返回 - 1

sigprcmask

参数详解

  1. int how:操作模式,3 种可选宏

    表格

    宏常量作用
    SIG_BLOCK屏蔽叠加:将set中的信号追加到原有屏蔽字(阻塞新增信号)
    SIG_UNBLOCK解除屏蔽:从原有屏蔽字中移除set里的信号
    SIG_SETMASK覆盖替换:直接用set完整替换当前进程屏蔽字
  2. const sigset_t *set:待操作的信号集

  • NULL:仅读取当前屏蔽字,不做任何修改;
  • 非空:根据how执行增 / 删 / 替换操作。
  1. sigset_t *oldset:保存旧屏蔽字
  • NULL:不保存原有屏蔽字;
  • 非空:函数执行完毕后,将修改前的原始信号屏蔽字存入oldset,方便后续恢复。

返回值

  • 成功:返回0
  • 失败:返回-1,并设置errno错误码

sigpending

读取进程未决信号集(pending 信号集),将当前所有已产生、但因被屏蔽暂时无法递达的信号存入参数set中。 未决信号:信号已经发送给进程,但该信号位于进程屏蔽字中被阻塞,系统暂存该信号,等待解除屏蔽后再处理。

参数详解

  • sigset_t *set:输出型参数,传入一个已初始化的信号集变量地址,函数执行完成后,set内部比特位会标记所有当前未决的信号。
  • 不可传入NULL,否则无法获取未决信号信息。

返回值

  • 成功:返回0
  • 失败:返回-1,同步设置全局错误码errno

那么这么修改pending-->我们之前就学过了-->kill接口


使用

我们自己设置要阻塞的信号

void PrintPending(const sigset_t &pending) { for(int sig=31;sig>0;sig--) { if(sigismember(&pending,sig)) { cout<<"1"; } else cout<<"0"; } sleep(1); cout<<endl; } int main() { cout<<"我的pid:"<<getpid()<<endl; sigset_t block_set, old_set; sigemptyset(&block_set); sigemptyset(&old_set); sigaddset(&block_set, 2); // 只是设置自己的block // 还要再进行系统调用 int n = sigprocmask(SIG_SETMASK, &block_set, &old_set); if (n < 0) perror("sigprocmask"); while (1) { // 获取pending表 sigset_t pending; sigemptyset(&pending); int n = sigpending(&pending); if (n < 0) perror("sigpending"); // 打印pending表 PrintPending(pending); sleep(1); } return 0; }

我们将每一位都设置成阻塞,依然是9等信号无法被阻塞

我们让进程进行15次循环之后将2号终止信号恢复

发现进程立马就停止了

一旦我们解除对某个信号的阻塞,他就会被立即递达

但是我们不想要这个样子就要用到signal函数

当阻塞结束的时候pending表相应的比特位会改变,那么是再handler之前变成0,还是再handler之后变成0的呢????

我们可以再handler方法内部继续将pending打印出来就可以知道了

根据观察我们不难得出----其实再调用handler函数之前,pending表对应的信号就已经被改成0了

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询