【信号】信号保存 {管理信号的数据结构:pending,block,handler;信号集;sigset_t类型;信号集操作函数;相关系统调用:sigpending, sigprocmask}

一、相关概念

  • 信号递达:实际执行信号的处理动作称为信号递达(Delivery),包括默认,忽略和自定义捕捉。
  • 信号未决:信号从产生到递达之间的状态称为信号未决(Pending)。因此上面提到的信号位图又被称为Pending位图。
  • 信号阻塞(屏蔽):进程可以选择阻塞 (Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.

注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。


二、管理信号的数据结构

在这里插入图片描述

在进程PCB中,信号的pending位图、block位图和handler函数指针数组是用于管理信号处理的数据结构。

  1. Pending位图:每个进程都有一个pending位图,用于记录当前已经被该进程接收但尚未处理的信号(未决信号)。当一个信号被接收时,对应的位会被设置为1,表示该信号处于未决状态,直到信号递达才清除该标志。进程可以通过检查pending位图来确定是否有未处理的信号。在Linux中,可以使用sigpending函数来获取当前进程的pending位图。

  2. Block位图:每个进程都有一个block位图,用于指定当前被阻塞的信号。当一个信号被阻塞时,对应的位会被设置为1,表示该信号被阻塞,暂时不会被递达。进程可以通过设置block位图来控制哪些信号被阻塞,以避免进程在关键时刻被中断。在Linux中,可以使用sigprocmask函数来设置和获取当前进程的block位图。

  3. Handler函数指针数组:每个进程都有一个handler函数指针数组,用于管理信号处理函数。该数组的索引对应于信号的编号,数组的元素是函数指针,指向相应信号的处理函数。当进程接收到一个信号时,会根据信号的编号在handler函数指针数组中查找对应的处理函数,并调用该函数来处理信号。在Linux中,可以使用signalsigaction函数来设置和获取信号处理函数。

提示:

  • signal函数注册信号处理程序的原理:将信号处理函数的指针填入到handler数组对应信号编号的位置。
  • pending位图、block位图和handler函数指针数组是针对每个进程而言的,每个进程都有自己独立的位图和数组。这样可以实现不同进程对信号的独立管理和处理。

信号的处理过程:

在这里插入图片描述

在上图的例子中:

  • SIGHUP(1)信号未阻塞也未产生过,当它递达时执行默认处理动作。
  • SIGINT(2)信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT(3)信号产生过,也未被阻塞,所以直接递达处理。它的处理动作是用户自定义程序。通过信号编号索引handler数组中的函数指针,并执行自定义处理程序。

三、信号集

3.1 sigset_t类型

Pending位图和Block位图又被称为信号集。在Linux系统中,sigset_t类型是一个用于表示信号集的数据类型。它是一个位向量,每个位表示一个特定的信号。这个类型可以表示每个信号的“有效”或“无效”状态。

在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集(Pending信号集)中“有效”和“无效”的含义是该信号是否处于未决状态。

相关概念:

  • 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask)。
  • 未决信号集也叫做Pending信号集
  • Handler函数指针数组也叫做Handler处理方法表

3.2 信号集操作函数

系统不允许用户直接对信号集进行位操作,而是提供了一套对应的信号集操作函数。

在Linux系统中,可以使用以下函数来操作信号集:

  1. int sigemptyset(sigset_t *set):将信号集set初始化为空集,使其中所有信号对应的bit位清0,表示该信号集不包含任何有效的信号。成功返回0,失败返回-1。

  2. int sigfillset(sigset_t *set):将信号集set初始化为包含所有信号的集合,使其中所有信号对应的bit位置1。成功返回0,失败返回-1。

  3. int sigaddset(sigset_t *set, int signum):将信号signum添加到信号集set中。成功返回0,失败返回-1。

  4. int sigdelset(sigset_t *set, int signum):将信号signum从信号集set中删除。成功返回0,失败返回-1。

  5. int sigismember(const sigset_t *set, int signum)判断信号signum是否在信号集set中,如果在则返回1,不在则返回0,错误返回-1并设置errno。

这些函数都需要传入一个sigset_t类型的指针作为参数,用于指定要操作的信号集。其中,signum参数表示信号的编号,可以使用预定义的宏如SIGINT、SIGTERM等来表示具体的信号。

注意:在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。


3.3 相关的系统调用接口

3.3.1 sigpending

sigpending函数用于获取当前被阻塞的未决信号集。未决信号是指已经发送给进程但尚未被处理的信号。

函数原型如下:

int sigpending(sigset_t *set);

参数:

  1. 参数set是一个指向sigset_t类型的指针,用于存储获取到的未决信号集。(输出型)

返回值:

  • 成功:返回0
  • 失败:返回-1,并设置errno来指示错误的原因。

通常,sigpending函数用于在信号处理函数中查询当前被阻塞的未决信号集,以便根据需要进行相应的处理。

3.3.2 sigprocmask

sigprocmask函数用于获取或更改进程的信号屏蔽字,即设置或修改进程当前阻塞的信号集。

函数原型如下:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数:

  1. 参数how指定了信号屏蔽字的修改方式,可以是以下三个值之一:

    • SIG_BLOCK:将set指向的信号集中的信号添加到当前信号屏蔽字中。(mask = mask | set)
    • SIG_UNBLOCK:将set指向的信号集中的信号从当前信号屏蔽字中移除。(mask = mask & ~set)
    • SIG_SETMASK:将当前信号屏蔽字设置为set指向的信号集。(mask = set)
  2. 参数set是一个指向sigset_t类型的指针,如果set是非空指针,则用于指定要设置的信号集。(输入型)

  3. 参数oldset是一个指向sigset_t类型的指针,如果oldset是非空指针,则用于存储修改前的信号屏蔽字。如果不需要保存旧的信号屏蔽字,可以将其设置为NULL。(输出型)

