Linux 信号保存

1. 信号的递达、阻塞和未决状态

在Linux系统中,信号的保存主要涉及到信号的递达、阻塞和未决状态。当信号产生时,进程不一定会立即处理它,而是可能在合适的时机进行处理。在信号递达之前,进程需要在内核中保存信号的状态,以便后续处理。

  • 信号递达(Delivery)指的是实际执行信号的处理动作。
  • 信号未决(Pending)是指信号从产生到递达之间的状态,在这段时间内,信号需要在内核中被保存。
  • 信号阻塞(Block)是进程选择不立即处理某个信号的状态,阻塞的信号会保持在未决状态,直到进程解除对该信号的阻塞。

Linux操作系统通过进程的内核数据结构来实现信号的保存。进程的PCB(Process Control Block)中包含了用于信号处理的三张表:

  • pending表:通过位图存储,每个比特位代表一个信号编号,比特位的内容表示信号是否已产生但尚未递达,1表示信号已产生,0表示信号未产生。
  • block表:同样通过位图存储,表示进程是否阻塞了特定的信号,1表示已阻塞信号,0表示未阻塞信号。
  • handler表:是一个函数指针数组,每个元素对应一个信号编号,存储了对应信号的处理函数。

当信号产生时,即使进程当前不处理信号,内核也会在pending表中标记该信号为未决状态,即pending表中的对应位会被设置为1。当进程从内核态返回用户态时,会检查pending表,并发送未决信号进行处理。如果信号被阻塞,则其在block表中的对应位会被设置为1,信号会保持在未决状态,直到阻塞被取消。

2. 信号集和信号操作函数

2.1 sigset_t

在Linux操作系统中,信号集是一个数据结构,用于表示一组信号。信号集通常由sigset_t类型的变量表示,它可以存储多个信号的状态,例如信号是否被阻塞或是否在未决状态,即该类型处理了block和 pending表。

在Linux中的源码如下:

typedef struct
{
  unsigned long int __val[_SIGSET_NWORDS];//存储位图
} __sigset_t;//注意这前面有两个下划线


typedef __sigset_t sigset_t;//typedef后,前面没有下划线了

2.2 信号操作函数

信号集由一系列位字组成,每个位对应一个信号。通过对这些位进行设置(置1)或清除(置0),可以控制进程对特定信号的响应。

  • int sigemptyset(sigset_t *set):将信号集中的所有比特位变为0,即清空信号集
  • int sigfillset(sigset_t *set):将信号集中的所有比特位变为1,即将所有信号添加到信号集中
  • int sigaddset(sigset_t *set, int signum):将信号集中的第 signum 个比特位变为1,即将signum 信号添加到信号集中
  • int sigdelset(sigset_t *set, int signum):将信号集中的第 signum 个比特位变为0,从信号集中移除signum信号
  • int sigismember(const sigset_t *set, int signum):检查特定信号是否存在于信号集中。

前四个函数的返回值都是:如果成功返回0,失败返回-1。

注意,我们通过这个函数操作的信号集,既不是block也不是pending,它目前只是一个进程中的变量而已。接下来要做的,就是把我们自己创建并设置的信号集,与block和pending交互。

2.3 信号集的应用

sigporcmask

sigprocmask 是用于管理进程信号block表的系统调用以及库函数。block表中的信号被进程主动阻塞,即在这段时间内,即使这些信号被触发,它们也不会立即被进程接收。

参数:

  • how 参数指定了如何修改block表:

    • SIG_BLOCK:将set中为1的比特位添加到block表中,即将 set 中指定的信号集合添加到当前的block表中。
    • SIG_UNBLOCK:将set中为1的比特位从block表中删除,即从当前block表中移除 set 中指定的信号集合。
    • SIG_SETMASK:将block表设置为 set 信号集合。
  • set 参数是一个指向 sigset_t 类型的指针,用于指定新的block表。

  • oldset 参数是一个指向 sigset_t 类型的指针,如果不为空,则函数会将修改前的block表的副本存储在此处。

返回值:如果函数执行成功,它返回0;如果发生错误,则返回-1。

sigpending

sigpending 是用于检索当前进程中信号pending表的系统调用。pending表中的信号是在信号被触发时产生的,但由于进程当前的信号屏蔽字中对这些信号设置了屏蔽位,因此它们被阻塞,等待进程解除对这些信号的屏蔽后才能被递送。

