【Linux】第三十九站:可重入函数、volatile、SIGCHLD信号

文章目录

  • 一、可重入函数
  • 二、volatile
  • 三、SIGCHLD信号

一、可重入函数

如下图所示,当我们进行链表的头插的时候,我们刚刚执行完第一条语句的时候,突然收到一个信号,然后我们这个信号的自定义捕捉方法中,正好还有一个头插,于是这个执行流再次进入这个函数中。执行完毕以后,返回到原来的执行流中继续运行。

这种现象就是函数被重入

就会导致下面的现象。

image-20240127195244183

我们可以看到,这个node2结点丢失了,最终导致了内存泄漏了

insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。

上面的现象是这样的:

  1. insert函数被mainh和handler执行流重复进入
  2. 导致了结点丢失,内存泄漏

所以我们有了如下定义:

如果一个函数,被重复进入的情况下,出错了,或者可能出错。

我们就要把这个函数叫做不可重入函数

否则叫做可重入函数

目前我们用到的大部分函数都是不可重入的!

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构

二、volatile

我们先看一下下面的代码

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstring>
using namespace std;int flag = 0;
void handler(int signo)
{cout << "catch a signal: " << signo <<endl;flag = 1;
}int main()
{signal(2, handler);while(!flag);cout << "process quit normal" << endl;return 0;
}

最终我们的结果如下

image-20240127202215532

一切都符合我们的预期

但是在极端情况下,由于main和handler属于两个执行流

编译器检测后发现这个flag没有发生过变化。检测的本质也是计算,逻辑运算,这里的逻辑反也是一种计算。

它会在优化条件下,flag变量可能被直接优化到CPU内的寄存器中。

如下所示,我们的g++可以通过带上O0~O3选项进行优化。后面的数字越大,优化级别越高

image-20240127202846501

如下所示,我们发现,如果是O0,就相当于没有优化,可以正常结束。如果是O1的话,那么此时就无法用二号信号退出了。

image-20240127203114223

如下所示,这是因为我们没有优化之前,CPU会不断的将内存中的数据放入到寄存器中。而我们使用2号信号修改了之后,也还是会不断的访存。所以这个flag会改变,所以就会跳出循环

image-20240127203617825

而现在,我们优化了之后,这个变量第一次拿到寄存器之后,就不再访存了,因为这样可以提高效率,就直接用寄存器当中的数据,而我们使用信号改掉的只是内存当中的数据。所以这里的运算就一直为真了。所以就不会退出了。

image-20240127204052935

这样因为优化,就如同形成了一个寄存器屏障。导致内存不可见了!

所以我们为了防止这样编译器的过度优化,我们可以给这个变量带上volatile关键字。

volatile int flag = 0; //防止编译器过度优化,保存内存的可见性

所以我们代码改为如下

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstring>
using namespace std;volatile int flag = 0;
void handler(int signo)
{cout << "catch a signal: " << signo <<endl;flag = 1;
}int main()
{signal(2, handler);while(!flag);cout << "process quit normal" << endl;return 0;
}

image-20240127204404438

三、SIGCHLD信号

我们之前用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不 能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,程序实现复杂。

其实,子进程在终止时会给父进程发SIGCHLD(17号)信号,该信号的默认处理动作是忽略,父进程可以自 定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程 终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。

我们可以先捕捉一下17号信号,验证一下是否真的有17号信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstring>
using namespace std;void handler(int signo)
{cout << "I am process: " << getpid() << ", catch a signo: " << signo << endl; 
}int main()
{signal(17, handler);pid_t id = fork();if(id == 0){while(true){cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;sleep(1);break;}cout << "child quit...!!!" << endl;exit(0);}//fatherwhile(true){cout << "I am father process: " << getpid() << endl;sleep(1);}return 0;
}

运行结果为

image-20240127211307262

所以利用这个17号信号,我们可以采用基于信号的方式进行等待

等待的好处:

  1. 获取子进程的退出状态,释放子进程的僵尸
  2. 虽然不知道父子谁先运行,但是我们清楚,一定是father最后退出

所以我们还是要调用wait/waitpid这样的接口。而且father必须保证自己是一直在运行的。

所以我们可以试着把子进程等待写入到信号捕捉函数中!

如下代码所示:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstring>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;void handler(int signo)
{sleep(3);pid_t rid = waitpid(-1, nullptr, 0);cout << "I am process: " << getpid() << ", catch a signo: " << signo << "child process quit: " << rid << endl; 
}int main()
{signal(17, handler);pid_t id = fork();if(id == 0){while(true){cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;sleep(3);break;}cout << "child quit...!!!" << endl;exit(0);}//fatherwhile(true){cout << "I am father process: " << getpid() << endl;sleep(1);}return 0;
}

运行结果如下所示

image-20240127212515932

如果有十个进程呢??如果同时退出呢??如果退出一半呢??

