信号保存和信号处理

目录

信号保存中重要的概念

内核中信号的保存

对sigset_t操作的函数

对block,pendding,handler三张表的操作

sigpromask

​编辑

sigpending

是否有sighandler函数呢?

案例

信号处理

操作系统是如何运行的?

硬件中断 

时钟中断

软中断

死循环

缺⻚中断?内存碎⽚处理?除零野指针错误?

理解用户态和内核态

信号的自定义捕捉 

sigaction

验证阻塞信号

处理完解除对信号的阻塞

验证在处理前将pending表清零 

验证sa_mask的作用

可重入函数

案例

volatile

SIGCHLD信号

waitpid补充

验证子进程退出会给父进程发送SIGCHLD信号

 基于信号对子进程回收

问题1:

解决方法循环回收

问题2: 


信号保存中重要的概念

信号捕捉的三种方式:1.默认 SIG_DFL  default

                                    2.忽略  SIG_ING    ingore

                                    3.自定义

信号递达:实际执行信号的处理动作

信号未决:信号从产生到递达的状态

信号阻塞:进程可以阻塞某个信号  -->阻塞/屏蔽特定信号,信号产生了,一定把信号pendding(保存),并且信号永远不递达,除非解除阻塞。

os信号的保存都与以上的三个概念有关

阻塞 vs 忽略

阻塞发生在为信号递达之前

忽略发生在信号递达

内核中信号的保存

内核数据结构示意图

pending:信号未决

block: 信号阻塞

handler:信号递达

进程维护了以上的结构,所以进程是可以识别信号的。

以后对进程信号的操作都是为绕着这三张表展开的。

内核中的相应的数据结构

 sigset_t 是信号集,也是信号屏蔽字(signal mask)(有block,pending)

类似umask

对sigset_t操作的函数

对block,pendding,handler三张表的操作

sigpromask

读取/修改进程屏蔽字 block表 

set:进程中的block表修改基于set 

oldset:修改前block表的模样

how:以何种方式修改mask(block表)

1. SIG_BLOCK:  mask = mask | set  新增

2.SIG_UNBLOCK : mask = mask & ~set  去除指定信号

3.SIG_SETMASK: mask = set  覆盖

sigpending

通过该函数可以知道pending表的情况

但为什么不设置一个输入型参数,修改pending表呢?

因为信号的产生都是在修改pending表

是否有sighandler函数呢?

答案:没有

那么如何修改handler表呢?

signal就一直在修改啊

是否有恍然大悟的感觉啊。

案例

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
#include <wait.h>
#include <vector>
void PrintBlock(const sigset_t &block)
{std::cout<<"block"<<"["<<getpid()<<"]: ";for (int i = 31; i > 0; i--){if (sigismember(&block, i)){std::cout << 1;}else{std::cout << 0;}}std::cout << std::endl;
}void handler(int signo)
{std::cout <<"signo:"<<signo<<" 信号递达"<<std::endl;exit(0);
}
int main()
{signal(2,handler);sigset_t block, oldblock;sigemptyset(&block);// 将二号新号加入sigaddset(&block, 2);//阻塞二号信号int ret = sigprocmask(SIG_SETMASK, &block, &oldblock);if (ret < 0){perror("sigpromask");exit(1);}int cnt = 0;while (true){PrintBlock(block);sleep(3);if(cnt==5){PrintBlock(oldblock);//取消对二号信号的阻塞sigprocmask(SIG_SETMASK, &oldblock, nullptr);}cnt++;}return 0;
}

 执行结果

一开始block表中二号信号被阻塞,即使收到了二号信号也不能处理,当修改block表二号没被阻塞时,在执行二号信号相应的处理方法。

信号处理

信号没被处理,被保存起来---->合适的时候

那么什么是合适的时候?

进程从用户态切换会用户态时,检测额pending && block,决定是否用handler表处理信号。

os的运行状态:

1.用户态 ---> 执行用户写的代码

2.内核态  -->执行内核代码

信号自定义捕捉的流程

操作系统是如何运行的?

硬件中断 

硬件中断的处理流程

 

中断向量表在os启动时就填充好了。

时钟中断

进程是由操作系统调度的,那么操作系统谁有谁调度的呢?

有个设备时钟源定期向cpu发送中断,cpu处理中断,执行中断处理历程,而查表查到的终端服务是进程调度。

这样操作系统不就能自主调度了吗

时钟源是外设,将中断传给cpu效率有点低,现在的时钟源都是继承在cpu中。

主频就是控制CPU工作的时钟信号的频率

主频越快,相应单位时间内操作系统执行的中断服务越多,进程调度越频繁,os的性能越高。

进程调度不一定切换进程

软中断

是否能不通过外设来产生中断呢?

可以,比如os支持系统调用,cpu设计了汇编指令(int 0x80 或 syscall),让cpu在内部触发中断逻辑,

这样就可已在软件中触发中断-->再根据中断号,查系统调用表,执行相应的系统调用

系统调用号的本质就是数组下标

死循环

无论是软中断还是硬件中断

os需要什么方法直接,向中断向量表中添加就可以了:os的本质就是一个死循环

void main(void) /* 这⾥确实是void,并没错。 */
{ /* 在startup 程序(head.s)中就是这样假设的。 */
...
/*
* 注意!! 对于任何其它的任务,'pause()'将意味着我们必须等待收到⼀个信号才会返
* 回就绪运⾏态,但任务0(task0)是唯⼀的意外情况(参⻅'schedule()'),因为任
* 务0 在任何空闲时间⾥都会被激活(当没有其它任务在运⾏时),
* 因此对于任务0'pause()'仅意味着我们返回来查看是否有其它任务可以运⾏,如果没
* 有的话我们就回到这⾥,⼀直循环执⾏'pause()'。
*/
for (;;)
pause();
} // end main

---------------------------------------------------------------------------------------------------------------------------------

用户怎么把中断号给os?寄存器(如eax)

os怎么把返回值返回给用户? 也是寄存器

用户和内核之间传递信息用的是寄存器。

系统调用-->是通过软中断完成的

中断号-->系统调用号写入寄存器eax-->软中断-->查表--->执行相应的方法-->将返回值写入寄存器返回给用户

既然执行系统调用需要触发软中断,那我们使用系统调用函数值没有用 int 0x80 或 syscall?

因为linux提供的系统调用接口,不是C语言函数,而是  系统调用号 + 约定的传递参数 ,用于内核和用户间传递信息的 系统调用号和返回值的寄存器  + syscall  或 int 0x80

也就是说GNU glibc 把真正的系统调用封装--->C语言封装版的系统调用

OS是躺在中断处理历程上的代码块。


 

缺⻚中断?内存碎⽚处理?除零野指针错误?

这些错误都是通过软中断来处理的。

软中断有: 1.陷阱  (int 0x80 或 syscall)

                    2.异常

CPU内部的软中断,⽐如int 0x80或者syscall,我们叫做 陷阱
CPU内部的软中断,⽐如除零/野指针等,我们叫做 异常。(所以,能理解“缺⻚异常”
为什么这么叫了吗?)

理解用户态和内核态

内核页表:整个系统只有一张

用户页表:每个进程都有一张

 对于任何进程,不管如何调度,任何进程都只能找到一个os

为什么只有一张内核页表?

因为用户最关心的是系统调用。

用户关心系统调用的地址吗?

不关心,只需要知道系统调用号就可以了。

系统调用是在进程的地址空间中发起的,但其执行是在内核地址空间中完成的。

1.调用任何函数(库中)都是在我们自己进程中的地址空间中进行的

操作系统无论怎么切换进程,进程都只能找到一个操作系统

2.os调用方法的执行是在内核地址空间中执行的

不管是通过哪个进程的地址空间进入内核,都是通过软中断进行操作的。

⽤⼾态就是执⾏⽤⼾[0,3]GB时所处的状态
内核态就是执⾏内核[3,4]GB时所处的状态
如何区分内核态还是用户态?
cpu中有cs段寄存器

用户如何进入内核态?

1.时钟/外设中断

