linux入门---信号的操作

news2024/11/19 0:29:55

目录标题

  • sigset_t
  • sigset_t的操作函数
  • sigprocmask
  • sigpending
  • 信号的屏蔽测试
  • sigaction

sigset_t

为了能够让操作系统更好的使用信号,操作系统提供了sigset_t的数据类型,操作系统中存在pending表和block表,但是这两张表是内核数据结构,用户是没有办法直接修改的,而且我要是想同时修改一个表中的10个信号或者两个表的十几个信号呢?操作系统是不可能给你提供10几个参数的函数的,所以他给我们统一定义了一个sigset_t的数据类型,因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态,所以sigset_t是操作系统为了让用户可以更好的修改两张表而提供的数据结构,我们一般把sigset_t的数据结构称之为信号集,信号集又分为两种,一种是pending信号集,一种事block信号集,我们一般把block信号集称为信号屏蔽字,sigset_t本质上就是一个位图,但是不同的操作系统实现的这个位图的方法是不一样的,这里的位图不是一个简单的整形,因为他要保证信号的扩展和兼容实时信号就不能简单的使用整形来实现,所以就不能简单使用安位与安位或来实现,所以操作系统提供了一些列的接口来对sigset_t的数据进行操作,那么接下来我们就来一一介绍对应的函数接口。

sigset_t的操作函数

这两个函数的声明如下:

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

这两个函数都有相同类型的参数,传递一个信号集的指针进去,sigemptyset函数可以将该信号集的每个信号都置为0,而sigfillset函数就可以将信号集中的每个信号都置为1,所以我们可以认为这两个函数的作用就是初始化,一个是将所用的信号初始化为0,一个是将所有的信号初始化为1。

int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);

这两个函数的参数也是相同的,第一个参数是一个信号集的指针,第二个参数是一个整型表示是信号集中的第几个信号,所以sigaddset函数就是将set指向的信号集中的第signo信号置为1,sigdelset函数的作用就是将set指向的信号集中的第signo信号置为0

int sigismember(const sigset_t *set, int signo);

这个参数的作用就是判断signo信号是否在信号集set里面。这5个函数不需要记忆用的时候查一下就行知道有这回事就行。

sigprocmask

在这里插入图片描述

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

这个函数的作用就是修改内核中的block表,第一个参数是一个标记位表示如何修改信号的屏蔽字,修改所用到的数据就来自于set,标记位有3个其中SIG_BLOCK表示将set中的信号屏蔽字添加到当前信号屏蔽字里面相当于mask=mask|set,SIG_UNBLOCK表示解除屏蔽字也就是从当前信号屏蔽字中删除set中包含的信号屏蔽字相当于mask=mask&~set,SIG_SETMASK表示将屏蔽字设计成跟set一样,set的作用就是将set的数据重置进进程的block,因为使用该函数后会修改进程屏蔽字所以oset参数的作用就是将原来block的数据放到oset里面。

sigpending

在这里插入图片描述
这个函数的作用就是获取当前进程的pending信号集,函数的参数是一个输出他的参数是一个输出型参数,哪个进程调用这个函数就输出哪个进程的pending表。

信号的屏蔽测试

那么有了上面的这些函数我们就可以写一个函数用来测试信号屏蔽的效果比如说我们屏蔽了某个信号然后不停的打印进程的pending位图,这时当我们发送对应信号的时候就可以看到打印的pending位图上的信号由0变成了1,但是没有执行该信号的功能,等过了一会我们解除了对信号的屏蔽时又可以看到pending位图上的信号由1变成了0并且执行了信号的功能,那么这就是我们要实现的测试,首先我们得创建两个信号集,一个用来设置新的信号,一个用来接收原来的老的信号,在使用信号集之前先使用sigemptyset对两个信号集进行初始化:

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
int main()
{
    sigset_t block oblock;
    sigemptyset(&block);
    sigemptyset(&oblock);
    return 0;
}

然后我们就可以使用sigaddset函数将你想要屏蔽的信号添加进信号屏蔽字里面,比如说想要屏蔽2号信号,那么我们就可以传递2给sigaddset函数的第二个参数,然后就可以调用sigprocmask函数将想要屏蔽的信号添加进内核的block表里面,那么这里的代码如下:

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
int main()
{
    sigset_t block oblock;
    //初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    //添加屏蔽的信号
    sigaddset(&block,2);
    //将屏蔽信号添加进内核的block表里面
    sigprocmask(SIG_SETMASK,&block,&oblock);
    
    return 0;
}

