当前位置: 首页 > article >正文

信号的捕捉(操作部分)

目录

信号集和信号屏蔽字

信号集

信号屏蔽字

信号位操作函数

sigemptyset

sigaddset

sigismember

sigprocmask

sigpending

手动操作让2号信号屏蔽打印pending

信号处理函数sigaction


我们继续来学习信号的捕捉

信号集和信号屏蔽字

信号集

信号集是存储一组信号的集合,本质也是一个位图,信号集可以存储未决信号,屏蔽信号,所以信号屏蔽字是信号集的一种,我们可以自己创建一个信号集,其类型为sigset_t,sigset_t类型是系统内置类型。

信号屏蔽字

信号屏蔽字Signal Mask进程级 的一个 位掩码,用于指定 哪些信号应该被阻塞(屏蔽),即这些信号在进程解除屏蔽之前不会被递送到进程。每个进程都有一个 信号屏蔽字,它是一个 位图(bitmask),每一位对应一个 信号(如 SIGINTSIGTERM 等):

信号屏蔽字是进程(或线程)的一个状态变量,它决定了当前进程(或线程)正在屏蔽哪些信号。其本质是位图,注意信号屏蔽字就是 Block 表(阻塞信号集),block表的另一种说法是信号屏蔽字。所以说我们创建信号屏蔽字本质不就是创建一个信号集吗。

对进程信号捕捉机制进行操着无非就是操作那三张表,无非就是在改位图的比特位,但是我们不建议直接进行位操作的修改,所以Linux内置了不少的函数帮助我们完成这个事。

信号位操作函数

sigemptyset

sigemptyset 是一个 POSIX 信号处理相关的函数,它用于初始化信号集(sigset_t),并将其清空,即不包含任何信号。

我们如果自己创建一个新的信号集,这时如果只是单纯的sigset_t set,这时信号集set往往是乱码的,不能直接用,我们就需要调用这个函数将其所以的bit位都制成0。

参数是一个指针。返回值如上:

sigaddset

sigaddset 是一个 POSIX 信号处理相关的函数,用于向信号集 (sigset_t) 中添加一个指定的信号。

通过sigaddset添加的信号编号signum,它会修改内部的比特位表示,使得对应信号的比特位变为 1(表示该信号已被添加),是一个由0置1的过程

sigismember

sigismember 是一个 POSIX 信号处理函数,用于检查某个信号是否在指定的 sigset_t 信号集中。

我们值得关注一下这个返回值。

上面这几个是本次试验要用到的函数,我们操作信号主要还是修改进程中的pending表和block表,有没有直接可以对着这两个表进行操作的函数。

sigprocmask

sigprocmask 是 POSIX 提供的信号管理函数,用于修改或查询进程的信号屏蔽字(signal mask),即进程当前阻塞的信号集。被屏蔽的信号不会被进程接收,直到解除屏蔽。

就是操作block表的,具体这么操作取决于此函数的第一个参数how的传参。

how有三种传参方式如上,也就是说可以直接用我们自己创建的信号屏蔽字取代进程中的block。

第二个参数set是输入型参数,第三个参数oldset是输出型参数,用于存放调用前进程的信号屏蔽字。

sigpending

sigpending 用于获取当前进程被阻塞但未被递达的信号(即挂起的信号),就是获取进程的pending表。

这个set参数是输出型参数。

为什么没有修改和替换等的操作,我们每次发信号不就在修改pending表吗,OS接收到信号pending表对应位置不就置1了吗,在 Linux 及 POSIX 规范中,进程的 挂起信号集pending)是内核自动管理的,用户态程序不能直接修改它。这是出于信号处理的设计原则系统安全性的考虑,记得9号信号是不能删除/替代/修改的,如果pending表可以让人随便修改,那不就违背了9号信号的原则了吗?

手动操作让2号信号屏蔽打印pending

我们接着的操作就是运用上面的函数,先屏蔽2号信号,然后获取pending表,只打印其中的32位,接着发送2号信号就可以看到2号信号pending效果,为什么要先屏蔽呢,我们不能让2号信号使得程序终止。

#include<iostream>
#include<unistd.h>
#include<signal.h>
using namespace std;
void PrintPending(const sigset_t& pending)
{
    cout << "curr pending list [" << getpid() << "]: ";
    for (int i = 31; i > 0; i--)
    {
        //判断pending表中是否存在此编号的信号
        if (sigismember(&pending, i))
        {
            cout << 1;
        }
        else if (sigismember(&pending, i) == 0)
        {
            cout << 0;
        }
        //忽略对i无效的情况
    }
    cout << endl;
}
int main()
{
    //1.屏蔽2号信号
    sigset_t block;
    sigset_t oblock;
    //初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    //添加2号信号进block,只是添加还没有设置进内核
    sigaddset(&block, 2);
    //设置进内核
    sigprocmask(SIG_SETMASK, &block, &oblock);
    while (true)
    {
        //循环获取并打印内核的pending表
        sigset_t pending;
        sigpending(&pending);
        //打印pending表的前32位(只关注32位信号)
        PrintPending(pending);
        sleep(1);
    }
    return 0;
}

