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 系统中进程对信号 "精细管控" 的核心手段,在系统编程、服务器开发等场景中有着广泛应用。

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

相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒2 天前
TShark:基础知识
linux
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334663 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 天前
linux 拷贝文件或目录到指定的位置
linux
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux