Linux知识点 -- 进程信号(二)

Linux知识点 – 进程信号(二)

文章目录

  • Linux知识点 -- 进程信号(二)
  • 一、信号保存
    • 1.相关概念
    • 2.信号保存的相关接口
    • 3.对所有的信号都进行自定义捕捉
    • 4.将2号信号block,并打印pending信号集
    • 5.将所有信号都block
  • 二、处理信号
    • 1.信号处理的时机
    • 2.信号处理的流程
    • 3.sigaction
  • 三、可重入函数
  • 四、volatile关键字
  • 五、SIGCHILD信号


一、信号保存

1.相关概念

  • 信号递达(Delivery):实际执行信号的处理动作;

  • 信号未决(Pedning):信号从产生到递达之间的状态;信号未决就是进程收到了一个信号,但是未处理,就是临时保存到了进程PCB中的对应的位图中;

  • 进程可以选择阻塞(block)某个信号;

  • 被阻塞的信号产生时将保持在未决状态,直到进程解决对此信号的阻塞,才执行递达的动作;

  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在抵达之后可选的一种处理动作;

  • 在进程PCB内部有3张表:
    在这里插入图片描述
    其中,pending就是信号未决的位图,进程在收到一个信号后,会将pending表中相应的位置位;
    handler是函数指针数组 – 数组下标对应信号的编号,就是对应信号的处理方式;signal自定义捕捉就是将信号对应的方法填入handler表;

    在这里插入图片描述
    也可以设置信号的忽略和默认;IGN是忽略;DFL是默认;
    block表是阻塞表,结构和pending一摸一样,代表的含义是对应的信号是否被阻塞;

  • 信号的处理过程:
    进程在接受一个信号后,会将pending表中相应的位置位,然后先去block表中查看该进程是否被阻塞,如果被阻塞,就不做任何动作,如果没有阻塞,再去handler表中查询处理方法;

2.信号保存的相关接口

(1)语言会为我们提供.h.hpp和语言的自定义类型;
同时,操作系统也会给我们提供.h和自定义类型;

(2)OS向我们提供了接口,一定要提供相对应的类型;
语言提供了访问系统调用的接口,也一定会提供相对应的类型;

  • sigset_t类型:
    未决和阻塞标志可以使用相同的数据类型(位图),sigset_t称为信号集,这个类型可以表示每个信号的有效或无效状态;
    在阻塞信号集中有效和无效的含义是该信号是否被阻塞,阻塞信号集也叫做信号屏蔽字
    而在未决信号集中有效和无效的含义是该信号是否处于未决状态;

    注:
    sigset_t不允许用户自己进行位操作,OS为我们提供了对应的操作方法;
    sigset_t使用者可以直接使用该类型,和用内置类型、自定义类型没有任何差别;
    sigset_t一定需要对应的系统接口,来完成对应的功能,其中系统接口需要的参数,可能就包含了sigset_t定义的变量或者对象;

  • OS提供的对sigset_t操作的接口:
    在这里插入图片描述
    分别是:
    全部位清0;
    全部位置1;
    某个信号置位;
    某个信号复位;
    判断信号是否存在;

    在这里插入图片描述
    sigpending函数获取当前调用进程的pending信号集;
    set是输出型参数;
    成功返回0,失败返回-1;

    在这里插入图片描述
    sigprocmask函数检查并更改block信号集;
    how参数:
    在这里插入图片描述
    set:根据how的不同的宏,有不同的功能;
    oldset:输出型参数,返回老的信号屏蔽字,不需要可以传空指针;

3.对所有的信号都进行自定义捕捉

#include<iostream>
#include<unistd.h>
#include<signal.h>using namespace std;void catchSig(int signum)
{cout << "获得了一个信号:" << signum << endl;
}int main()
{for(int i = 1; i <= 31; i++){signal(i, catchSig);}while(true){sleep(1);}return 0;
}

运行结果:
在这里插入图片描述
可以发现,其他信号都被自定义捕捉了,只有9号信号杀死了该进程,因为9号信号是不能被捕捉的

