首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Linux】:进程信号(信号保存 & 信号处理)

【Linux】:进程信号(信号保存 & 信号处理)

作者头像
IsLand1314
发布2024-11-19 09:46:24
发布2024-11-19 09:46:24
1.1K00
代码可运行
举报
文章被收录于专栏:学习之路学习之路
运行总次数:0
代码可运行

一、信号保存

🐸 1. 信号其他相关的基本概念
  • 实际执行信号的处理动作称为 信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为 信号未决(Pending)
  • 进程可以选择 阻塞 (Block) 某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作
  • 注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作
🐸 2. 在内核中的表示

如下是信号在内核中的表示示意图:

在这个阶段有以下几种情况:

💢 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。

  • SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler
    • 信号未决:信号产生后,在未被处理之前,处于未决状态。这意味着信号已经被发送,但目标进程尚未对其作出响应。操作系统会检查目标进程的Pending表,确定哪些信号处于未决状态(每个进程都有一个Pending位图,用于记录哪些信号处于未决状态。这个位图由32个比特位组成,分别代表32个不同的信号,如果对应的比特位为1,表示该信号已经产生但尚未处理)
    • 信号阻塞:如果目标进程阻塞了某些信号,那么这些信号会保持在未决状态,直到进程解除对这些信号的阻塞(与Pending位图类似,Block位图用于记录哪些信号被进程阻塞。当信号被阻塞时,对应的比特位会被设置为1)

还有一个函数指针表示处理动作

  • handler表:是一个函数指针数组,每个下标都是一个信号的执行方式(有31个普通信号,信号的编号就是数组的下标,可以采用信号编号,索引信号处理方法!)如signal函数在进行信号捕捉的时候,其第二个参数就是,提供给handler的

信号阻塞过程如下:

  • 如果进程选择阻塞某个信号,操作系统会在block表中设置对应信号的比特位为1。此时,即使信号已经产生(pending表中对应比特位为1),进程也不会立即处理该信号
  • 被阻塞的信号将保持在pending表中,直到进程解除对该信号的阻塞(即block表中对应比特位被重置为0)
  • 注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

💦 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?

🎐 Linux的实现:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里

信号阻塞和未决的区别

  1. 信号阻塞(Blocking):是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。它使得系统暂时保留信号留待以后发送。阻塞只是暂时的,通常用于防止信号打断敏感的操作。
  2. 信号未决(Pending):是一种状态,指的是从信号的产生到信号被处理前的这一段时间。信号产生后,如果未被处理且没有被阻塞,则处于未决状态,等待被处理。

二、信号处理

🐇 1. sigtest_t

从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。

  • 因此:未决和阻塞标志可以用相同的数据类型 sigset_t 来存储,sigset_t 称为信号集

这个类型可以表示每个信号的 “有效” “无效” 状态

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

💞 阻塞信号集也叫做当前进程的 信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略

🔥 注意:该类型只在 Linux 系统上有效,是 Linux 给用户提供的一个用户级的数据类型

🐇 2. 信号集操作函数
🥑 2.1 基本认识

🌈 sigset_t 类型对于每种信号用一个 bit 表示 “有效” 或 “无效” 状态,至于这个类型内部如何存储这些 bit 则依赖于系统实现, 从使用者的角度是不必关心的,使用者只能调用以下函数来操作 sigset_t 变量, 而不应该对它的内部数据做任何解释

  • 比如用 printf 直接打印sigset_t变量是没有意义的
代码语言:javascript
代码运行次数:0
运行
复制
#include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

  • 函数 sigemptyset 初始化 set 所指向的信号集,使其中所有信号的对应 bit 清零,表示该信号集不包含任何有效信号
  • 函数 sigfillset 初始化 set 所指向的信号集,使其中所有信号的对应 bit 置位,表示 该信号集的有效信号包括系统支持的所有信号

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

这四个函数都是成功返回0,出错返回-1。

  • sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。
🥑 2.2 sigprocmask

调用函数 sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)

代码语言:javascript
代码运行次数:0
运行
复制
#include <signal.h>

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

参数说明

1) how: 用于指定如何修改信号屏蔽字的操作方式。它可以取以下几个值之一:

  • SIG_BLOCK:将信号集 set 中的信号添加到当前信号屏蔽字中,阻止这些信号的传
  • SIG_UNBLOCK: 从当前信号屏蔽字中删除信号集 set 中的信号,允许这些信号的传递。
  • SIG_SETMASK:将信号屏蔽字设置为 set 中的信号集,完全替换掉当前的屏蔽字。

2)set:指向一个 sigset_t 类型的信号集,表示需要操作的信号集合。可以使用 sigemptyset()、sigfillset()、sigaddset() 等函数来操作这个集合。

3)oldset: 指向一个 sigset_t 类型的变量,用于保存调用 sigprocmask 前的原始信号屏蔽字(如果 oldset 不为 NULL)。如果不关心原始的屏蔽字,可以将其设置为 NULL。