然后我们就可以创建一个循环不停的打印pending表里面的内容,因为要查看pending表所以要使用sigpending函数,因为要使用sigpending函数所以我们还得创建一个名为pending的sigset_t对象,那么这里我们就可以创建一个函数,每次循环都对pending进行初始化,然后使用spending获得内核中表的数据,因为要打印pending对象里面的数据所以这里我们可以创建一个函数来实现这样的功能,那么这里的代码如下:

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
void showpending(const sigset_t* pending)
{

}
int main()
{
    sigset_t block oblock pending;
    //初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    //添加屏蔽的信号
    sigaddset(&block,2);
    //将屏蔽信号添加进内核的block表里面
    sigprocmask(SIG_SETMASK,&block,&oblock);
    while(true)
    {
        sigemptyset(&pending);
        spending(&pending);
        showpending(&pending);
        sleep(1);
    }
    return 0;
}

那么接下来我们只用实现一下showpending函数就行,因为一共右31个信号,所以我们可以创建一个循环让其循环31次,因为我们不能使用按位或和按位与来获取pending中的数据,所以我们得循环使用sigismember函数来一个一个的判断信号是否存在,如果存在我们就打印1如果不存在我们就打印0,那么完整的代码如下:

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
void showpending(const sigset_t* pending)
{
    for(int i=31;i>0;i--)
    {
        if(sigismember(pending,i))
        {
            cout<<'1';
        }
        else
        {
            cout<<'0';
        }
        
    }
    cout<<endl;
}
int main()
{
    sigset_t block,oblock, pending;
    //初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    //添加屏蔽的信号
    sigaddset(&block,2);
    //将屏蔽信号添加进内核的block表里面
    sigprocmask(SIG_SETMASK,&block,&oblock);
    while(true)
    {
        sigemptyset(&pending);
        sigpending(&pending);
        showpending(&pending);
        sleep(1);
    }
    return 0;
}

然后我们就可以运行一下代码进行测试,运行的结果如下:
在这里插入图片描述
一开始没有收到任何的信号所以这里打印的结果都是0,当我们在键盘上面输入ctrl c发送2号信号给进程时就可以看到2号位子上的数据由0变成了1,2号信号本来会终结进程的但是由于我们将其屏蔽了,所以程序此时依然会继续运行:
在这里插入图片描述
那么这就是将信号屏蔽的过程,那么这是由0变成了1,我们还可以通过解除信号的阻塞将其由1再变成0,那么这里我们就可以在循环外面定义一个变量将其初始化位0,每次循环都对这个变量加一,等该变量的值等于10的时候外面就对2号信号进行解锁,然后打印一句话说我们将信号的阻塞回复到了之前的状态,那么这里的代码就如下:

int main()
{
    sigset_t block,oblock, pending;
    //初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    //添加屏蔽的信号
    sigaddset(&block,2);
    //将屏蔽信号添加进内核的block表里面
    sigprocmask(SIG_SETMASK,&block,&oblock);
    int cnt=1;
    while(true)
    {
        sigemptyset(&pending);
        sigpending(&pending);
        showpending(&pending);
        cnt++;
        if(cnt==10)
        {
            sigprocmask(SIG_SETMASK, &oblock, &block);
            cout << "恢复对信号的屏蔽,不屏蔽任何信号\n";
        }
        sleep(1);
    }
    return 0;
}

代码的运行结果如下:
在这里插入图片描述
可以看到这里确实只打印了9次,但是为什么没有打印if语句里面的那句话呢?原因很简单一旦对特定信号进行解除屏蔽,一般OS要至少立马递达一个信号!所以当我们对2号信号进行解封的时候立马就会抵达2号信号,而2号信号的作用就是终止进程,所以还没来得及打印这句话操作系统就将这句话终止了,那么要想解决这个问题就可以将打印的话放到解封的前面:

if(cnt==10)
{
    cout << "恢复对信号的屏蔽,不屏蔽任何信号\n";
    sigprocmask(SIG_SETMASK, &oblock, &block);
}

