Linux之信号(二)

信号保存

信号递达

实际执行信号的处理动作

信号未决

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

阻塞信号

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


上面这些概念不是凭空产生的,在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 置 1 set:信号集;signum:信号编号(如 SIGINT=2) 成功返回 0,失败返回 - 1
int sigdelset(sigset_t *set, int signum); 从信号集中删除单个信号,对应 bit 置 0 set:信号集;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接口


使用

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

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

相关推荐
我是小灰灰吖2 小时前
QT在线安装器使用指南:从下载到配置完整教程
linux·qt·ubuntu
GlueNa2SiO32 小时前
第十八章 Linux故障排查与恢复
linux·服务器·笔记·学习
寺中人3 小时前
Linux 基础命令入门实战教程:从零掌握常用操作,新手快速上手
linux·运维·服务器·shell·linux 命令·linux 基础教程·linux 入门
V哥AI增长3 小时前
Schema.org 结构化数据与GEO技术落地:AI引擎引用机制与JSON-LD部署实证研究
大数据·运维·人工智能
小张成长计划..4 小时前
【Linux】18:基础IO
linux·运维·服务器
MIXLLRED4 小时前
解决——Ubuntu远程桌面指令更换网络
linux·网络·ubuntu
啊哈一半醒5 小时前
Windows 虚拟机搭建 CentOS 全过程(2026 最新版 + 踩坑解决)
linux·windows·centos
wunaiqiezixin5 小时前
MIT 6.S081 lazy 前置篇(lab5):缺页异常、惰性分配、写时复制、页面换出
linux·unix·os·xv6·mit6.s081
东东最爱敲键盘5 小时前
二阶段总结
运维