2.异常   cpu--->软中断

3.陷阱    系统调用(int 0x80,syscall)

信号的自定义捕捉 

1.前三步很好理解,那么为什么要转为用户态来调用处理方法,直接在内核态来处理不好吗?

 内核和用户的权限不同,有安全风险;并且用户来处理,如果出了错误用户担责任。

2.信号处理完只能从内核返回,因为处理函数的调用与main()函数之间不存在调用关心(栈区)

3.如何继续执行原来的程序?

cpu中有pc寄存器(pc指针),指向下一条要执行的命令,只需要恢复pc指针的值就可以了。

sigaction

作用检查并修改handler表

sigaction结构 

sa_handler :函数指针

sa_mask:自定义屏蔽的信号 

信号处理期间,如果处理方法有系统调用,那么就陷入内核态

os不允许信号处理方法嵌套--->实现该方法的方式是将block表相应的信号置为1(阻塞该信号),但信号处理完会自动解除阻塞

什么时候pending表清0?

在调用信号处理之前,因为如果是处理完之后清零,那么就无法区分pending中的1是处理进程时 收到的1,还是处理完时的1                                                                                                                                                                                                                

验证阻塞信号

void PrintBlock()
{sigset_t block;sigprocmask(SIG_SETMASK,nullptr,&block);for(int i = 31;i>0;i--){if(sigismember(&block,i))std::cout<<1;elsestd::cout<<0;}std::cout<<std::endl;
}
void handler(int signo)
{std::cout<<"signo: "<<signo<<std::endl;while(true){PrintBlock();sleep(3);}
}int main()
{//signal(2,handler);struct sigaction act,oldact;act.sa_handler = handler;sigaction(2,&act,&oldact);//std::cout<<"执行"<<std::endl;PrintBlock();while(true){pause();}
}

处理完解除对信号的阻塞

void PrintBlock()
{sigset_t block;sigprocmask(SIG_SETMASK,nullptr,&block);for(int i = 31;i>0;i--){if(sigismember(&block,i))std::cout<<1;elsestd::cout<<0;}std::cout<<std::endl;
}
void handler(int signo)
{std::cout<<"signo: "<<signo<<std::endl;// while(true)// {//     PrintBlock();//     sleep(3);// }PrintBlock();sleep(3);
}int main()
{//signal(2,handler);struct sigaction act,oldact;act.sa_handler = handler;sigaction(2,&act,&oldact);//std::cout<<"执行"<<std::endl;PrintBlock();while(true){pause();PrintBlock();}
}

验证在处理前将pending表清零 

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
#include <wait.h>
#include <vector>
void PrintBlock()
{sigset_t block;sigprocmask(SIG_SETMASK, nullptr, &block);std::cout<<"block: ";for (int i = 31; i > 0; i--){if (sigismember(&block, i))std::cout << 1;elsestd::cout << 0;}std::cout << std::endl;
}
void PrintPending()
{sigset_t pending;sigpending(&pending);sigprocmask(SIG_SETMASK, nullptr, &pending);std::cout<<"pending: ";for (int i = 31; i > 0; i--){if (sigismember(&pending, i))std::cout << 1;elsestd::cout << 0;}std::cout << std::endl;
}
void handler(int signo)
{std::cout << "signo: " << signo << std::endl;// while(true)// {//     PrintBlock();//     sleep(3);// }PrintBlock();PrintPending();sleep(3);
}int main()
{// signal(2,handler);struct sigaction act, oldact;act.sa_handler = handler;sigaction(2, &act, &oldact);// std::cout<<"执行"<<std::endl;PrintBlock();PrintPending();while (true){pause();PrintBlock();PrintPending();}
}

 

验证sa_mask的作用

void handler(int signo)
{std::cout << "signo: " << signo << std::endl;// while(true)// {//     PrintBlock();//     sleep(3);// }PrintBlock();//PrintPending();while(true){}//sleep(3);
}int main()
{// signal(2,handler);struct sigaction act, oldact;act.sa_handler = handler;sigset_t mask;sigemptyset(&mask);sigaddset(&mask,2);sigaddset(&mask,3);//把三号信号屏蔽act.sa_mask = mask;//设置屏蔽信号sigaction(2, &act, &oldact);// std::cout<<"执行"<<std::endl;PrintBlock();//PrintPending();while (true){pause();//PrintBlock();//PrintPending();}
}

