首页 > 系统相关 >初识Linux · 信号保存

初识Linux · 信号保存

时间:2024-11-15 22:16:15浏览次数:3  
标签:set handler 递达 保存 阻塞 初识 信号 Linux pending

目录

前言:

Block pending handler表

信号保存


前言:

前文我们已经介绍了信号产生,在时间的学习线上,信号的学习分为预备知识,信号产生,信号保存,信号处理,本文我们学习信号保存,在前言部分,我们介绍几个信号保存中的概念。

信号递达:实际执行信号的处理动作。 信号未决:信号从产生到递达之间的状态。

对于信号产生之后,在递达的这个过程,成为未决,就像老师给你布置了作业,你接受到了做作业的这个信号,但是因为贪玩,不想做,这个状态,就是未决。


Block pending handler表

除了前言部分介绍的两个信号,还有一个概念是阻塞,其实就是,接受到了信号,但是对于信号排外的这个动作,它和忽略不是一样的,这要注意。

对于阻塞来说,一个信号如果阻塞了,和它有没有未决有关系吗?

当然是没有的,就像老师一直给你布置作业,但是你一直不想写,虽然接受到了这个信号,但是不鸟他,欸对吧,这就是阻塞。

对于进程来说,可以选择阻塞某个信号,而对于阻塞来说,如果进程阻塞了某个信号,那么对于该信号来说,就是永不递达,直到接触阻塞。

现在我们来介绍进程中的三张表:分别是Block pending handler表:

进程中存在三张表,block pending handler,他们都是位图,比特位的位置用来表示信号的某个状态,比如block位图表示的是是否被阻塞,pending位图表示的是是否接收到了该信号。

对于handler来说,首先我们思考一个问题,我们是否考虑过为什么signal使用的函数指针呢?因为task_struct中使用的handler就是函数指针,也就是说handler本质上是一个函数指针数组,当接收到了信号之和,通过该数组找到函数的地址并执行。

所以进程是如何识别信号的呢?

两个位图 + 一个函数指针数 == 让进程接收到信号

这里需要注意,忽略是递达之后的一种可选动作,阻塞是信号一旦被阻塞就不会递达。所以被阻塞的信号一直处于未决的状态,直到解除了阻塞状态。

那么说了这么多,我们是不是应该见见这三张表啊?所以进入下一个话题,语言层面操作两个位图。


信号保存

对于函数的调用,我们需要认识的是这5个函数,分别是sigemptyset, sigfillset, sigaddset, sigdelset,sigismember。

其实从名字来看,我们就能分析出来是干啥的,sigemptyset,将信号结构体置为空,fill填充信号,add添加信号,del删除信号,ismember判断是否有这个信号。

上文提到了这个信号结构体,可是我们好像没有看到过这个信号结构体?

那我们看看:

当定义一个sigset对象之后,我们转到定义,就会发现是这个结构体实际上就是一个无符号的长整型数组,说白了,它就是一个位图而已。通过/和%运算可以定义到信号的位置。

再介绍几个函数:

对于函数sigpending来说,它的参数set是一个输出型参数,获取当前pending位图。

对于函数sigprocmask的参数来说:

  • how:指定如何更改当前的信号屏蔽字。它可以是以下三个值之一:

    • SIG_BLOCK:将 set 中指定的信号添加到当前信号屏蔽字中。
    • SIG_UNBLOCK:从当前信号屏蔽字中移除 set 中指定的信号。
    • SIG_SETMASK:将当前信号屏蔽字设置为 set,忽略其当前值。
  • set:指向一个 sigset_t 类型的变量,该变量包含要更改的信号集。如果 set 是 NULL,则不修改信号屏蔽字,但 oldset 仍然会被设置为当前的信号屏蔽字。

  • oldset:如果非空,则指向一个 sigset_t 类型的变量,该变量将被设置为调用 sigprocmask 之前的信号屏蔽字。

对于oldset来说,就是存储之前信号,how参数的block参数是将set的信号添加到当前进程,un就是取消,setmask是将信号直接换成set,不要之前信号屏蔽值了。

所以,对于信号来说,因为能修改信号的只有OS,那么OS要提供给用户系统调用函数,但是不能直接修改,直接修改风险太大了,毕竟直接修改简直让我们和OS没有两样,所以提供了一个结构体,叫做sigset_t,并且使用函数,可以通过修改sigset_t的对象,间接修改当前进程。

现在我们试试:

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 << std::endl;

}

int main()
{

    sigset_t block_set, old_set;
    //初始化sigset_t对象
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    //添加2号信号
    sigaddset(&block_set,2);
    //修改当前进程的信号
    sigprocmask(SIG_BLOCK,&block_set,&old_set);

    while(true)
    {
        sigset_t pending;
        sigpending(&pending);
        // 打印pending位图
        PrintPending(pending);

        sleep(1);
    }
    return 0;
} 

我们通过定义两个sigset_t对象,然后初始化一下,对1号对象添加信号,此时,我们并没有完成对当前进程修改信号这个过程,当我们调用sigprocmask之后,我们就可以修改进程信号了,并且,我们可以打印pending出来看看。

我们往1507065进程发送信号2,发现pending位图的第二个比特位变成了1,也就是它接收到了2号信号,所以变成了1。

现在我们看到了0到1的变化,那么如果我们想看1到0的变换呢?我们只需要修改how的参数,SIG_SETMASK,这里我们可以直接复用原来的oldset:

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

加入以上逻辑就发现,时间一到直接就停止了,所以我们应该自定义,取消默认行为:

void handler(int signo)
{
    std::cout << "Hello SIGINT " << std::endl;
}

