Linux 信号控制

目录

一、背景:为什么要研究信号屏蔽与未决信号?

二、代码拆解:从示例看核心逻辑

[1. 信号集的初始化与屏蔽设置](#1. 信号集的初始化与屏蔽设置)

[2. 未决信号的查询与打印](#2. 未决信号的查询与打印)

三、关键问题解答:穿透代码看本质

[问题 1:"我可以将所有的信号都进行屏蔽,信号不就不会被处理了吗?"](#问题 1:“我可以将所有的信号都进行屏蔽,信号不就不会被处理了吗?”)

[问题 2:"sigset_t 是在哪里开辟的空间?"](#问题 2:“sigset_t 是在哪里开辟的空间?”)

[问题 3:"未决信号的本质是什么?"](#问题 3:“未决信号的本质是什么?”)

四、扩展:信号屏蔽与未决的典型应用场景

[场景 1:关键逻辑的 "原子性" 保障](#场景 1:关键逻辑的 “原子性” 保障)

[场景 2:批量处理未决信号](#场景 2:批量处理未决信号)

五、总结


在 Linux 系统编程中,信号是进程间通信和系统事件通知的重要机制。今天我们就以一段关于 ** 信号屏蔽(sigprocmask)未决信号(sigpending)** 的代码为切入点,深入剖析这两个概念的底层逻辑与实际应用。

一、背景:为什么要研究信号屏蔽与未决信号?

在多任务、多事件的系统环境中,进程可能会同时收到多个信号。但有些场景下,我们希望暂时屏蔽某些信号 (比如在执行关键逻辑时,不希望被干扰);同时,我们也需要知道有哪些信号被屏蔽后处于 "待处理" 状态 (即未决信号)。这两个机制 ------sigprocmasksigpending,就是用来解决这些问题的关键工具。

二、代码拆解:从示例看核心逻辑

先看我们的示例代码,它主要做了两件事:屏蔽所有 1-31 号信号 ,并循环打印当前的未决信号集合

1. 信号集的初始化与屏蔽设置

运行

cpp 复制代码
sigset_t bset, oset;
sigemptyset(&bset);
sigemptyset(&oset);
for (int i = 1; i <= 31; i++)
{
    sigaddset(&bset, i); // 将1-31号信号加入屏蔽集
}
sigprocmask(SIG_SETMASK, &bset, &oset);
  • sigset_t:是 Linux 中用于表示信号集合的数据结构,本质上是一个位图(每一位对应一个信号)。
  • sigemptyset:初始化一个空的信号集合(所有位设为 0)。
  • sigaddset:将指定信号加入集合(对应位设为 1)。
  • sigprocmask核心系统调用 ,用于修改进程的信号屏蔽字 (即进程当前屏蔽哪些信号)。SIG_SETMASK表示用bset完全替换当前的屏蔽字,oset用于保存旧的屏蔽字(以便后续恢复)。

2. 未决信号的查询与打印

运行

cpp 复制代码
sigset_t pending;
while (true)
{
    int n = sigpending(&pending); // 获取当前进程的未决信号集合
    if (n < 0)
        continue;
    PrintPending(pending); // 打印未决信号的位图
    sleep(1);
}

void PrintPending(sigset_t &pending)
{
    for (int signo = 31; signo >= 1; signo--)
    {
        if (sigismember(&pending, signo)) // 判断信号是否在未决集合中
            cout << "1";
        else
            cout << "0";
    }
    cout << "\n\n";
}
  • sigpending:获取进程的未决信号集合(即被屏蔽且已经发送到进程,但尚未被处理的信号)。
  • sigismember:判断某个信号是否在信号集合中。
  • 打印逻辑:从 31 号到 1 号信号依次判断,输出 "1" 表示该信号未决,"0" 表示未未决,这样就能直观看到未决信号的位图状态。

三、关键问题解答:穿透代码看本质

问题 1:"我可以将所有的信号都进行屏蔽,信号不就不会被处理了吗?"

回答:大部分信号会被屏蔽,但有例外!

在 Linux 中,9号信号(SIGKILL)19号信号(SIGSTOP)不可屏蔽 的。这是系统级的安全设计 ------ 比如SIGKILL用于强制终止进程,若能被屏蔽,进程就可能无法被杀死,导致系统资源泄漏;SIGSTOP用于暂停进程,同理需要保证其强制性。

所以,即使代码中尝试屏蔽所有 1-31 号信号,SIGKILLSIGSTOP仍然可以突破屏蔽,作用于进程。

问题 2:"sigset_t 是在哪里开辟的空间?"

回答:在用户栈上,属于进程的用户空间。

sigset_t bset, oset;是在main函数中定义的局部变量,因此存储在进程的用户栈 中(属于用户空间内存)。当调用sigprocmask时,内核会读取用户空间中bset的值,进而修改内核中进程task_struct里的信号屏蔽字。

问题 3:"未决信号的本质是什么?"

回答:未决信号是 "被屏蔽且已送达" 的信号的临时存储。

每个进程的task_struct中,有一个未决信号集合 (pending)和一个信号屏蔽字(mask)。当一个信号被发送到进程时:

  • 若该信号未被屏蔽(mask 中对应位为 0),则进程会立即处理(执行默认动作或自定义处理器);
  • 若该信号被屏蔽 (mask 中对应位为 1),则会被记录到未决集合(pending 中对应位置 1),直到屏蔽被解除,才会被处理。

四、扩展:信号屏蔽与未决的典型应用场景

场景 1:关键逻辑的 "原子性" 保障

在执行一段不希望被信号打断的关键逻辑时(比如数据库事务、文件写入的关键步骤),可以先屏蔽信号,执行完逻辑后再解除屏蔽:

运行

cpp 复制代码
sigset_t oldmask, newmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT); // 屏蔽Ctrl+C信号(2号)

sigprocmask(SIG_BLOCK, &newmask, &oldmask); // 保存旧屏蔽字,屏蔽SIGINT

// 执行关键逻辑...

sigprocmask(SIG_SETMASK, &oldmask, NULL); // 恢复旧屏蔽字,解除SIGINT屏蔽

场景 2:批量处理未决信号

有时我们希望 "积攒" 一批信号,然后一次性处理。可以先屏蔽信号,等业务逻辑完成后,查询未决信号并逐个处理:

运行

cpp 复制代码
// 屏蔽信号
sigset_t mask, oldmask;
sigfillset(&mask); // 填充所有信号(除不可屏蔽的)
sigprocmask(SIG_SETMASK, &mask, &oldmask);

// 执行业务逻辑,期间收到的信号会进入未决状态...

// 查询并处理未决信号
sigset_t pending;
sigpending(&pending);
for (int signo = 1; signo <= 31; signo++)
{
    if (sigismember(&pending, signo))
    {
        cout << "处理未决信号:" << signo << endl;
        // 这里可以调用信号处理函数
    }
}

// 恢复屏蔽字
sigprocmask(SIG_SETMASK, &oldmask, NULL);

五、总结

通过这段代码和对原理的剖析,我们可以清晰地理解:

  • sigprocmask是进程 "主动控制哪些信号可以打扰自己" 的工具;
  • sigpending是进程 "查看有哪些被屏蔽的信号在排队等待处理" 的窗口;
  • 信号屏蔽与未决机制,是 Linux 系统中进程对信号 "精细管控" 的核心手段,在系统编程、服务器开发等场景中有着广泛应用。

希望这篇博客能帮大家穿透代码,真正理解信号屏蔽与未决的底层逻辑~

相关推荐
骥龙2 小时前
3.10、构建网络防线:防火墙、WAF 与蜜罐实战
服务器·网络·数据库·网络安全
九河云3 小时前
华为云 ECS 弹性伸缩技术:应对业务峰值的算力动态调度策略
大数据·服务器·人工智能·物联网·华为云
夜月yeyue3 小时前
Linux内高端内存
linux·运维·单片机·嵌入式硬件·ci/cd·硬件架构
云宏信息3 小时前
运维效率提升实战:如何用轻量化云管平台统一纳管与自动化日常资源操作
运维·服务器·网络·架构·云计算
猫豆~4 小时前
nginx实战-PHP——day2
linux·centos·云计算
杨云龙UP4 小时前
MySQL 自动备份与覆盖恢复实战:一套脚本搞定全库/按库备份恢复
linux·运维·数据库·sql·mysql
三小尛4 小时前
Linux的常见指令
linux
starvapour4 小时前
Ubuntu下sudo的免密操作
linux·ubuntu
sjg200104144 小时前
Deepin 20.9 误装gcc-8-base_8.4.0-1ubuntu1~16.04.1_amd64 后卸载
linux·运维·服务器
一帘忧梦4 小时前
linux 系统rcs脚本启动
linux·运维·lua