屏蔽了2号和3号信号 

可重入函数

一个函数被两个以上的执行流同时进入--->重入(重复进入)

重入,不出问题--->可重入函数

重入,出问题---> 不可重入函数--->涉及对全局资源的处理

大部分库中的函数都是不可重入函数

重入和不可重入没有好坏之分,只是特性。

案例

因为中断,将node2插入链表,head指向node2,但返回main函数继续执行时,head又指向了node1导致找不到node2,也就造成了内存泄漏。main和信号的处理是两个执行流,进入的都是inset函数。

volatile

volatile是易变关键字

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int flag = 1;
void handler(int signo)
{printf("signo:%d\n",signo);printf("flag -> 0\n");flag = 0;}
int main()
{signal(2,handler);while(flag);printf("quit normal!\n");return 0;
}

 没开启编译器优化,会实时更新进程地址空间中flag的值

当把编译器的优化等级开高一些,为什么不退出了呢?

开了优化后,即使进程内存地址空间中的flag改变了,但cpu中的flag不会根据进程地址空间中的flag更新寄存器中的flag,所以一直死循环没有退出 。

volatile flag = 1;//加上volatile关键字

即使优化编译器优化等级开高了,也实时通过进程地址空间中值修改寄存器中的值。 

SIGCHLD信号

当子进程退回后----(给父进程发送)--->SIGCHLD信号

waitpid补充

pid为-1时,可以等待任何子进程。

验证子进程退出会给父进程发送SIGCHLD信号

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
void handler(int signo)
{std::cout << "signo: " << signo << std::endl;pid_t ret = waitpid(-1, nullptr, 0);//pid==-1,表示等待任何子进程if (ret > 0){std::cout<<"子进程: "<<ret<<"退出"<<std::endl;}else if (ret < 0){std::cout << "wait error" << std::endl;}sleep(3);
}
int main()
{signal(SIGCHLD, handler);pid_t pid = fork();if (pid == 0){std::cout << "我是子进程: ";std::cout << getpid() << std::endl;exit(0);}std::cout << "我是父进程: " << getpid() << std::endl;while (true);return 0;
}

 基于信号对子进程回收

问题1:

如果有n个子进程同时给父进程发送SIGCHLD信号,这是只能记录和处理一个信号;

如果pending记录了一个信号,其他的信号再发过来也不会被记录。

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
void handler(int signo)
{std::cout << "signo: " << signo << std::endl;pid_t ret = waitpid(-1,nullptr,0);if(ret>0){std::cout << "子进程: " << ret << "退出" << std::endl;}else if(ret<0){std::cout<<"wait error"<<std::endl;}
}
int main()
{signal(SIGCHLD, handler);pid_t pid = fork();for (int i = 0; i < 5; i++){pid_t pid = fork();if (pid == 0){std::cout << "我是子进程: ";std::cout << getpid() << std::endl;exit(0);}}while(true);return 0;
}

创建了10个子进程只有7个子进程退出,剩余的子进程发出的信号没有被处理,这些子进程变为了僵尸

解决方法循环回收 

