在Linux中,Block、Pending、Handler 是与信号处理相关的三个重要概念,它们分别描述了信号的屏蔽(Block)、等待处理(Pending)和相应处理函数(Handler) 的情况。
概念: Block 表示对某个信号进行屏蔽,使其在进程中暂时无法被接收。
组成: 信号屏蔽集(Signal Mask)是由一组位表示的数据结构,每一位对应一个信号。当某个信号的位被设置(为1)时,表示该信号被屏蔽;当被清零(为0)时,表示该信号不被屏蔽。进程可以通过修改信号屏蔽集来控制屏蔽和解除屏蔽的信号。
概念: Pending 表示已经发送给进程但尚未被处理的信号。
组成: 待处理信号集(Pending Signal Set)是一个由一组位表示的数据结构,类似于信号屏蔽集。当某个信号的位被设置时(为1),表示该信号当前在待处理状态;清零(为0)则表示该信号当前不在待处理状态。进程可以通过系统调用获取当前待处理信号的集合。
概念: Handler 表示与信号相关联的处理函数,用于在接收到信号时执行特定的操作。
组成: 为了处理各种信号,内核维护了一个信号处理函数表。这个表以信号编号为索引,对应的元素是与该信号关联的处理函数的地址。每个处理函数都是用户定义的,用于在接收到相应信号时执行特定的操作。
sigset_t 是一个数据结构,用于表示信号集合,它在 C 语言中通常是一个位图或类似的数据结构。该数据结构的目的是管理进程中各种信号的状态,包括哪些信号被阻塞、哪些信号处于未决状态等。
通常来说,sigset_t 是一个包含足够位数的整数类型,每个位代表一个信号。
位的值表示信号的状态,例如,1 表示信号被阻塞或处于未决状态,0 表示信号未被阻塞或未处于未决状态。
#include
int sigemptyset(sigset_t *set);功能: 初始化指定的信号集,将其中所有信号的对应位清零。
参数: set 是指向 sigset_t 类型的指针,表示待初始化的信号集。
返回值: 成功返回 0,失败返回 -1。#include
int sigfillset(sigset_t *set);功能: 初始化指定的信号集,将其中所有信号的对应位置位,表示该信号集包含系统支持的所有信号。
参数: set 是指向 sigset_t 类型的指针,表示待初始化的信号集。
返回值: 成功返回 0,失败返回 -1。#include
int sigaddset(sigset_t *set, int signo);功能: 将指定的信号 signo 添加到信号集 set 中,将该信号的对应位置为 1,表示该信号现在处于“有效”状态。。
参数: set 是指向 sigset_t 类型的指针,表示待修改的信号集;
signo 是待添加的信号。
返回值: 成功返回 0,失败返回 -1。#include
int sigdelset(sigset_t *set, int signo);功能: 从指定的信号集中删除某个有效信号。
参数: set 是指向 sigset_t 类型的指针,表示待修改的信号集;
signo 是待删除的信号。
返回值: 成功返回 0,失败返回 -1。#include
int sigismember(const sigset_t *set, int signo);功能: 判断指定的信号是否是信号集的成员。
参数: set 是指向 sigset_t 类型的指针,表示待查询的信号集;
signo 是待检查的信号。
返回值: 若成功,包含则返回 1,不包含则返回 0;失败返回 -1。
sigprocmask 函数允许程序员读取或更改进程的信号屏蔽字,从而控制信号的阻塞状态。同时,sigpending 函数允许了解那些在进程中产生但被阻塞的信号。
输入型参数:
how操作方式:
SIG_BLOCK:将 set 中的信号添加到当前的信号屏蔽字中。
相当于 mask = mask | set,即将 set 中的信号添加到当前的信号屏蔽字中。SIG_UNBLOCK:从当前的信号屏蔽字中解除 set 中的信号。
相当于 mask = mask & ~set,即将 set 中的信号从当前的信号屏蔽字中解除。SIG_SETMASK:将当前的信号屏蔽字设置为 set 中的值。
相当于 mask = set,即将当前的信号屏蔽字设置为 set 中指定的值。
set参数:
set 参数是一个指向 sigset_t 类型的指针,用于指定要进行信号操作的信号集。
输出型参数:
oset 参数是一个指向 sigset_t 类型的指针,用于存储原始的信号屏蔽字。
返回值:
sigprocmask 函数的返回值表示操作是否成功。如果成功,返回 0;如果出错,返回 -1。
sigpending 函数获取的就是当前进程的 pending
set参数:
set 参数是一个指向 sigset_t 类型的指针,用于存储当前被阻塞但仍处于未决状态的信号。
代码示例
#include
#include
#include
using namespace std;
// 打印信号集合的函数
void PrintPending(sigset_t &pending)
{
// 逐个检查信号位,打印二进制表示
for (int signo = 31; signo >= 1; signo--)
if (sigismember(&pending, signo))
cout << "1";
else
cout << "0";
cout << "\n";
}
// 信号处理函数
void handler(int signo)
{
// 输出捕获到的信号
cout << "捕获到信号: " << signo << endl;
}
int main()
{
// 初始化信号集
sigset_t bset, oset;
sigemptyset(&bset);
sigemptyset(&oset);
// 将所有信号都添加到信号集中,实现屏蔽所有信号的效果
for (int i = 1; i <= 31; i++)
{
sigaddset(&bset, i);
}
// 将进程的当前信号屏蔽字设置为屏蔽所有信号的集合,并保存原始信号屏蔽字
sigprocmask(SIG_SETMASK, &bset, &oset);
// 通过信号处理函数捕获信号
signal(SIGINT, handler);
// 循环,从 0 到 31 依次给自己发送信号
for (int signo = 0; signo <= 31; signo++)
{
// 获取当前未决状态的信号集合
sigset_t pending;
int n = sigpending(&pending);
if (n < 0)
continue;
// 打印未决状态的信号集合
PrintPending(pending);
// 给自己发送信号,注意排除 SIGKILL 和 SIGSTOP 信号(9 和 19)
if (signo != 9 && signo != 19)
{
raise(signo); // 或者使用 kill(getpid(), signo);
usleep(100000); // 等待 100 毫秒
}
// 等待1秒
sleep(1);
}
return 0;
}
SIGKILL (9): 这是一个无法被捕获、阻塞或忽略的信号。它的主要作用是立即**终止目标进程,且无法被该进程阻止或捕获。因为 SIGKILL 是一个强制终止信号,所以在正常情况下应该避免将其发送给进程,以防止可能导致数据损坏或其他不良后果。
SIGSTOP (19): 这是一个使目标进程暂停执行的信号。与 SIGKILL 不同,SIGSTOP 可以被捕获,但无法被阻塞或忽略。它通常用于暂停进程以进行调试或其他目的。
SIGCONT (18):主要用于继续恢复被暂停的进程,通常是由于收到SIGSTOP或SIGTSTP信号。这是为了实现进程的挂起和恢复功能。如果SIGCONT可以被阻塞,那么进程将无法在被暂停后继续执行。
sigaction函数用于设置信号的处理动作,其原型如下:
#include
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
signo:指定要处理的信号编号。
act:指定新的信号处理行为。
oldact:用于存储原来的信号处理行为,可为nullptr。
sa_handler:一个函数指针,指定了信号的处理函数。
sa_mask:sa_mask是 struct sigaction 结构它是一个信号集,用于指定在执行信号处理函数时需要被阻塞的信号。也就是说,在处理当前信号时,sa_mask 中指定的信号会被阻塞,防止它们干扰当前信号的处理。
sa_flags 字段是一个整数,用于指定一些标志,影响信号处理的行为,这里设置成0即可,这里不做详细介绍。 其他字段也不做介绍
代码示例
#include
#include
#include
#include
// 打印当前未决信号集合的函数
void PrintPending()
{
sigset_t set;
sigpending(&set);
// 遍历输出信号集合
for (int signo = 1; signo <= 31; signo++)
if (sigismember(&set, signo))
std::cout << "1";
else
std::cout << "0";
std::cout << "\n";
}
// 信号处理函数
void handler(int signo)
{
std::cout << "捕获到信号,信号编号:" << signo << std::endl;
// 在信号处理函数中,循环打印当前未决信号集合
while (true)
{
PrintPending();
sleep(1);
}
}
int main()
{
// 创建 sigaction 结构体变量 act 和 oact,用于设置和保存信号处理方式
struct sigaction act, oact;
memset(&act, 0, sizeof(act));
memset(&oact, 0, sizeof(oact));
// 初始化信号集合 act.sa_mask,清空其中的所有信号位
sigemptyset(&act.sa_mask);
// 设置信号集合 act.sa_mask,其中包括信号 1、3、4,同时屏蔽1,3,4号信号
sigaddset(&act.sa_mask, 1);
sigaddset(&act.sa_mask, 3);
sigaddset(&act.sa_mask, 4);
// 设置信号处理函数为 handler
act.sa_handler = handler; // 也可以设置为 SIG_IGN(忽略信号)或 SIG_DFL(默认处理)
// 使用 sigaction 函数将信号 2(SIGINT)的处理方式设置为 act,保存原有处理方式到 oact
sigaction(2, &act, &oact);
// 循环输出当前进程的PID,可以观察进程运行
while (true)
{
std::cout << "我是进程:" << getpid() << std::endl;
sleep(1);
}
return 0;
}
信号处理函数执行时,当前信号会被加入进程的信号屏蔽字,防止同类信号再次中断处理。函数执行结束后,内核自动还原原来的信号屏蔽字状态。如果需要屏蔽其他信号,可以使用 sa_mask 字段,在处理结束后同样会自动还原。