【进程信号】信号阻塞的原理

简介: 【进程信号】信号阻塞的原理

常见信号术语

  • 信号递达(Delivery):实际执行信号的处理动作
  • 信号未决(Pending):信号从产生到递达的中间状态
  • 信号阻塞(Block):进程可以选择阻塞某一个信号,被阻塞的的信号保持在未决状态,直到接触阻塞才会被递达。
  • 信号忽略:信号在递达之后的一种不作为。与阻塞不同,被阻塞的信号没有被递达。
  • 捕获信号:进程通过自定义信号处理程序来处理信号,而不是使用系统的默认动作

信号在内核中的表示

对于信号的学习,我们将其分为三个部分:产生信号、接收信号、处理信号。现在我们已经知道了信号是如何产生的,那我们的进程又是如何接收信号的呢?换句话说,一个进程是如何知道操作系统给它发信号了呢?

Pending表

其实在进程的PCB里面有一个信号位图(Signal Bitmap),用于表示有哪些发送给该进程但是还没有被处理的信号。每一个位都表示一种信号,如果有信号发送给该进程,那么这个位图对应的信号位就会置为1。假如给进程发送一个3号信号,那么这个位图的第3位就会置为1。因为信号的发送与进程实际处理之间是异步的,所以发送信号之后进程可能不会立即处理这个信号。

这个信号位图也被称为Pending(未决)表

有了Pending表,进程就能知道接收了哪些信号并准备处理信号,而一个信号是否要被处理,则需要观察该信号是否被阻塞。此时需要查看进程的Block表的内容。(处理信号相当于和响应信号)

Block表

同样的,PCB里面还有一个位图表示被进程阻塞的信号集。其每一个比特位都表示一个信号编号,如果是1则表示该对应信号被阻塞,即使信号到达,进程也不会处理。直到信号从该Block表中移除。Block表起到的作用实际上就是一个信号掩码(sig mask)。

sigset_t

观察上面图片,信号的未决表和阻塞表都是位图结构,且每一个比特位的位置都是表示信号的编号。于是我们可以统一用一个数据类型sigset_t(位图)来储存pending表和Block表。这个类型可以表示每个信号的有效或者无效状态。在阻塞信号集中,有效表示阻塞,在pending信号集中,有效表示未决。

我们可以通过signal函数来自定义一个信号的处理动作,实际上就是修改handler数组中元素内容·

信号集操作函数

操作系统提供了一些函数来管理信号集(sigset_t,包括Block和pending表),以下是常见的信号集操作函数(都包含在signal.h头文件中):

sigemptyset

功能:初始化一个空的信号集,即将信号集中所有信号清楚,也就是让信号集中的每一个比特位清0。

原型:

int sigemptyset(sigset_t *set);
  • set指向的信号集清空
  • 成功返回0,否则返回-1.

sigfillset

功能:初始化一个满的信号集,跟sigemptyset函数相反,让信号集中每一个比特位都置为1。

原型:

int sigfillset(sigset_t *set);
  • set指向的信号集填满
  • 成功返回0,否则-1.

sigaddset

功能:从信号集中添加一个指定的信号。

原型:

int sigaddset(sigset_t *set, int signum);
  • set指向的信号集中添加一个signum信号
  • 成功放回0,否则-1.

sigdelset

从信号集中删除一个指定的信号

原型:

int sigdelset(sigset_t* set,int signum);
  • set指向的信号集中删除一个signum信号
  • 成功返回0,失败则返回-1.

sigismember

功能:检查一个指定的信号是否在信号集中

原型:

int sigismember(const sigset_t *set,int signum);
  • 如果set指向的信号集中有signum信号就返回1,不在返回0,查看失败返回-1。

sigprocmask

功能:检查或者修改信号屏蔽字(阻塞信号集)。用来阻塞或者解阻信号。

跟上面的sigaddset和sigdelset函数不一样的是,sigprocmask可以修改进程的Block表

原型:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • how:指定操作类型。以下是常见的操作类型:
  • SIG_BLOCK:将set指向的信号集中的信号添加到阻塞信号集中
  • SIG_UNBLOCK:从阻塞信号集中移除set指向的信号集中的信号
  • SIG_SETMASK:将set指向的信号集设置为新的阻塞信号集
  • set指向一个sigset_t类型的对象,用于修改阻塞信号集。如果该参数为NULL,则表示不修改阻塞信号集。
  • oldset:指向一个sigset_t类型的对象,用于存储被set修改之前的阻塞信号集
  • 成功返回0,失败返回-1.

sigpending

功能:获取当前进程的未决信号集(pending表)

原型:

int sigpending(sigset_t *set);
  • set指向的信号集重置为当前进程的未决信号集
  • 成功返回0,否则-1.

程序测试

为了更好的理解信号未决和信号阻塞。现在给出一个代码测试,观察其运行结果:

#include <iostream>
#include <signal.h>

using namespace std;

void PrintSet(sigset_t *set)
{
    for (int i = 31; i >= 1; i--)
    {
        if (sigismember(set, i))
        {
            putchar('1');
        }
        else
        {
            putchar('0');
        }
    }
    puts("");
}

int main()
{
    sigset_t s, p;
    sigemptyset(&s);//初始化信号集
    sigaddset(&s, SIGINT);//添加2号
    sigprocmask(SIG_BLOCK, &s, NULL);
    while (1)
    {
        sigpending(&s);
        PrintSet(&s);
        sleep(1);
    }

    return 0;
}

代码中阻塞了信号2(SIGINT),也就是我们ctrl+c发出的信号。在接收到ctrl+c信号时,观察其Pending表的内容。

我们看到,按下ctrl+c后程序没有被终止,说明这个信号确实被阻塞了,然后未决表显示该信号一直出于未决状态,没有被递达。

相关文章
|
25天前
|
安全
【进程通信】信号的捕捉原理&&用户态与内核态的区别
【进程通信】信号的捕捉原理&&用户态与内核态的区别
|
2天前
|
算法 Linux 调度
Linux进程——进程的创建(fork的原理)
Linux进程——进程的创建(fork的原理)
|
15天前
|
Linux Shell
蓝易云 - 【Linux-Day8- 进程替换和信号】
这两个概念在Linux系统编程和shell脚本编写中都非常重要,理解它们可以帮助你更好地理解和控制Linux系统的行为。
23 9
|
25天前
|
存储 Unix Linux
【Linux 系统】进程信号 -- 详解(下)
【Linux 系统】进程信号 -- 详解(下)
|
25天前
|
NoSQL Linux Shell
【Linux 系统】进程信号 -- 详解(上)
【Linux 系统】进程信号 -- 详解(上)
|
25天前
|
NoSQL Linux Shell
【进程通信】了解信号以及信号的产生
【进程通信】了解信号以及信号的产生
|
9天前
|
消息中间件 存储 缓存
【嵌入式软件工程师面经】Linux系统编程(线程进程)
【嵌入式软件工程师面经】Linux系统编程(线程进程)
20 1
|
13天前
|
Linux Shell C语言
【Linux】进程终止
【Linux】进程终止
|
2天前
|
Linux Shell C语言
Linux进程控制——Linux进程程序替换
Linux进程控制——Linux进程程序替换
|
2天前
|
Linux 调度
Linux进程控制——Linux进程等待
Linux进程控制——Linux进程等待