如果是个进程同时退出,那么上面代码就有问题了,因为可能一个进程进程正在退出的时候,已经将这个信号屏蔽了,导致有很多进程无法被回收,全部都是僵尸进程了。

如下代码所示,我们在捕捉函数中循环等待,但是要主要加上非阻塞式。否则会一直卡在那里了。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstring>
#include <time.h>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;void handler(int signo)
{sleep(3);pid_t rid;while((rid = waitpid(-1, nullptr, WNOHANG)) > 0){cout << "I am process: " << getpid() << ", catch a signo: " << signo << "child process quit: " << rid << endl; }
}int main()
{srand(time(nullptr));signal(17, handler);for(int i = 0; i < 10; i++){pid_t id = fork();if(id == 0){while(true){cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;sleep(10);break;}cout << "child quit...!!!" << endl;exit(0);}sleep(rand() % 5 + 3);}//fatherwhile(true){cout << "I am father process: " << getpid() << endl;sleep(1);}return 0;
}

image-20240127214154334

事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可 用。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstring>
#include <time.h>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;
int main()
{signal(17, SIG_IGN);srand(time(nullptr));for(int i = 0; i < 10; i++){pid_t id = fork();if(id == 0){while(true){cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;sleep(10);break;}cout << "child quit...!!!" << endl;exit(0);}sleep(rand() % 5 + 3);}//fatherwhile(true){cout << "I am father process: " << getpid() << endl;sleep(1);}return 0;
}

运行结果如下,可以看到是没有僵尸进程的

image-20240127214707488

这里需要注意的是,默认是SIG_DFL,它的动作是忽略。和SIG_IGN是不一样的!!!

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

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

相关文章

“值得一试的六个浏览器扩展推荐|让你的上网更加便捷和有趣!”

iTab新标签页(免费ChatGPT) iTab是新一代组件式标签页的首创者&#xff0c;简洁美观高效无广&#xff0c;是您打造个人学习工作台的浏览器必备插件。 详情请见&#xff1a; iTab新标签页(免费ChatGPT) - Microsoft Edge Addons AdGuard 广告拦截器 AdGuard 广告拦截器可有效的…

GD32移植FreeRTOS+CLI过程记录

背景 之前我只在STM32F0上基于HAL库和CubeMX移植FreeRTOS&#xff0c;但最近发现国产化替代热潮正盛&#xff0c;许多项目都有国产化器件指标&#xff0c;而且国产单片机确实比意法的便宜&#xff0c;所以也买了块兆易创新的GD32F303开发板&#xff0c;试一试它的优劣。虽然GD…

【Web前端实操17】导航栏效果——滑动门

滑动门 定义: 类似于这种: 滑到导航栏的某一项就会出现相应的画面,里面有对应的画面出现。 箭头图标操作和引用: 像一些图标,如果需要的话,可以找字体图标,比如阿里巴巴矢量图标库:iconfont-阿里巴巴矢量图标库 选择一个——>添加至购物车——>下载代码 因…

Facebook的智能时代:AI技术在社交中的崛起

随着科技的快速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术已经深刻改变了我们的生活方方面面&#xff0c;而社交媒体领域也不例外。在这个信息爆炸的时代&#xff0c;Facebook正以令人瞩目的速度推动着AI技术在社交领域的崛起。本文将深入探讨Facebook如何在智能…

STM32控制DS18B20温度传感器获取温度

时间记录&#xff1a;2024/1/28 一、DS18B20温度传感器介绍 &#xff08;1&#xff09;测温范围-55℃~125℃&#xff0c;在-10℃到85℃范围内误差为0.4 &#xff08;2&#xff09;返回的温度数据为16位二进制数据 &#xff08;3&#xff09;STM32和DS18B20通信使用单总线协议…

Nginx解析漏洞复现

首先这个漏洞不是软件或代码的问题&#xff0c;是认为疏忽造成的。 一、环境搭建 从vulhub上面下载vulhub-master.zip文件&#xff0c;上传到服务器中&#xff0c;或者直接在服务器下载。 unzip vulhub-master.zip 进入漏洞目录 cd /vulhub-master/vulhub-master/nginx/ng…

【electron】打包问题处理

目录 项目无法在win7执行场景尝试处理 项目无法在win7执行 场景 使用electron25.0.1、electron-builder24.2.1&#xff0c;打出来的项目在win7系统上跑不起来&#xff0c;报错无法定位程序输入点DiscardVirtualMemoty于动态链接库KERNEL32.dll上。 尝试处理 通过百度发现ele…

高分文献解读|乳酸通过与可溶性腺苷酸环化酶结合调控铁代谢