返回值:

  • 成功:返回0
  • 失败:返回-1,并设置errno来指示错误的原因。

通过调用sigprocmask函数,可以控制进程对特定信号的阻塞和解除阻塞。这对于在特定情况下暂时屏蔽某些信号的处理,或者恢复之前的信号屏蔽状态非常有用。


四、测试程序

4.1 尝试捕捉所有普通信号

测试代码:

mysignal.cc:尝试捕捉[1,31]号,所有的普通信号

void CatchSig(int signum)
{
    cout << "捕获了一个信号:" << signum << endl;
}

void test1()
{
    for (int i = 1; i <= 31; ++i)
    {
        signal(i, CatchSig);
    }
    while(1);
}

sendsig.sh:编写shell脚本,发送[1,31]号信号

#!/bin/bash

i=1
id=$(pidof mysignal) //获取mysignal进程的PID
while [ $i -le 31 ]
do
    if [ $i -eq 9 ];then //跳过SIGKILL(9)信号
        let i++
        continue
    fi
    if [ $i -eq 19 ];then //跳过SIGSTOP(19)信号
        let i++
        continue
    fi
    kill -$i $id //kill命令发送信号
    echo "kill -$i $id"
    let i++
    sleep 1
done

运行结果:

在这里插入图片描述

SIGKILL(9)信号 和 SIGSTOP(19)信号 不能被捕捉;当发送19号信号暂停进程后,再发送SIGCONT(18)信号,进程会继续运行。

在这里插入图片描述


4.2 先阻塞2号信号,再解除阻塞,打印观察pending信号集

测试代码:先阻塞2号信号,再向该进程发送2号信号,最后解除阻塞。过程中,打印观察pending信号集。

void ShowPending(const sigset_t *ppset)
{
    for (int i = 1; i <= 31; ++i)
    {
        if (sigismember(ppset, i))
            cout << "1";
        else
            cout << "0";
    }

    cout << endl;
}

void test2()
{
    // 需要捕捉2号信号,否则解除阻塞后进程会立即退出。
    signal(2, CatchSig);
    // 定义信号集对象
    sigset_t bset, obset, pset;
    // 初始化信号集对象
    sigemptyset(&bset);
    sigemptyset(&bset);
    sigemptyset(&bset);
    // 将要进行屏蔽的信号添加到bset
    sigaddset(&bset, SIGINT);
    // 阻塞bset信号集 [默认情况进程不会对任何信号进行block]
    cout << "阻塞2号信号!" << endl;
    sigprocmask(SIG_BLOCK, &bset, &obset);
    int cnt = 0;

    // 打印观察pending信号集
    while (true)
    {
        // 获取当前进程的pending信号集
        sigpending(&pset);
        // 显示pending信号集中的没有被递达的信号
        ShowPending(&pset);
        sleep(1);
        ++cnt;
        // 10秒后解除阻塞
        if (cnt == 10)
        {
            // 默认情况下,解除对于2号信号的block的时候,确实会进行递达
            // 但是2号信号的默认处理动作是终止进程!
            // 需要对2号信号进行捕捉
            cout << "解除阻塞2号信号!" << endl;
            sigprocmask(SIG_SETMASK, &obset, nullptr);
        }
    }
}

运行结果:

在这里插入图片描述


4.3 尝试阻塞所有信号

测试代码:尝试阻塞[1,31]号,所有的普通信号

void test3()
{
    sigset_t bset, pset;
    // 初始化信号集对象将所有位置1,阻塞所有信号
    sigfillset(&bset);
    cout << "阻塞所有信号!" << endl;
    sigprocmask(SIG_BLOCK, &bset, nullptr);
    while (true)
    {
        sigpending(&pset);
        ShowPending(&pset);
        sleep(1);
    }
}

运行结果:

在这里插入图片描述

SIGKILL(9)信号 和 SIGSTOP(19)信号 不能被阻塞;当发送19号信号暂停进程后,再发送SIGCONT(18)信号,进程会继续运行。

在这里插入图片描述


4.4 小结

  1. SIGKILL(9)信号 和 SIGSTOP(19)信号 不能被捕捉,也不能被阻塞
  2. 当发送19号信号暂停进程后,再发送SIGCONT(18)信号,也能成功递达使进程继续运行。
  3. 发送信号的本质就是将目标进程的pending信号集对应信号的bit位置为1,如果该信号未被阻塞则直接递达,然后清除该信号的pending标志。
  4. 当一个信号被接收且被阻塞时,pending信号集对应的位会被设置为1,表示该信号处于未决状态,直到解除阻塞,信号才能递达,最后清除该信号的pending标志
  5. pending信号集的操作方法:
    • 所有的信号产生方式都能修改pending信号集;
    • 系统调用sigpending用于获取当前进程的pending信号集
  6. 信号屏蔽字的操作方法:
    • 系统调用sigprocmask用于获取或修改当前进程的信号屏蔽字
  7. Handler处理方法表的操作方法:
    • 系统调用signal用于修改当前进程的Handler处理方法表
    • 系统调用sigaction用于获取或修改当前进程的Handler处理方法表(下一章节介绍)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

芥末虾

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

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

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

打赏作者

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

抵扣说明:

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

余额充值