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;
}