云计算百科
云计算领域专业知识百科平台

【Linux笔记】——进程信号的保存

🔥个人主页🔥:孤寂大仙V 🌈收录专栏🌈:Linux 🌹往期回顾🌹:【Linux笔记】——进程信号的产生 🔖流水不争,争的是滔滔不


  • 一、信号的相关概念
  • 二、信号集操作函数
    • sigset_t
    • 信号集操作函数
    • sigprocmask
    • sigpending
  • term 和 core

一、信号的相关概念

  • 实际执行信号的处理动作称为信号递达(Delivery)。
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择**阻塞(Block)**某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才能执行递达的动作。
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
  • 这些概念都是内核完成的 在这里插入图片描述 如图,block就是阻塞,pending就是未决,handler是信号处理函数就是信号来了以后“干具体事情的地方”,它才是最终决定信号怎么被响应的“执行动作”。

    对block和pending进行一个比喻,block可以理解为拦着不让外卖员敲门(不想收外卖时挂个禁止通行的牌子),而pending可以理解为外卖员到了,但是被门外拦着(外卖员在门口堵着)。

    简单梳理一下handler触发的整个流程:

  • 信号来了(内核识别到,不管是硬件还是软件发来的信号)
  • 判断block:block是一个信号信号阻塞位图,每一位对应一个信号标号1表示阻塞(block了,不让送达)0表示不阻塞(可以送达)。
  • 检测block位图中,信号对应的那一位,如果是1,则信号是挂到pending位图中,若是0,则理解开始信号处理流程,调用用户注册的handler函数。
  • 注意: 信号来了。不管block了没有,这个信号都会被挂到pending位图中(置为1),也就是说信号来了就记下来了pending代表“来了”。 然后才会去看block位图,如果这个信号在block中被阻塞,信号就会一直挂在pending中,等待解封。如果block没有阻塞,就交个handler执行啦。

    还是看上图,pending位图保存收到的信号位图,比特位的位置:表示的是第几个信号,比特位的内容:表示是否收到。block位图表示是否阻塞,比特位的位置:表示的是第几个信号,比特位的内容表示:是否阻塞。

    二、信号集操作函数

    sigset_t

    从上图来看,每个信号只有一个bit的未决标志, 非0即1, 不记录该信号产生了多少次,阻塞标志也是这样 表示的。因此, 未决和阻塞标志可以勇相同的数据类型sigset_t来存储, , 这个类型可以表示每个信号的“有效”或“无效”状态, 在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞, 而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的 这里的“屏蔽”应该理解为阻塞⽽不是忽略。

    信号集操作函数

    #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)

    操作信号集(sigset_t) 的一堆函数,干的都是“组装、修改、判断信号集”的活。 这些信号集其实是用户态的临时数据结构,本质上是位图,跟内核里block位图的存储形式类似。

    典型函数: sigemptyset(清空信号集,全是0) sigfillset(全填1) sigaddset(添加某个信号) sigdelset(删除某个信号) sigismember(判断某信号在不在信号集中)

    这四个函数都是成功返回0,出错返回-1。sigismember是⼀个布尔函数,⽤于判断⼀个信号集的有效信 号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

    sigprocmask

    调用函数 sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)。 这个系统调用才是真正去修改内核中当前进程的 block 位图。 它会用你准备好的信号集(sigset_t)去 “替换、添加、删除” block 位图中对应的位。所有这里要注意上面的信号集操作函数只是工具,你拿着信号集草稿去找 sigprocmask 办手续,sigprocmask 帮你真正去 kernel 里改。

    #include <signal.h>
    int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
    返回值:若成功则为0,若出错则为1

    参数有: how:表示是“添加(阻塞)”、“删除(解除阻塞)”、“直接替换成新的”。 set:你准备好的信号集。 oldset:可以把修改前的block值保存下来。

    在这里插入图片描述 这些都是修改block位图。

    sigpending

    #include <signal.h>
    int sigpending(sigset_t *set);
    读取当前进程的未决信号集,通过set参数传出。
    调⽤成功则返回0,出错则返回1

    不管你爱不爱搭理(阻塞不阻塞),信号一到,pending 位图对应的位置就会被置 1。 不会因为 block、不会因为 handler 正忙,就不记。→ 来一个挂一个, pending 只负责“有信号来了”的事实,不负责“要不要执行”。

    那sigpending() 是干嘛的?

    查询“当前进程”哪些信号已经挂在 pending 位图上(那些“已经到了但是还没被响应”的信号)。 用于排查信号是否被 block 阻塞住了。 诊断信号处理的状态,是个“监测手段”,不是“处理动作”。

    实验

    #include <iostream>
    #include <unistd.h>
    #include <cstdio>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <signal.h>

    // 打印 pending 集
    void PrintPending(sigset_t &pending) {
    std::cout << "curr process[" << getpid() << "] pending: ";
    for (int signo = 31; signo >= 1; signo) {
    if (sigismember(&pending, signo)) {
    std::cout << 1;
    } else {
    std::cout << 0;
    }
    }
    std::cout << "\\n";
    }

    // 信号处理函数
    void handler(int signo) {
    std::cout << signo << " 号信号被递达!!!" << std::endl;
    std::cout << "——————————-" << std::endl;
    sigset_t pending;
    sigpending(&pending);
    PrintPending(pending);
    std::cout << "——————————-" << std::endl;
    }

    int main() {
    // 0. 捕捉 2 号信号
    signal(SIGINT, handler); // 自定义捕捉 SIGINT (Ctrl+C)

    // 1. 屏蔽 2 号信号
    sigset_t block_set, old_set;
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    sigaddset(&block_set, SIGINT);

    // 1.1 设置进程的 Block 表
    sigprocmask(SIG_BLOCK, &block_set, &old_set);

    int cnt = 15;
    while (true) {
    // 2. 获取当前进程的 pending 信号集
    sigset_t pending;
    sigpending(&pending);

    // 3. 打印 pending 信号集
    PrintPending(pending);

    cnt;

    // 4. 解除对 2 号信号的屏蔽
    if (cnt == 0) {
    std::cout << "解除对 2 号信号的屏蔽!!!" << std::endl;
    sigprocmask(SIG_SETMASK, &old_set, &block_set);
    }

    sleep(1);
    }

    return 0;
    }

    前面 15 秒,Ctrl+C 打了很多次,都不会触发 handler,因为 SIGINT 被 block 了,但 pending 位图记录了。

    你看到的:

    curr process[448336] pending: 0000000000000000000000000000010

    代表第 2 号信号 (SIGINT) 被挂在 pending 里。

    cnt==0 时解除阻塞,pending 里的 SIGINT 会被马上递达,调用 handler,打印信号收到。

    term 和 core

    子进程退出时产生的 core 和 term 现象,本质上也是由信号触发的结果,不过它们不单纯是“信号”本身,而是信号带来的退出状态。

    core会在当前路径下,形成一个文件,进程异常退出的时候,进程在内存中的核心数据,会从内存拷贝到磁盘,形成一个文件的核心转储,并且支持debug。然后在进程退出 trem就直接进程退出了。

    子进程的 term / core 是“信号导致的退出状态”,本质是信号事件的结果表现。term 代表被终止,core 代表还顺便扔了个尸体(core dump 文件)出来。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Linux笔记】——进程信号的保存
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!