再运行一下代码就可以看到下面这样的场景:
在这里插入图片描述
但是这里依然存在一个问题,虽然我们看到了if语句打印出来的一句话,但是我们没有看到信号由1变成0的现象啊,那么要想看到对应的现象我们就得对2号信号的方法进行重定义,那么这里就得使用signal函数完整代码如下:

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
void showpending(const sigset_t* pending)
{
    for(int i=31;i>0;i--)
    {
        if(sigismember(pending,i))
        {
            cout<<'1';
        }
        else
        {
            cout<<'0';
        }
        
    }
    cout<<endl;
}
void handler(int signal)
{
    cout<<"收到了信号:"<<signal<<endl;
}
int main()
{
    signal(2,handler);
    sigset_t block,oblock, pending;
    //初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    //添加屏蔽的信号
    sigaddset(&block,2);
    //将屏蔽信号添加进内核的block表里面
    sigprocmask(SIG_SETMASK,&block,&oblock);
    int cnt=1;
    while(true)
    {
        sigemptyset(&pending);
        sigpending(&pending);
        showpending(&pending);
        cnt++;
        if(cnt==10)
        {
            cout << "恢复对信号的屏蔽,不屏蔽任何信号\n";
            sigprocmask(SIG_SETMASK, &oblock, &block);
        }
        sleep(1);
    }
    return 0;
}

代码的运行结果如下:
在这里插入图片描述
那么这就是我们测试的完整内容。

sigaction

之前我们学过一个跟函数信号有关的signal函数,他可以实现对信号行为的重定义,那么接下来我们还要介绍一个与信号捕捉有关的函数sigaction,函数的声明如下:
在这里插入图片描述

这个函数的作用就是给特定的信号设定特定的方法,act是一个结构体并且该结构体的名字和函数的名字一模一样,结构体的成员如下:
在这里插入图片描述
因为这个结构体未来还会用来处理实时信号,所以里面的有些成员我们现在先不关心,第一个成员sa_handler就是一个函数指针也就是之前要设定的信号处理的方法,sa_sigaction不用管它,sa_flags设置为0不用管,sa_restorer也设置为null不用管,sa_mask是一个信号集这个具体有什么用我们待会再说,所以对于这个结构体我们只需要关系sa_mask成员和sa_handler就可以了。
函数中的act是一个输入型参数用于把我们给的信号设置进内核里面,oldact是输出型参数用来获取之前设定的老的信号,那么接下来我们就要用一端代码来理解这个函数的作用,首先创建两个sigaction结构体对象,一个用来设置进内核信号,一个用来接收内核信号原来的性质

#include<iostream>
#include<signal.h>
using namespace std;
int main()
{
    struct sigaction act ,oact;
    return 0;
}

然后就对act对象的内部成员进行初始化,因为这里需要函数指针,所以我们得创建对应的handler函数,在函数里面我们就打印一句话表示接收到了对应的信号,然后倒计时10秒为了让这里的现象更加的明显我们还可以添加一个倒计时的功能,因为sa_mask是一个sigset_t类型所以我们还得使用sigemptyset函数对其进行初始化,那么这里的代码就如下:

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
void handler(int signo)
{
    cout<<"接收到了信号:"<<signo<<endl;
    int cnt=10;
    while(cnt)
    {
        printf("cnt:%2d\r",cnt--);
        fflush(stdout);
        sleep(1);
    }
    cout<<endl;
}
int main()
{
    struct sigaction act ,oact;
    act.sa_handler=handler;
    act.sa_flags=0;
    act.sa_restorer=nullptr;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,&oact);
    while(true)
    {
        sleep(1);
    }
    return 0;
}

那么接下来我们就可以对其进行测试,运行程序可以看到这里没有任何的反应:
在这里插入图片描述
对其发送一个2号信号就可以看到这里出现了信号处理的动作
在这里插入图片描述

在这里插入图片描述
我们这里一下子只发送了一个2号信号,那如果我们一下子发送多个2号信号会出现什么样的场景呢?会不会递归处理我们发送的信号呢?那么这里的运行结果如下:
在这里插入图片描述

在这里插入图片描述
可以看到这里就处理了两次信号,并没有递归式的除了发送次数的信号,因为当我们正在递达某一个信号期间,同类信号是无法被递达的,当当前信号正在被捕捉时,系统就会自动将当前信号加入到进程的信号屏蔽字block里面,当信号完成捕捉动作之后,系统又会自动恢复对该信号的捕捉,当发送了多个信号,信号处理完毕时只会再执行一次信号的动作,原因就是处理信号的时候会讲pending中的1变成0,而多次发送信号又会0变成1,因为只有一个比特位所以无法递归多次,所以这里就会出现两次信号执行的现象,一般一个信号被解除屏蔽的时候会自动进行递达当前屏蔽信号,如果信号已经被pending的话就执行该信号,没有的话就不执行任何的操作。我们进程处理信号的原则是串行的处理同类型的信号,不允许递归,而sa_mask的作用就是:当我们正在处理某一种信号的时候,如果我们要想顺便屏蔽其他的信号的话就可以将对应信号添加到这个sa_mask中,比如说下面的操作:
在这里插入图片描述
当前正在处理2号信号,但是我们可以通过发送3号信号的方式结束真正处理2号信号的进程:
在这里插入图片描述
在这里插入图片描述
但是我们将3号信号放进sa_mask之后就可以看到,处理2号信号的时候发送3号信号不会终止进程:

int main()
{
    struct sigaction act ,oact;
    act.sa_handler=handler;
    //act.sa_sigaction=nullptr;
    act.sa_flags=0;
    act.sa_restorer=nullptr;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask,3);
    sigaction(SIGINT,&act,&oact);
    while(true)
    {
        sleep(1);
    }
    return 0;
}

测试的结果如下:
在这里插入图片描述
在这里插入图片描述
只有当2号信号处理完了才会接着处理3号信号:
在这里插入图片描述
那么这就是sa_mask的作用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1064961.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构-顺序存储二叉树

文章目录 目录 文章目录 前言 一 . 什么是顺序存储二叉树 二 . 模拟实现 前序遍历 总结 前言 大家好,今天给大家讲一下顺序存储二叉树 一 . 什么是顺序存储二叉树 顺序存储二叉树是一种将二叉树的节点按照从上到下、从左到右的顺序存储在数组中的方法。具体来说&#xff0c;顺…

Jackson 的 SNAKE_CASE 反序列化

最近项目中有关 JSON 的序列化和反序列化中&#xff0c;我们遇到了一个问题就是 category_id 我们在定义对象的时候使用的是 categoryId。 当程序进行反序列化的时候&#xff0c;我们获得的对象值为 NULL。 这是因为 jackson 提供了一个命名规则&#xff0c;如果你是希望进行…

FISCO BCOS(三十七)———FISCOBCOS应用开发,交易hash、区块高度的获取

这个需求怎么做? 交易hash的获取方式有很多,这里先介绍一种方式。 根据块高查询区块信息 https://webasedoc.readthedocs.io/zh_CN/latest/docs/WeBASE-Front/interface.html那我们如何知道现在的区块高度是多少? https://webasedoc.readthedocs.io/zh_CN/latest/docs/W…

谦卑篇(Be humble)//伟大是用卑微来换取的,任何时候都应该看清自己

1. 宇宙诞生138亿年&#xff0c;地球迄今经过了46亿个春秋&#xff0c;人生不过百年&#xff0c;//从生命科学的角度。 2. 蔡崇信&#xff1a;好的领导者是很谦卑的&#xff0c;他们会用谦卑雇佣来能力更强的人。// 从创业者的角度。 3. 《臣服实验》&#xff1a; 臣服生命之流…

新版Ai企业级系统去授权版本完美运行

Ai企联系统去授权版新鲜出炉 一款市面上新出的AI企联系统&#xff0c;一款市面上新出的AI企联系统 项目uniapp开发的&#xff0c;支持3.5 4.0 Mj 此套系统5端适配&#xff0c;WebH5微信小程序抖音小程序双端APP&#xff0c;支持流量主! 好像有能力的快手小程序那些也可以上…

高通camx开源部分学习简介

camera整体框架 sensor 上电&#xff0c;通过 MIPI协议传输&#xff0c;得到RAW图像数据。RAW图像数据经过ISP处理&#xff0c;得到YUV图像数据。YUV图像数据再经过DMA传输到DDR内存中&#xff0c;DDR内存也就是上图中标识的HOST。每个厂家的 ISP原理和功能大致相同&#xff0c…

Qt中 QMap 类、QHash 类、QVector 类详解

目录 一、QMap 类 1.插入数据信息 2.删除数据信息 3.迭代器 4.STL类型迭代 5.key键/T键查找 6.修改键值 7. 一个键对应多个值 直接使用QMultiMap类来实例化一个QMap对象 二、QHash 类 三、QVector类 一、QMap 类 QMap<Key,T>提供一个从类型为 Key 的键到类型为…

制作电商页面(Html)

任务 制作一个电商页面&#xff0c;要求所卖物品清晰&#xff0c;页面色调清晰&#xff0c;要有主页和详情页。 网站所买物品&#xff1a;书籍 色调&#xff1a;#FF2400 橙红色 代码 主页HTML代码&#xff1a; <html><head><meta charset"utf-8"…