返回值:成功时,返回 0,失败时,返回 -1,并将 errno 设置为相应的错误代码。

🧃🥝🥥🫒 上面函数使用分析

  • 如果 oset 是非空指针, 则读取进程的当前信号屏蔽字通过oset参数传出
  • 如果 set 是非空指针, 则更改进程的信号屏蔽字, 参数 how 指示如何更改。
  • 如果 oset 和 set 都是非空指针, 则先将原来的信号屏蔽字备份到 oset里,然后根据 set 和 how 参数更改信号屏蔽字。

假设当前的信号屏蔽字为 mask,下表说明了 how 参数的可选值

如果调用 sigprocmask 解除了对当前若干个未决信号的阻塞,则在 sigprocmask 返回前,至少将其中⼀个信号递达。

下面是一个使用 sigprocmask 的简单示例,演示如何阻塞和解除阻塞信号

代码语言:javascript
代码运行次数:0
运行
复制
#include<iostream>
#include<signal.h>
#include<unistd.h>
#include <sys/wait.h>

void handle_signal(int sig) {
    printf("Signal %d received\n", sig);
}

int main() {
    sigset_t set, oldset;

    // 初始化信号集,清空
    sigemptyset(&set);
    // 将 SIGINT 信号添加到信号集
    sigaddset(&set, SIGINT);

    // 阻塞 SIGINT 信号
    if (sigprocmask(SIG_BLOCK, &set, &oldset) == -1) {
        perror("sigprocmask");
        return 1;
    }

    printf("SIGINT blocked, sleeping for 5 seconds...\n");
    sleep(5);

    // 解除阻塞 SIGINT 信号
    if (sigprocmask(SIG_SETMASK, &oldset, NULL) == -1) {
        perror("sigprocmask");
        return 1;
    }

    printf("SIGINT unblocked, sleeping for another 5 seconds...\n");
    sleep(5);

    return 0;
}
🥑 2.3 sigpending 2.4 使用样例

(检查pending信号集,获取当前进程pending位图)

代码语言:javascript
代码运行次数:0
运行
复制
#include <signal.h>  

int sigpending(sigset_t *set);
  • 参数:set 是一个指向 sigset_t 类型的指针,用于存储当前进程的未决信号集合。
  • 返回值:函数调用成功时返回 0,失败时返回 -1,并设置 errno 以指示错误原因。
🥑 2.4 使用样例

打印 pending 表并且实现对 2 号信号的屏蔽

代码语言:javascript
代码运行次数:0
运行
复制
void PrintPending(const sigset_t &pending)
{
    std::cout << "curr pending list [" << getpid()  << "]" ;
    for(int signo = 31; signo > 0; signo--)
    {
        if(sigismember(&pending, signo))
        {
            std::cout << 1 ;
        }
        else
        {
            std::cout << 0 ;
        }
    }
    std::cout << std::endl;
}


int main()
{
    // 1. 对 2 号信号进行屏蔽
    sigset_t block, oblock;
    sigemptyset(&block);
    sigemptyset(&oblock);

    // 1.1. 添加 2 号信号
    // 我们有没有把 2 号信号的屏蔽,设置进入内核中,只是再用户栈上设置了 block 的位图结构
    // 没有设置到内核中
    sigaddset(&block, 2);

    // 1.2 设置进入内核
    sigprocmask(SIG_SETMASK, &block, &oblock);                         

    while(true)
    {
        // 2. 如何获取 pending 表
        sigset_t pending;
        sigpending(&pending);

        // 2.1 打印
        PrintPending(pending);
        sleep(3);
    }

    return 0;
}

运行如下:

解除对 2 号信号的屏蔽,修改如下:

运行如下:

我们发现:后续输出没有了,原因:由于 2 号信号的默认动作是终止进程,一旦解除屏蔽,处理 2 号信号执行默认动作,就把自己干掉了,因此还需要做修改

代码语言:javascript
代码运行次数:0
运行
复制
void non_handler(int signo)
{
    std::cout << "处理:" << signo << std::endl;
}

int main()
{
    // 忽略 2 号信号的两种方式
    // 方式一:系统调用
    ::signal(2, SIG_IGN); 
    // 方式二:动态捕捉
    ::signal(2, non_handler);

    // 1. 对 2 号信号进行屏蔽
    ......
}

运行如下:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-11-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、信号保存
    • 🐸 1. 信号其他相关的基本概念
    • 🐸 2. 在内核中的表示
  • 二、信号处理
    • 🐇 1. sigtest_t
    • 🐇 2. 信号集操作函数
      • 🥑 2.1 基本认识
      • 🥑 2.2 sigprocmask
      • 🥑 2.3 sigpending 2.4 使用样例
      • 🥑 2.4 使用样例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档