那么这里有新问题了,信号从0变成1的这个过程,是递达之前还是递达之后呢?

我们打印出来看看:

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

因为是在函数里面发生改变的,所以应该是递达之前改变的。

对于已经屏蔽的信号,如果解除之后,那么就会立即执行。

以上的操作都是修改的pending,没有直观看到block,这段代码对于block修改较为直观:

void handler(int signum) {
    printf("Caught signal %d\n", signum);
}
 
int main() {
    sigset_t set, oldset;
 
    // 设置信号处理程序
    signal(SIGINT, handler);
 
    // 初始化信号集
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
 
    // 阻塞 SIGINT 信号
    if (sigprocmask(SIG_BLOCK, &set, &oldset) == -1) {
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }
 
    printf("SIGINT is blocked. Press Ctrl+C (SIGINT) now...\n");
    sleep(5);
 
    // 解除阻塞 SIGINT 信号
    if (sigprocmask(SIG_UNBLOCK, &set, NULL) == -1) {
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }
 
    printf("SIGINT is unblocked. Press Ctrl+C (SIGINT) again...\n");
    sleep(5);
 
    // 恢复原来的信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldset, NULL) == -1) {
        perror("sigprocmask");
        exit(EXIT_FAILURE);
    }
 
    printf("Original signal mask restored.\n");
 
    // 继续执行其他操作
    sleep(5);
 
    return 0;
}

感谢阅读!

标签:set,handler,递达,保存,阻塞,初识,信号,Linux,pending
From: https://blog.csdn.net/2301_79697943/article/details/143807405

相关文章

  • linux下编译安装memcached
    Memcached在Linux下的编译安装一、安装依赖库Memcached依赖于一些系统库,在大多数Linux发行版中,需要安装libevent库。Debian/Ubuntu系统使用以下命令安装依赖库:sudoapt-yupdatesudoapt-yinstalllibevent-devCentOS/RHEL系统可以通过以下命令安装:sudoyum......
  • Linux cp和mv命令 对于目录复制到目录的情况 的 所有情况示例
    cp和mv命令的行为总结表假设以下路径设置:源路径:/nihao或/nihao//nihao包含文件和子目录:file1,dir1/,file2目标路径:/nima或/nima/表格cp命令行为命令目标路径存在?最终路径结构cp-r/nihao/nima/是/nima/nihao/cp-r/nihao//nima/是/ni......
  • linux运维面试题【基础篇】
    前言本篇主要分享linux运维面试过程中常见的面试题材,当时自己面试的时候就遇到3道原题,最终也是顺利通过面试,下面给大家分享一下:面试题库1、描述Linux系统的启动过程电源BIOS自检读取MBR,运行grub加载内核内核启动/sbin/init程序,进行系统初始化根据系统运行级别执行......
  • Linux常用命令之find命令详解
    find命令详解find是Unix和类Unix操作系统(如Linux和macOS)中一个非常强大的命令行工具,用于在文件系统中搜索文件和目录。find命令可以根据多种条件(如文件名、类型、大小、修改时间等)进行搜索,并可以执行复杂的操作。基本语法find命令的基本语法如下:find[路径]......
  • Linux编程:基于 Unix Domain Socket 的进程/线程间通信实时性优化
    文章目录0.引言1.使用`epoll`边缘触发模式非不要不选择阻塞模式边缘触发(ET)模式优点示例2.使用实时调度策略3.CPU绑定4.使用无锁缓冲区5.优化消息传递的大小和频率6.使用`SO_RCVTIMEO`和`SO_SNDTIMEO`7.示例代码其他阅读0.引言前几天被问到“如何优......
  • 【linux命令】史上最全Linux命令,结合用例通俗易懂(网络管理命令)
    前言:目前关于Linux命令的文章往往存在内容不全的问题,导致初学者和中级用户在使用过程中遇到困难。许多文章仅涵盖基础命令,而缺乏对系统管理、网络配置、包管理和脚本编写等重要主题的详细讲解。此外,实际操作中的常见问题及其解决方案也常常未被提及,使得用户在遇到困难时无法......
  • rocky与linux的nmcli命令用法
    rocky的linux命令用法nmcli是NetworkManager的一个命令行工具,也是目前Centos比较强大的网络管理命令行工具(目前适用于大多数linux发行版)。nmcli四类常用命令:n、g、c、d。查看NetworkManager是否接管网络设置显示NetworkManager是否接管网络设置:nmclinetwork#返回结果......
  • linux内核中nand坏块管理的实现方法
    在Linux内核中,NAND闪存的坏块管理是通过以下几个关键的机制实现的:1.坏块标记(BadBlockMarking)NAND闪存是一种块级存储设备,闪存中的一些块可能会出现故障,导致无法正常读取或写入。为防止操作系统尝试使用这些坏块,Linux内核会对坏块进行标记。坏块通常是在NAND闪存的出厂阶段......
  • linux之通配符
    特殊符号#注释或者命令提示符中的用户提示符表示管理员$获取变量内容echo$PATH!强制执行wq!q!|命令的拼接,管道符;命令的分隔符,不管前面的命令是否执行成功,继续向后执行&& 前面的命令必须执行成功,才会往后执行|| 前面的命令执行失败,才......
  • Ubantu基础Linux命令
    一、用户命令1.1、添加用户语法:sudouseradd[选项]用户名-d指定用户的家目录-u设置创建用户的id值-e账户的到期时间,格式为YYYY-MM-DD-g指定一个初始的用户基本组(必须已存在)-G指定一个或多个扩展用户组-N不创建与用户同名的基本用户组-s指定该用户......