4.将2号信号block,并打印pending信号集

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>using namespace std;static void showPending(sigset_t &pending)
{for(int sig = 1; sig <= 31; sig++){if(sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}int main()
{//1.定义信号集对象sigset_t bset, obset;sigset_t pending;//2.初始化sigemptyset(&bset);sigemptyset(&obset);sigemptyset(&pending);//3.添加要进行屏蔽的信号sigaddset(&bset, 2);//4.设置set到内核中对应的进程内部int n = sigprocmask(SIG_BLOCK, &bset, &obset);assert(n == 0);(void)n;cout << "block 2号信号成功 " << endl;//5.重复打印当前进程的pending信号集while(true){//获取当前进程的pending信号集sigpending(&pending);//显示当前进程的pending信号集showPending(pending);sleep(1);}return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述
当发送了2号信号后,pending表中对应的位置1了,2号信号是被阻塞了,应该一直在pending表中,无法被递达;

在一定时间后恢复2号信号的block

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>using namespace std;static void showPending(sigset_t &pending)
{for(int sig = 1; sig <= 31; sig++){if(sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}int main()
{//1.定义信号集对象sigset_t bset, obset;sigset_t pending;//2.初始化sigemptyset(&bset);sigemptyset(&obset);sigemptyset(&pending);//3.添加要进行屏蔽的信号sigaddset(&bset, 2);//4.设置set到内核中对应的进程内部int n = sigprocmask(SIG_BLOCK, &bset, &obset);assert(n == 0);(void)n;cout << "block 2号信号成功 " << endl;//5.重复打印当前进程的pending信号集int count = 0;while(true){//获取当前进程的pending信号集sigpending(&pending);//显示当前进程的pending信号集showPending(pending);sleep(1);count++;if(count == 20){int n = sigprocmask(SIG_SETMASK, &obset, nullptr);//将原来的信号集附上去assert(n == 0);(void)n;cout << "接触对2号信号的block " << endl;}}return 0;
}

运行结果:
在这里插入图片描述
结果是没有看到pending表从1变为0;
默认情况下,回复对于2号信号block的时候,确实会进行递达;
但是2号信号的默认处理动作是终止进程,将进程直接终止;

我们需要对2号信号进行捕捉:

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<cassert>using namespace std;void catchSig(int signum)
{cout << "获得了一个信号:" << signum << endl;
}static void showPending(sigset_t &pending)
{for(int sig = 1; sig <= 31; sig++){if(sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}int main()
{signal(2, catchSig);//1.定义信号集对象sigset_t bset, obset;sigset_t pending;//2.初始化sigemptyset(&bset);sigemptyset(&obset);sigemptyset(&pending);//3.添加要进行屏蔽的信号sigaddset(&bset, 2);//4.设置set到内核中对应的进程内部int n = sigprocmask(SIG_BLOCK, &bset, &obset);assert(n == 0);(void)n;cout << "block 2号信号成功 " << endl;//5.重复打印当前进程的pending信号集int count = 0;while(true){//获取当前进程的pending信号集sigpending(&pending);//显示当前进程的pending信号集showPending(pending);sleep(1);count++;if(count == 20){int n = sigprocmask(SIG_SETMASK, &obset, nullptr);//将原来的信号集附上去assert(n == 0);(void)n;cout << "接触对2号信号的block " << endl;}}return 0;
}

在这里插入图片描述
注:
没有一个接口时用来设置pending位图的,这是因为所有信号的发送方式,都是修改pending位图的过程;

5.将所有信号都block

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cassert>using namespace std;static void showPending(sigset_t &pending)
{for (int sig = 1; sig <= 31; sig++){if (sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}static void blockSig(int sig)
{sigset_t bset;sigemptyset(&bset);sigaddset(&bset, sig);int n = sigprocmask(SIG_BLOCK, &bset, nullptr);assert(n == 0);(void)n;
}int main()
{for(int sig = 1; sig <= 31; sig++){blockSig(sig);}sigset_t pending;while(true){sigpending(&pending);showPending(pending);sleep(1);}return 0;
}

在这里插入图片描述
当发到9号信号的时候,进程停止,9号信号是不能被屏蔽的;
在这里插入图片描述
跳过9号信号:
在这里插入图片描述
19号也是无法屏蔽
在这里插入图片描述

二、处理信号

1.信号处理的时机

  • 信号产生之后,可能无法被立即处理,要在合适的时候处理;
  • 因为信号的相关数据字段都是在进程PCB内部,这属于内核范畴,进程在运行时会从内核范畴 -> 内核状态 -> 用户态 -> 内核状态 -> 内核范畴;
    在内核态中,从内核态返回用户态的时候,进行信号的检测和处理
  • 当我们进行系统调用的时候,比如缺陷异常等,会进入内核态;int 80是一个系统中断语句,可以陷入内核;
  • 用户态是一个受管控的状态,内核态是一个操作系统执行自己代码的一个状态,具备非常高的优先级;
  • CPU的寄存器是由两套的,一套用户可见,另一套不可见,CPU自用;
  • CR3表示当前CPU的执行权限,1表示内核,3表示用户;
  • 在进程地址空间中,不光有用户地址空间,还有内核地址空间,内核地址空间使用的是内核级的页表,该页表是整个OS只有一份的,能够被所有的进程看到,因此所有进程看到的都是一个操作系统;
    在这里插入图片描述
    当我们进程需要调用系统接口时,就跳转到进程的内核地址空间,根据内核级页表,在内存中找到系统调用的相关方法;
  • 当我们有权限进入内核态时,进程使用的页表就是内核级页表了,就能够访问 OS的方法了,这也就意味着进程进入了内核态,可以处理信号了;

2.信号处理的流程

在这里插入图片描述

  • 注意:
    (1)在第二步时,进程在内核态处理完成系统任务后,会在重回用户态的时候进行信号的检测和处理;
    (2)在第三步检测到信号,并处理时,如果信号的处理方式时系统默认方式,就直接在内核态处理了,然后返回用户态的执行流继续执行;如果信号的处理方式是用户自定义的,就需要返回用户态去执行相应的方法;这时进程的状态时用户态,能够执行自定义信号处理,但是系统不会去在内核态执行用户代码,因为涉及到系统安全问题;
    (3)在第四步返回用户态执行信号处理后,进程会再次进入内核态,从内核态在返回用户态进程中断处继续执行;
    (4)一共四次状态切换;

3.sigaction

在这里插入图片描述

  • 参数:
    signum:信号编号;
    act:信号处理动作;struct sigaction是一个结构体,里面包含用户自定义的信号处理方式的函数指针等数据;
    在这里插入图片描述
    oldact:信号过去的处理方式;
#include<iostream>
#include<signal.h>
#include<unistd.h>using namespace std;void handler(int signum)
{cout << "获取了一个信号:" << signum << endl;
}int main()
{//内核数据类型,用户栈定义的struct sigaction act, oact;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = handler;//设置进当前调用进程的PCB中sigaction(2, &act, &oact);while(true) sleep(1);return 0;
}

运行结果:
在这里插入图片描述
捕获2号信号并执行自定义处理方式;

  • 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的的信号屏蔽字,这样就保证了在处理某个信号时,如果该信号再次产生,那么它就会被阻塞到当前信号处理结束为止;如果在调用信号处理函数时,还希望屏蔽除当前信号的其他信号,就可以使用sigaction函数的sa_mask参数,来指定希望额外屏蔽的信号;
#include<iostream>
#include<signal.h>
#include<unistd.h>using namespace std;static void showPending(sigset_t &pending)
{for (int sig = 1; sig <= 31; sig++){if (sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}void handler(int signum)
{cout << "获取了一个信号:" << signum << endl;sigset_t pending;int c = 10;while(true){sigpending(&pending);showPending(pending);c--;if(!c){break;}sleep(1);}
}int main()
{//内核数据类型,用户栈定义的struct sigaction act, oact;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = handler;//设置进当前调用进程的PCB中sigaction(2, &act, &oact);while(true) sleep(1);return 0;
}

运行结果:
在这里插入图片描述
第二次获取二号信号的时候,就进行了屏蔽;

如果需要同时添加对其他信号的屏蔽:

#include<iostream>
#include<signal.h>
#include<unistd.h>using namespace std;static void showPending(sigset_t &pending)
{for (int sig = 1; sig <= 31; sig++){if (sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}void handler(int signum)
{cout << "获取了一个信号:" << signum << endl;sigset_t pending;int c = 10;while(true){sigpending(&pending);showPending(pending);c--;if(!c){break;}sleep(1);}
}int main()
{//内核数据类型,用户栈定义的struct sigaction act, oact;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = handler;//同时添加对其他信号的屏蔽sigaddset(&act.sa_mask, 3);sigaddset(&act.sa_mask, 4);sigaddset(&act.sa_mask, 5);sigaddset(&act.sa_mask, 6);sigaddset(&act.sa_mask, 7);//设置进当前调用进程的PCB中sigaction(2, &act, &oact);while(true) sleep(1);return 0;
}

运行结果:
在这里插入图片描述

三、可重入函数

在这里插入图片描述
在main函数调用insert方法时,信号来了,调用handler,handler也去调用insert,那么像这样被多个执行流调用insert就叫做函数重入
在这里插入图片描述
函数重入出问题的叫做不可重入函数;
不出问题的叫做可重入函数;

函数的可重入性是函数的一种特征,我们目前使用的大多数函数,都是不可重入的;

四、volatile关键字

当接收到2号信号时,将flag置1,进程退出;
在这里插入图片描述
运行结果:
在这里插入图片描述
如果我们更改编译选项,让g++对代码作出一定的优化:
在这里插入图片描述
运行结果:
在这里插入图片描述
现在进程就无法退出了,但是flag还是变成了1;

这是因为在优化了代码之后,后面的语句没有更改flag,在后面检测flag的时候,就不访问内存中的flag了,而是检测寄存器edx中的flag;而寄存器中的flag是第一次读取的0,因此进程就不会退出了;
在变量定义的时候加上volatile关键字:
在这里插入图片描述
这个关键字的作用是**保持变量在内存中的可见性;**
运行结果:
在这里插入图片描述
注:优化是在编译时就完成的;

五、SIGCHILD信号

在这里插入图片描述
在这里插入图片描述
如果我们需要等待子进程退出,10个子进程5个退出,后面的信号还需要进行wait检测是否退出;
因为5个进程都发送了sigchild信号,但是OS只能收到一个;
这时主进程只能阻塞等待该子进程退出;
我们也可使用vector保存进程pid,来进行非阻塞遍历所有进程,这样不会被阻塞;
也可以在waitpid时候传入-1, 就可以等待任意一个退出的进程,进程也不会被阻塞;

  • 如果我们不想等待子进程,还想在子进程退出之后,自动释放僵尸子进程:可以设置对SIGCHILD信号的忽略
    在这里插入图片描述
    运行结果:
    子进程退出后自动回收僵尸子进程;
    在这里插入图片描述
    sigchild的默认动作就是忽略,但是为什么要再加一个忽略呢?
    因为这两个忽略时不同等级的,OS的忽略就是默认动作,不会回收子进程,会形成僵尸进程;
    而自己设置的忽略,告诉OS不光要忽略子进程,还要回收资源;

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言 字符指针

1、介绍 概念&#xff1a; 字符指针&#xff0c;就是字符类型的指针&#xff0c;同整型指针&#xff0c;指针指向的元素表示整型一样&#xff0c;字符指针指向的元素表示的是字符。 假设&#xff1a; char ch a;char * pc &ch; pc 就是字符指针变量&#xff0c;字符指…

git命令使用

君子拙于不知己,而信于知己。——司马迁 清屏&#xff1a;clear 查看当前面板的路径&#xff1a;pwd 查看当前面板的文件&#xff1a;ls 创建文件夹&#xff1a;mkdir 文件夹名 创建文件&#xff1a;touch 文件名 删除文件夹&#xff1a;rm -rf 文件夹名 删除文件&#xff1a;r…

磁力线试验+多图

今天要磨制一个钢针工具。磨下来很多的铁屑&#xff0c;灵机一动&#xff0c;何不来试验一下磁铁的磁力线。这可是难得的材料。 下放7颗强力磁铁&#xff0c;可见强力磁铁的磁力线非常集中。 下放直径4CM的喇叭磁铁 强力磁铁U型铁 强力磁铁E型铁氧体磁芯&#xff0c;可见磁力线…

高忆管理:简单的选股方法?好股票为什么不涨?

股票是一种很受欢迎的出资方式&#xff0c;但选股并不是一件简略的事。那么简略的选股办法&#xff1f;好股票为什么不涨&#xff1f;高忆管理也为大家预备了相关内容&#xff0c;以供参考。 简略的选股办法&#xff1f; 1、基本面选股&#xff0c;这种办法是依据公司所处职业…

掌握Python的X篇_30_使用python解析网页HTML

本篇将会介绍beutifulsoup4模块&#xff0c;可以用于网络爬虫、解析HTML和XML&#xff0c;对于没有接触过前端&#xff0c;不了解HTML是如何工作的&#xff0c;需要先解释一下什么事HTML。 1. HTML 网页中的各种布局等的背后都是非常简单的纯文本格式&#xff0c;那种格式称为…

【猿灰灰赠书活动 - 02期】- 【Java从入门到精通2023年7月最新(第7版)】

说明&#xff1a;博文为大家争取福利&#xff0c;与清华大学出版社合作进行送书活动 图书&#xff1a;《Java从入门到精通》 一、好书推荐 图书介绍 Java入门经典&#xff0c;95万Java程序员的入行选择。配备升级版Java开发资源库&#xff0c;在线大咖课在线答疑&#xff0c;学…

ffmepg滤镜

视频按顺时针方向旋转90度 ffplay -vf transpose1 -i juren-30s.mp4 ffplay -f lavfi -i testsrc -vf transpose1 -f lavfi -i testsrc这个滤镜是ffmpeg给用户的一个测试使用的视频 视频水平翻转(左右翻转) -vf hflip 实现慢速播放&#xff0c;声音速度是原始速度的50% ffpla…

《起风了》C++源代码

使用方法 Visual Studio、Dev-C、Visual Studio Code等C/C创建一个 .cpp 文件&#xff0c;直接粘贴赋值即可。 #include <iostream> #include <Windows.h> #pragma comment(lib,"winmm.lib") using namespace std; enum Scale {Rest 0, C8 108, B7 …

时序预测 | MATLAB实现基于CNN-LSTM卷积长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于CNN-LSTM卷积长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于CNN-LSTM卷积长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍 MATLAB实现基…

Leetcode-每日一题【剑指 Offer 32 - III. 从上到下打印二叉树 III】

题目 请实现一个函数按照之字形顺序打印二叉树&#xff0c;即第一行按照从左到右的顺序打印&#xff0c;第二层按照从右到左的顺序打印&#xff0c;第三行再按照从左到右的顺序打印&#xff0c;其他行以此类推。 例如: 给定二叉树: [3,9,20,null,null,15,7], 3 / \ 9 20…

[NLP]LLM 训练时GPU显存耗用量估计

以LLM中最常见的Adam fp16混合精度训练为例&#xff0c;分析其显存占用有以下四个部分&#xff1a; GPT-2含有1.5B个参数&#xff0c;如果用fp16格式&#xff0c;只需要1.5G*2Byte3GB显存, 但是模型状态实际上需要耗费1.5B*1624GB. 比如说有一个模型参数量是1M&#xff0c;在…

MySQL 根据多字段查询重复数据

MySQL 根据多字段查询重复数据 在实际的数据库应用中&#xff0c;我们经常需要根据多个字段来查询重复的数据。MySQL 提供了一些方法来实现这个功能&#xff0c;让我们能够快速准确地找到和处理重复数据。本文将介绍如何使用 MySQL 来根据多字段查询重复数据&#xff0c;并提供…