这里sigprocmask的how可以使用上面的直接替换也可以使用SIG_BLOCK添加。至此我们就完成了先写入2号信号,然后内核屏蔽,打印pending的过程。

可以看到发送2号信号之后pending表果然有记录了(由0变1),为什么没有退出呢,因为block表里面已经事先屏蔽了该信号。

接着我们体验2号信号从屏蔽到接触的过程,接触2号信号只需要再次使用sigprocmask将保存原有block表的oblock替代之前的就可以了。

    //设置进内核
    sigprocmask(SIG_SETMASK, &block, &oblock);
    int cnt = 0;
    while (true)
    {
        //循环获取并打印内核的pending表
        sigset_t pending;
        sigpending(&pending);
        //打印pending表的前32位(只关注32位信号)
        PrintPending(pending);
        sleep(1);
        cnt++;
        if (cnt == 15)
        {
            cout << "解除对2号信号的屏蔽" << endl;
            //我们这次不关心旧的block表的值
            sigprocmask(SIG_SETMASK, &oblock, nullptr);
        }
    }
    return 0;
}

我们一旦解除了,2号信号一递达进程就被退出了,所以就停止了!!!

信号处理函数sigaction

sigactionLinux 中用来 处理信号(signal) 的系统调用,它比 signal() 更加强大和可靠。

也就是说sigaction具有和signal相同的功能,就是更改handler特定数组下标里面的内容。

和sigpromask的结构有点像,对一个编号为signum的信号进行act形式的捕捉,返回原有的捕捉方法oldact,这个act和oldact的类型是一个结构体,名字和函数名一样。

我们可以看到,sa_sigaction这个是用来捕捉实时信号的,我们不用管,上面这个就是signal同款的捕捉方式函数,同样的使用函数名传参(回调函数),sa_flag不用管,sa_mask是自定义屏蔽信号的位图,就是想屏蔽更多信号就sigaddset到这里。

sa_mask 里添加的信号会在 信号处理函数运行时自动屏蔽,防止嵌套执行,所以sigaddset添加进入sa_mask的信号就不需要再sigprocmask进内核了,算是一种自动添加到block表的行为

不过:

sigaction 里的 sa_mask 作用是:

当 信号(SIGINT )被触发时(某种行为如Ctrl+C),handler() 运行的这段时间,sa_mask 里的信号会自动被屏蔽,直到 handler() 结束。所以这种屏蔽是暂时的,当信号没有被捕捉或者处理完了不调用handler了就不生效了。

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;

void Printblock()
{
    sigset_t set;
    sigset_t olset;
    sigemptyset(&set);
    sigemptyset(&olset);
    sigprocmask(SIG_BLOCK, &set, &olset);
    for (int i = 31; i >= 0; i--)
    {
        if (sigismember(&olset, i))
        {
            cout << 1;
        }
        else
        {
            cout << 0;
        }
    }
    cout << endl;
}
void handler(int signo)
{
    while (1)
    {
        cout << "get a sig: " << signo << endl;
        Printblock();
        sleep(1);
    }
}

int main()
{
    struct sigaction act, oact;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaddset(&act.sa_mask, 5);
    sigaddset(&act.sa_mask, 6);
    ::sigaction(2, &act, &oact);
    while (true)
    {
        Printblock();
        //等待信号到来
        pause();
    }
}

可以看到确实如此!!!

当handler方法正在被处理时,此时2号信号的pending表一定是1,但是如果假设handler处理时间很久并且有指令调用系统调用使CPU陷入内核,此时又来了很多个2号信号,OS不允许信号处理方法进行嵌套,当某个信号正在被处理时又来了这个信号,操作系统就不会将pending表其再置1了,转而将block表的对应位置1表示阻塞,等信息处理完自动解除阻塞状态。

pending表由1->0的过程表示信号被处理完了,这个过程是在调用handler表的处理函数之前就置0了。


http://www.kler.cn/a/594037.html

相关文章:

  • C#从入门到精通(1)
  • yarn install 出现certificate has expired报错问题
  • 算法题(102):八皇后
  • centos7/8/9安装dockerdocker-compose
  • 车载以太网网络测试-17【传输层-TCP】
  • 理想发布的下一代自动驾驶架构MindVLA是什么?
  • 【HarmonyOS Next之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(七) -> JS动画(二)
  • 进程地址空间(上)【Linux】
  • prompt工程
  • 【LangChain入门 4 Prompts组件】提示词追加示例 FewShotPromptTemplate和示例选择器ExampleSelector
  • docker部署dify
  • xss-labs第八、九关卡以及XSS GAME的Ok,Boomer关卡
  • uniapp中的vue组件与组件使用差异
  • 如何使用webpack预加载 CSS 中定义的资源和预加载 CSS 文件
  • 微分方程求解及推导过程
  • 微软Data Formulator:用AI重塑数据可视化的未来
  • 联想拯救者触摸板会每次开机都自动关闭、联想笔记本触摸板关闭、笔记本电脑触摸板自动关闭的解决方法
  • 解锁Python print()函数高级用法
  • Linux实现交换分区和时间同步
  • 如何通过spark history页面查看gluten是否集成成功