乳酸(LA)的过量产生可能发生在运动期间或者许多疾病中&#xff0c;例如癌症中。个人伴有高乳酸血症的患者常表现为贫血、血清铁减少以及一种铁代谢关键调控因子—铁调素&#xff08;hepcidin&#xff09;升高。然而&#xff0c;目前尚不清楚乳酸是否以及如何调节铁调素的表达。…

算法39:统计全 1 子矩形(力扣1504)----单调栈

题目: 给你一个 m x n 的二进制矩阵 mat &#xff0c;请你返回有多少个 子矩形 的元素全部都是 1 。 示例 1&#xff1a; 输入&#xff1a;mat [[1,0,1],[1,1,0],[1,1,0]] 输出&#xff1a;13 解释&#xff1a; 有 6 个 1x1 的矩形。 有 2 个 1x2 的矩形。 有 3 个 2x1 的矩…

ERP定制费用怎么算?详解跨境电商成本控制策略

在全球化数字经济的浪潮中&#xff0c;跨境电商行业具有潜在的高利润和巨大的发展空间&#xff0c;吸引着众多创业者的关注和投入。然而&#xff0c;与潜在利润相伴随的是种种挑战&#xff0c;成本控制便是其中关键的一环。在这一行业中&#xff0c;ERP定制费用作为一个重要的成…

第十三章认识Ajax(四)

认识FormData对象 FormData对象用于创建一个表示HTML表单数据的键值对集合。 它可以用于发送AJAX请求或通过XMLHttpRequest发送表单数据。 以下是FormData对象的一些作用&#xff1a; 收集表单数据&#xff1a;通过将FormData对象与表单元素关联&#xff0c;可以方便地收集表…

C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现

C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现 —— 杭州 2024-01-28 code review! 文章目录 C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现1.RTTI、RAII、MVC、MVVM、SOLID简述2.RAII (Resource Acquisition Is Initialization)3.RTTI (Run-Time Type Informat…

Springboot响应数据详解

功能接口 Controller下每一个暴露在外的方法都是一个功能接口 功能接口的请求路径是RequestMapping定义的路径&#xff0c;浏览器需要请求该功能则需要发出该路径下的请求。 RestController RestControllerControllerResponseBody(响应数据的注解) ResponseBody 类型&#…

【数据分析】numpy基础第二天

文章目录 前言数组的形状变换reshape的基本介绍使用reshapereshape([10, 1])运行结果reshape自动判断形状reshape([-1, 1])运行结果 合并数组使用vstack和hstackvstack和hstack的运行结果使用concatenateconcatenate运行结果 分割数组array_split运行结果 数组的条件筛选条件筛…

Matlab|【完全复现】基于价值认同的需求侧电能共享分布式交易策略

目录 1 主要内容 2 部分程序 3 程序结果 4 下载链接 1 主要内容 该程序完全复现《基于价值认同的需求侧电能共享分布式交易策略》&#xff0c;针对电能共享市场的交易机制进行研究&#xff0c;提出了基于价值认同的需求侧电能共享分布式交易策略&#xff0c;旨在降低电力市…

腾讯云幻兽帕鲁服务器创建教程,附4核16G服务器价格表

腾讯云0基础搭建帕鲁服务器4C16G14M服务器稳定无卡顿&#xff0c;先下载SteamCMD&#xff0c;并运行&#xff1b;然后下载Palserver&#xff0c;修改服务ini配置&#xff0c;启动PalServer&#xff0c;进入游戏服务器。腾讯云百科txybk.com分享腾讯云创建幻兽帕鲁服务器教程&am…

写点东西《JWT 与会话身份验证》

写点东西《JWT 与会话身份验证》 身份验证与授权 JWT 与session身份验证 - 基本差异 什么是 JWT&#xff1f; JWT 结构&#xff1a; JWT 工作流程&#xff1a;优势: 安全问题&#xff1a; 处理令牌过期&#xff1a; 基于session的身份验证&#xff08;通常称为基于 cookie 的身…

深度强化学习(王树森)笔记07

深度强化学习&#xff08;DRL&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接&#xff1a;https://github.com/wangshusen/DRL 源代码链接&#xff1a;https://github.c…

Qt|大小端数据转换

后面打算写Qt关于网络编程的博客&#xff0c;网络编程就绕不开字节流数据传输&#xff0c;字节流数据的传输一般是根据协议来定义对应的报文该如何组包&#xff0c;那这就必然牵扯到了大端字节序和小端字节序的问题了。不清楚的大小端的可以看一下相关资料&#xff1a;大小端模…

【华为 ICT HCIA eNSP 习题汇总】——题目集11

1、某公司的内网用户采用 NAT 技术的 NO-pat 方式访问互联网&#xff0c;若所有的公网地址均被使用&#xff0c;则后续上网的内网用户会&#xff08;&#xff09;。 A、挤掉前一个用户&#xff0c;强制进行 NAT 转换上网 B、将报文同步到其他 NAT 转换设备上进行 NAT 转换 C、自…