void handler(int signo)
{while (true){pid_t ret = waitpid(-1, nullptr, 0);if (ret > 0){std::cout << "子进程: " << ret << "退出" << std::endl;// std::cout << "wait success" << std::endl;}else if (ret < 0){std::cout << "暂时回收完毕" << std::endl;break;}}
}

运行结果:子进程全都被回收 

 

没有僵尸子进程

问题2: 

基于问题1,如果10个子进程中,有几个进程迟迟不退出那么,就会阻塞在信号处理那里等待子进程的退出

解决方法

options中有个WNOHANG,如果没有子进程结束则立即返回0

这样处理方法就有阻塞变为了非阻塞,如果有子进程退出则又会在进入循环回收,一次往复,避免了子进程出现僵尸。 

pid_t ret = waitpid(-1, nullptr, WNOHANG);

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

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

相关文章

基于HTTP编写ping操作

基于HTTP编写ping操作 前言 在上一集我们就完成了创建MockServer的任务&#xff0c;那么我们就可以正式开始进行网络的通讯&#xff0c;那么我们今天就来基于HTTP来做一个客户端ping服务端的请求&#xff0c;服务端返回pong的响应。 需求分析 基于HTTP&#xff0c;实现ping…

机器学习 贝叶斯公式

这是条件概率的计算公式 &#x1d443;(&#x1d434;|&#x1d435;)&#x1d443;(B|A)&#x1d443;(&#x1d434;)/&#x1d443;(&#x1d435;) 全概率公式 &#x1d443;(&#x1d435;)&#x1d443;(&#x1d435;|&#x1d434;)&#x1d443;(&#x1d434;)&am…

【工具插件类教学】在 Unity 中使用 iTextSharp 实现 PDF 文件生成与导出

目录 一、准备工作 1. 安装 iTextSharp 2. 准备资源文件 二、创建 ExportPDFTool 脚本 1、初始化 PDF 文件,设置字体 2、添加标题、内容、表格和图片 三、使用工具类生成 PDF 四、源码地址 在 Unity 项目中,我们有时会需要生成带有文本、表格和图片的 PDF 文件,以便…

Java 责任链模式 减少 if else 实战案例

一、场景介绍 假设有这么一个朝廷&#xff0c;它有 县-->府-->省-->朝廷&#xff0c;四级行政机构。 这四级行政机构的关系如下表&#xff1a; 1、县-->府-->省-->朝廷&#xff1a;有些地方有完整的四级行政机构。 2、县-->府-->朝廷&#xff1a;直…

vue项目使用eslint+prettier管理项目格式化

代码格式化、规范化说明 使用eslintprettier进行格式化&#xff0c;vscode中需要安装插件ESLint、Prettier - Code formatter&#xff0c;且格式化程序选择为后者&#xff08;vue文件、js文件要分别设置&#xff09; 对于eslint规则&#xff0c;在格式化时不会全部自动调整&…

Leetcode 整数转罗马数字

这段代码的算法思想是基于罗马数字的减法规则&#xff0c;将整数转换为罗马数字的字符串表示。下面是详细的解释&#xff1a; 算法步骤&#xff1a; 定义数值和符号对应关系&#xff1a;代码中定义了两个数组&#xff1a;values 和 symbols。values 数组包含了罗马数字的数值&…

web——sqliabs靶场——第六关——报错注入和布尔盲注

这一关还是使用报错注入和布尔盲注 一. 判断是否有sql注入 二. 判断注入的类型 是双引号的注入类型。 3.报错注入的检测 可以使用sql报错注入 4.查看库名 5. 查看表名 6.查看字段名 7. 查具体字段的内容 结束 布尔盲注 结束

Spring Cloud Eureka 服务注册与发现

Spring Cloud Eureka 服务注册与发现 一、Eureka基础知识概述1.Eureka两个核心组件2.Eureka 服务注册与发现 二、Eureka单机搭建三、Eureka集群搭建四、心跳续约五、Eureka自我保护机制 一、Eureka基础知识概述 1.Eureka两个核心组件 Eureka Server &#xff1a;服务注册中心…

CAN通讯演示(U90-M24DR)

概述 CAN通讯一般用的不多&#xff0c;相比于Modbus通讯不是特别常见&#xff0c;但也会用到&#xff0c;下面介绍一下CAN通讯&#xff0c;主要用U90军用PLC演示一下具体的数据传输过程。想更具体的了解的话&#xff0c;可以自行上网学习&#xff0c;此处大致介绍演示。…

时序论文19|ICML24 : 一篇很好的时序模型轻量化文章,用1k参数进行长时预测

论文标题&#xff1a;SparseTSF: Modeling Long-term Time Series Forecasting with 1k Parameters 论文链接&#xff1a;https://arxiv.org/pdf/2402.01533 代码链接&#xff1a;https://github.com/lss-1138/SparseTSF 前言 最近读论文发现时间序列研究中&#xff0c;模型…

(动画版)排序算法 -希尔排序

文章目录 1. 希尔排序&#xff08;Shellsort&#xff09;1.1 简介1.2 希尔排序的步骤1.3 希尔排序的C实现1.4 时间复杂度1.5 空间复杂度1.6 希尔排序动画 1. 希尔排序&#xff08;Shellsort&#xff09; 1.1 简介 希尔排序&#xff08;Shells Sort&#xff09;&#xff0c;又…

Python学习从0到1 day26 第三阶段 Spark ④ 数据输出

半山腰太挤了&#xff0c;你该去山顶看看 —— 24.11.10 一、输出为python对象 1.collect算子 功能: 将RDD各个分区内的数据&#xff0c;统一收集到Driver中&#xff0c;形成一个List对象 语法&#xff1a; rdd.collect() 返回值是一个list列表 示例&#xff1a; from …

DNS解析库

DNS解析库 dnsDNS的解析库以及域名的详解解析库dns解析的端口dns域名的长度限制流程优先级在现实环境中实现内网的dns解析 练习&#xff08;Ubuntu内网实现DNS解析&#xff09;主服务器备服务器 dns 域名系统&#xff0c;域名和ip地址互相映射的一个分布式的数据库&#xff0c…

kafka 生产经验——数据积压(消费者如何提高吞吐量)

bit --> byte --> kb -->mb -->gb --> tb --> pb --> eb -> zb -->yb

【记录】公司管理平台部署:容器化部署

前置条件 技能要求 了解Docker基本使用和常用命令。会写Dockerfile文件。会写docker-compose文件环境要求 云服务器,已安装好安装Docker本机 IntelliJ IDEA 2022.1.3配置 配置服务器SSH连接 进入 Settings -> Tools -> SSH Configurations 点击加号创建SSH连接配置 填…

从零开始 blender插件开发

blender 插件开发 文章目录 blender 插件开发环境配置1. 偏好设置中开启相关功能2. 命令行打开运行脚本 API学习专有名词1. bpy.data 从当前打开的blend file中&#xff0c;加载数据。2. bpy.context 可用于获取活动对象、场景、工具设置以及许多其他属性。3. bpy.ops 用户通常…

el-table 行列文字悬浮超出屏幕宽度不换行的问题

修改前的效果 修改后的效果 ui框架 element-plus 在网上找了很多例子都没找到合适的 然后这个东西鼠标挪走就不显示 控制台也不好调试 看了一下El-table的源码 他这个悬浮文字用的el-prpper 包着的 所以直接改 .el-table .el-propper 设置为max-width:1000px 就可以了 吐槽一…

Tcp中的流量控制,拥塞控制,超时重传时间的选择,都附带相应例子说明

端口号的了解 通常进行通信时&#xff0c;发送方使用任意端口&#xff0c;指定接收方为指定端口&#xff0c;因为接收方在接收到后的需要根据发送方指定的接收方端口号&#xff0c;来选择使用哪一个服务进程进行处理。 端口号还可以分类为两个大类&#xff1a; TCP和UDP报文的…

Nextflow最佳实践:如何在云上高效处理大规模数据集

1. Nextflow 软件架构介绍 Nextflow 是一个用于简化数据驱动计算流程的工具&#xff0c;可以在各种计算环境中轻松部署。它采用了分布式计算和容器技术&#xff0c;实现了高度模块化、可重复性和可扩展性。NextFlow 的软件架构主要包括以下几个部分&#xff1a; 用户界面&…

一文看懂ERP、SCM、SRM、WMS、TMS、进销存管理系统

经常有人来私信问我ERP、SCM、SRM、WMS、TMS、进销存管理系统等等&#xff0c;它们听起来都很专业&#xff0c;但到底各自是什么&#xff1f;承担着怎样的角色呢&#xff1f;它们具体都有哪些功能&#xff1f;相互之间又存在怎样的关联&#xff0c;对企业而言又意味着什么呢&am…