参数:

  • set 是一个指向 sigset_t 类型的指针,用于存放进程当前pending表。

返回值:如果函数执行成功,它返回0;如果发生错误,则返回-1。

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
int main()
{
    sigset_t set, oset;
    sigemptyset(&set);
    sigemptyset(&oset);
    sigaddset(&set, 2);
    sigprocmask(SIG_BLOCK, &set, &oset);
    while (1)
    {
        sigset_t pending_set;
        sigpending(&pending_set);
        for (int i = 31; i > 0; --i)
        {
            if (sigismember(&pending_set, i))
                cout << 1;
            else
                cout << 0;
        }
        cout << endl;
        sleep(1);
    }
    return 0;
}

上述代码中,我们先定义了两个sigset_t 类型的变量 set 和 oset ,而后通过 sigaddset 函数把2号信号添加到信号集 set 中,随后通过 sigprocmask 函数将 set 添加到 block 表上,对2号信号进行阻塞,之后我们循环从 31 到 0 依次检测 pending 表中的信号,若我们按下 Ctrl + c 发送2号信号就会发生阻塞,pending 表上第二个 bit 位上就会一直为1。

sigaction

sigaction 是一个在类Unix操作系统中用于设置或查询信号处理函数的系统调用。它提供了比传统的 signal 函数更多的控制选项,并且是POSIX标准的一部分。通过 sigaction可以指定信号的处理函数、信号掩码(block表)以及信号处理的额外标志。

参数:

  • signum:要操作的信号编号。
  • act:指向 struct sigaction 结构的指针,包含了新的信号处理信息。
  • oldact:指向 struct sigaction 结构的指针,如果不为空,则保存旧的信号处理信息。

struct sigaction 结构体的源码如下:

struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};
  • void (*sa_handler)(int):信号的处理函数指针,如果未设置 SA_SIGINFO 标志,则使用此函数。
  • void (*sa_sigaction)(int, siginfo_t *, void *):信号的处理函数指针,如果设置了 SA_SIGINFO 标志,则使用此函数,它可以接收更多关于信号的信息。
  • sigset_t sa_mask:信号掩码,在信号处理函数执行期间阻止的信号集,即 block 表。
  • int sa_flags:信号处理的标志,可以包含以下值的按位或组合:
    • SA_RESTART:使被信号打断的系统调用自动重新发起。
    • SA_NOCLDSTOP:使父进程在子进程停止时不会收到 SIGCHLD 信号。
    • SA_NOCLDWAIT:使父进程在子进程终止时不会收到 SIGCHLD 信号,并且子进程不会变成僵尸进程。
    • SA_NODEFER:使信号处理函数执行期间对该信号的屏蔽无效。
    • SA_RESETHAND:信号处理后重置为默认处理方式。
    • SA_SIGINFO:使用 sa_sigaction 作为信号处理函数。
  • void (*sa_restorer)(void):已废弃,不应使用。

一般了解void (*sa_handler)(int)和sigset_t sa_mask即可。实际上可以把sigaction看成signal和sigprocmask的结合,其既可以自定义信号处理函数,还可以额外设置处理信号时的block表。

void PrintBlock()
{
    sigset_t tmp, block;
    sigemptyset(&tmp);
    sigemptyset(&block);
    sigprocmask(SIG_BLOCK, &tmp, &block);
    for (int i = 31; i > 0; --i)
    {
        if (sigismember(&block, i))
            cout << 1;
        else
            cout << 0;
    }
    cout << endl;
}
void handler(int sig)
{
    cout << "get signal: " << sig << endl;
    PrintBlock();
}
int main()
{
    cout<<"before set signal, block:" << endl;
    PrintBlock();
    cout<<"after set signal, block:" << endl;
    struct sigaction act, oact;
    sigemptyset(&act.sa_mask);
    sigemptyset(&oact.sa_mask);
    act.sa_handler = handler;
    for (int i = 1; i < 32; ++i)
    {
        sigaddset(&act.sa_mask, i);
    }
    sigaction(2, &act, &oact);
    raise(2);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

我要满血复活

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

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

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

打赏作者

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

抵扣说明:

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

余额充值