Linux对于block阻塞信号的理解

        在深入探讨Linux系统中信号(Signals)的阻塞机制前,我们有必要先回顾信号的基础知识,进而深入理解信号阻塞这一高级特性及其在系统编程中的重要作用。

信号基础:

系统间的轻量级通信

        信号是Unix/Linux系统中进程间通信的一种基本手段,它是一种异步通知机制,允许操作系统或一个进程向另一个进程发送简短的消息。信号的种类繁多,从基本的SIGINT(用户中断,如Ctrl+C触发)到SIGKILL(不可阻塞且无法忽略的进程终止信号),每一种信号都有其特定的意义和默认处理行为。信号机制的引入,旨在提供一种快速、非侵入式的进程控制手段,使进程能够对外部事件作出响应,如优雅地终止、挂起或恢复执行等。

底层原理:信号内核中的表示

信号在内核中的表示示意图

阻塞Block:信号处理的暂停键

        在信号处理的复杂世界中,阻塞信号是一项关键技术,它赋予了进程对特定信号响应的控制权。当一个信号被进程阻塞时,即便该信号被发送给进程,也不会立即被进程处理。相反,它被临时存储在内核维护的一个未决信号队列中,直到进程解除对该信号的阻塞为止。这种机制使得进程能在执行关键操作时免受外部信号的干扰,保证操作的原子性和完整性。

未决信号:等待处理的队列

        当一个被阻塞的信号被发送给进程时,它不会立即执行默认动作或调用用户定义的处理函数,而是进入未决状态。内核维护着一个未决信号队列,确保信号在进程准备好处理之前得以保留。一旦进程解除对某信号的阻塞,内核会检查该信号是否存在于未决队列中,如果存在,则立即将信号递送给进程,触发相应的处理逻辑。

阻塞的机制与实践

调用

sigset_t

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

sigprocmask

        sigprocmask系统调用,进程可以控制其信号屏蔽字(signal mask),从而决定哪些信号应当被阻塞。信号屏蔽字是一个位图,每一位对应一个信号编号,位为1表示对应的信号被阻塞,位为0则表示允许该信号传递。通过设置不同的参数(如`SIG_BLOCK`、`SIG_UNBLOCK`或`SIG_SETMASK`),sigprocmask允许进程添加或移除信号到屏蔽字中,或是直接替换当前的信号屏蔽状态。

代码演示

        下面是一个C++实现的简单代码。这段C++代码展示了如何在Linux环境下使用信号处理机制,具体是监控一个进程的未决信号状态,并在特定时刻解除信号的阻塞。以下是详细过程:

#include <stdio.h>
#include <signal.h> // 信号处理相关头文件
#include <unistd.h> // 提供了sleep等与进程控制相关的函数
#include <stdlib.h> // 提供了exit等函数
#include <iostream> // C++标准输入输出库

using namespace std;

// 函数:打印当前进程的未决信号状态
void PrintPending(sigset_t &pending) {
    cout << "PrintPending:";
    // 遍历31至1(信号编号范围通常是1-64,这里简化处理)
    for (int i = 31; i >= 1; i--) {
        // 如果信号i在未决信号集中
        if (sigismember(&pending, i)) {
            cout << "1"; // 输出1表示该信号在未决状态
        } else {
            cout << "0"; // 输出0表示该信号未被设置
        }
    }
    cout << "\n\n"; // 换行
}

// 信号处理函数,当接收到指定信号时被调用
void handler(int signo) {
    cout << "catch a signo:" << signo << endl; // 输出捕获到的信号编号
}

int main() {
    // 设置信号2(SIGINT)的处理函数为handler
    signal(2, handler);

    // 定义三个信号集:bset用于设置要阻塞的信号,oset用于保存原始的信号屏蔽状态,pending用于存储当前未决的信号
    sigset_t bset, oset, pending;
    // 初始化信号集bset和oset为空集
    sigemptyset(&bset);
    sigemptyset(&oset);
    // 将信号2添加到bset中,准备阻塞它
    sigaddset(&bset, 2);
    // 使用sigprocmask设置信号屏蔽,参数SIG_SETMASK表示替换当前的信号屏蔽字为bset,并将原来的屏蔽字存入oset
    sigprocmask(SIG_SETMASK, &bset, &oset);

    // 计数器,用于控制何时解除信号阻塞
    int cnt = 0;
    while (true) {
        // 调用sigpending获取当前进程的未决信号集
        int n = sigpending(&pending);
        if (n < 0) continue; // 如果出错,则跳过本次循环
        // 打印当前的未决信号状态
        PrintPending(pending);
        // 睡眠1秒,模拟进程的其他操作
        sleep(1);
        cnt++; // 计数器加1
        // 当计数达到20次时,解除信号2的阻塞
        if (cnt == 20) {
            cout << "unblock 2 signo\n";
            // 使用oset中保存的原始信号屏蔽字解除对信号2的阻塞
            sigprocmask(SIG_SETMASK, &oset, nullptr);
        }
    }

    return 0;
}

        这段代码首先注册了一个信号处理器来处理信号2(SIGINT,通常由Ctrl+C触发,亦可使用kill -2 pid触发),然后设置了信号屏蔽,阻止信号2到达进程。程序进入一个无限循环,每秒检查一次未决信号集,并打印其状态。当循环计数达到20次后,解除对信号2的阻塞,此时如果向该进程发送SIGINT信号,应该能看到信号处理器被调用。 

阻塞的实用价值

        信号阻塞机制在多线程编程、守护进程设计、系统稳定性保障等方面发挥着重要作用。例如,当一个进程正在执行关键资源的锁定或解锁操作时,若不加以控制,意外的信号中断可能导致资源状态不一致,引发死锁或其他严重错误。通过临时阻塞某些信号,可以确保这些敏感操作的连续性和正确性。

        此外,在实现复杂信号处理逻辑时,阻塞某些信号直到当前处理逻辑完成,可以避免信号处理函数之间的竞态条件,保持程序行为的一致性和可预测性。

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶孤程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值