力扣 -- 647. 回文子串

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int countSubstrings(string s) {int ns.size();vector<vector<bool>> dp(n,vector<bool>(n));//无需初始化int ret0;//一定要从下往上填写每一行for(int in-1;i>0;i--){//每一行的i…

Selenium基础

最近在学习爬虫的有关知识&#xff0c;发现一个很有意思的工具Selenium&#xff0c;所以打算花点时间看Selenium提供的手册来学习&#xff0c;为了防止以后忘记和方便复习&#xff0c;打算记录一下我的学习过程&#xff0c;我使用的Selenium版本是4.8.2。 目录 Selenium概述 网…

C++设计模式-享元(Flyweight)

目录 C设计模式-享元&#xff08;Flyweight&#xff09; 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-享元&#xff08;Flyweight&#xff09; 一、意图 运用共享技术有效地支持大量细粒度的对象。 二、适用性 一个应用程序使用了大量的对象。完全由…

【RabbitMQ】初识消息队列 MQ,基于 Docker 部署 RabbitMQ,探索 RabbitMQ 基本使用,了解常见的消息类型

文章目录 前言一、初识消息队列 MQ1.1 同步通信1.2 异步通信1.3 MQ 常见框架及其对比 二、初识 RabbitMQ2.1 什么是 RabbitMQ2.2 RabbitMQ 的结构 三、基于 Docker 部署 RabbitMQ四、常见的消息类型五、示例&#xff1a;在 Java 代码中通过 RabbitMQ 发送消息5.1 消息发布者5.2…

【数据库——MySQL】(15)存储过程、存储函数和事务处理习题及讲解

目录 1. 题目1.1 存储过程1.2 存储函数1.3 事务处理 2. 解答2.1 存储过程2.2 存储函数2.3 事务处理 1. 题目 1.1 存储过程 创建表 RandNumber &#xff1a;字段&#xff1a;id 自增长&#xff0c; data int&#xff1b; 创建存储过程向表中插入指定个数的随机数&#xff08;1-…

UDP通信程序的详细解析

2.UDP通信程序 2.1 UDP发送数据 Java中的UDP通信 UDP协议是一种不可靠的网络协议&#xff0c;它在通信的两端各建立一个Socket对象&#xff0c;但是这两个Socket只是发送&#xff0c;接收数据的对象&#xff0c;因此对于基于UDP协议的通信双方而言&#xff0c;没有所谓的客户端…

MySql017——组合查询UNION和UNION ALL

一、UNION作用 可用UNION操作符来组合数条SQL查询。 二、UNION 使用规则 1、UNION的使用很简单。所需做的只是给出每条SELECT语句&#xff0c;在各条语句之间放上关键字UNION。2、UNION必须由两条或两条以上的SELECT语句组成&#xff0c;语句之间用关键字UNION分隔&#xff…

Hive 【Hive(七)窗口函数练习】

窗口函数案例 数据准备 1&#xff09;建表语句 create table order_info (order_id string, --订单iduser_id string, -- 用户iduser_name string, -- 用户姓名order_date string, -- 下单日期order_amount int -- 订单金额 ); 2&#xff09;装载语句 i…

代码随想录算法训练营第五十八天 | 动态规划 part 16 | 583. 两个字符串的删除操作、72. 编辑距离

目录 583. 两个字符串的删除操作思路思路2代码 72. 编辑距离思路代码 583. 两个字符串的删除操作 Leetcode 思路 dp[i][j]&#xff1a;以i-1为结尾的字符串word1&#xff0c;和以j-1位结尾的字符串word2&#xff0c;想要达到相等&#xff0c;所需要删除元素的最少次数。递推公…

【AI视野·今日NLP 自然语言处理论文速览 第四十八期】Thu, 5 Oct 2023

AI视野今日CS.NLP 自然语言处理论文速览 Thu, 5 Oct 2023 Totally 50 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Retrieval meets Long Context Large Language Models Authors Peng Xu, Wei Ping, Xianchao Wu, Lawrence McA…

算法题:摆动序列

这道题是一道贪心算法题&#xff0c;如果前两个数是递增&#xff0c;则后面要递减&#xff0c;如果不符合则往后遍历&#xff0c;直到找到符合的。&#xff08;完整题目附在了最后&#xff09; 代码如下&#xff1a; class Solution(object):def wiggleMaxLength(self, nums):…

【力扣-每日一题】714. 买卖股票的最佳时机含手续费

class Solution { public:int maxProfit(vector<int>& prices, int fee) {//[i][0]-不持有 [i][1]-持有int mprices.size();vector<vector<int>> dp(m,vector<int>(2));dp[0][0]0; //初始状态dp[0][1]-prices[0];for(int i1;i<m;i){dp[i]…