【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);
    }
    //father
    while(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);
    }
    //father
    while(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);
    }
    //father
    while(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);
    }
    //father
    while(true)
    {
        cout << "I am father process: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

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

image-20240127214707488

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python模拟艾里光束:光可以不沿直线传播

文章目录 Airy光束有限能量Airy光束 Airy光束 在光学领域&#xff0c;傍轴近似下光束传输遵循方程 i ∂ ϕ ∂ z 1 z a ∂ 2 ϕ ∂ x 2 0 i\frac{\partial\phi}{\partial z}\frac{1}{z}\frac{a\partial^2\phi}{\partial x^2}0 i∂z∂ϕ​z1​∂x2a∂2ϕ​0 其中 k 2 π n …

【发展】不确定时代下的从容 —— 终局思维、长期主义与复利

文章目录 一、终局思维1、电影 《蝴蝶效应》2、未来是什么样的 二、长期主义1、这是一个不确定的时代2、做难但正确的事情 三、复利1、复利思维2、马太效应 一、终局思维 终局思维 在面对很多选择时&#xff0c;从终点出发考虑问题&#xff0c;来决定当下的选择。 1、电影 《蝴…

容器和虚拟机的对比

容器和虚拟机的对比 容器和虚拟机在与硬件和底层操作系统交互的方式上有所不同 虚拟化 使多个操作系统能够同时在一个硬件平台上运行。 使用虚拟机监控程序将硬件分为多个虚拟硬件系统&#xff0c;从而允许多个操作系统并行运行。 需要一个完整的操作系统环境来支持该应用。…

从零开始:CentOS系统下搭建DNS服务器的详细教程

前言 如果你希望在CentOS系统上建立自己的DNS服务器,那么这篇文章绝对是你不容错过的宝藏指南。我们提供了详尽的步骤和实用技巧,让你能够轻松完成搭建过程。从安装必要的软件到配置区域文件,我们都将一一为你呈现。无论你的身份是运维人员,还是程序员,抑或是对网络基础设…

GitLab16.8配置webhooks、Jenkins2.4配置GitLab插件实现持续集成、配置宝塔面板实现持续部署

看本篇文章的前提是已经部署完GItlab和Jenkins服务器&#xff0c;已经可以手动构建成功&#xff0c;并且经过了很多次实践&#xff0c;对这两款软件基本熟悉。 建议大家按以下顺序看 前端自动化&#xff08;其一&#xff09;部署gitlab https://blog.csdn.net/weixin_45062076…

DolphinScheduler + Amazon EMR Serverless 的集成实践

01 背景 Apache DolphinScheduler 是一个分布式的可视化 DAG 工作流任务调度开源系统&#xff0c;具有简单易用、高可靠、高扩展性、⽀持丰富的使用场景、提供多租户模式等特性。适用于企业级场景&#xff0c;提供了一个可视化操作任务、工作流和全生命周期数据处理过程的解决方…

2024.1.24 C++QT 作业

思维导图 练习题 1.提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数 #include <iostream> #include <string.h> #include <array> using namespace std;int main() {string str;cout << "…

《微信小程序开发从入门到实战》学习九十六

7.2 基础内容组件 7.2.4 progress组件 progress组件的示例代码如下&#xff1a; <progress percent"20" show-info /> 7.3 表单组件 表单组件是用于收集信息的组件。第三章介绍了许多表单组件&#xff0c;包括form、input、textarea、picker、switch、butt…

在WebSocket中使用Redis出现空指针异常解决方案

文章目录 在WebSocket中使用Redis1.问题描述2.原因3.解决步骤1.新建一个SpringUtil.java类&#xff0c;通过getBean的方法主动获取实例2.在WebSocketSingleServer.java中导入 在WebSocket中使用Redis 1.问题描述 在controller 和 service中都可以正常使用Redis&#xff0c;在…

【Javaweb程序设计】【C00161】基于SSM电子产品交易管理系统(论文+PPT)

基于SSM电子产品交易管理系统&#xff08;论文PPT&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的电子产品交易系统 本系统分为前台用户和后台管理员2个功能模块. 前台用户模块&#xff1a;当游客打开系统的网址后&#xff0c;首先看到的就…

qemu + vscode图形化调试linux kernel

一、背景 使用命令行连接gdb 在调试时&#xff0c;虽然可以通过tui enable 显示源码&#xff0c;但还是存在设置断点麻烦&#xff08;需要对着源码设置&#xff09;&#xff0c;terminal显示代码不方便&#xff0c;不利于我们学习&#xff1b;另外在gdb 下p命令显示结构体内容…

猫用空气净化器哪款牌子好?好用能吸毛的宠物空气净化器推荐

作为一个养猫多年的铲屎官&#xff0c;我真的无法抗拒猫星人的可爱魅力&#xff01;以前&#xff0c;每当我路过宠物店&#xff0c;我总会忍不住停下来&#xff0c;在玻璃窗前停留半个小时以上。但是后来&#xff0c;我终于有了自己的猫咪。每天都能享受到给它摸小肚子的乐趣&a…

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

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

gdb 调试 - 在vscode图形化展示在远程的gdb debug过程

前言 本地机器的操作系统是windows&#xff0c;远程机器的操作系统是linux&#xff0c;开发在远程机器完成&#xff0c;本地只能通过ssh登录到远程。现在目的是要在本地进行图形化展示在远程的gdb debug过程。&#xff08;注意这并不是gdb remote &#xff01;&#xff01;&am…

产业需求大数据平台

产业需求大数据平台&#xff0c;依托大数据、NLP等技术&#xff0c;全面采集区域产业人才需求数据&#xff0c;从宏观、中观、微观三个层面对产业需求进行分析&#xff0c;并匹配学校自身的办学定位、专业布局、人才培养目标、培养规格和课程设置&#xff0c;进行专业设置匹配度…

【JavaScript基础入门】03 JavaScript 基础语法(一)

JavaScript 基础语法&#xff08;一&#xff09; 目录 JavaScript 基础语法&#xff08;一&#xff09;1. JS 初体验2. JavaScript注释2.1 单行注释2.2 多行注释 3. JavaScript结束符4. JavaScript输入输出语句 1. JS 初体验 JS 有3种书写位置&#xff0c;分别为内联、内部和外…

2024.1.28 GNSS 学习笔记

1.基于 地球自转改正卫地距 以及 伪距码偏差 重构定位方程&#xff1a; 先验残差计算公式如下所示&#xff1a; 2.观测值如何定权&#xff1f;权重如何确定&#xff1f; 每个卫星的轨钟精度以及电离层模型修正后的误差都有差异&#xff0c;所以我们不能简单的将各个观测值等权…

Hotspot源码解析-第25章-类的初始化

第25章-类的初始化 这一章主要是讲类的初始化操作&#xff0c;后续类加载章节中也会用到这一章的知识&#xff0c;只不过&#xff0c;这里就讲&#xff0c;是因为虚拟在初始化过程中&#xff0c;需要对基础类&#xff0c;比如System/Thread等类进行初始化操作&#xff0c;所以…

DL/T645、IEC104转OPC UA网关BE112

随着电力系统信息化建设和数字化转型的进程不断加速&#xff0c;对电力能源的智能化需求也日趋增强。健全稳定的智慧电力系统能够为工业生产、基础设施建设以及国防建设提供稳定的能源支持。在此背景下&#xff0c;高性能的工业电力数据传输解决方案——协议转换网关应运而生&a…

贪吃蛇项目(基于C语言和数据结构中的链表)

建立文件 首先先建立3个文件。 Snake.h 函数的声明 Snake.c 函数的定义 Test.c 贪吃蛇的测试 分析项目 我们分析这整个项目 建立节点 首先在我们实现游戏开始的部分之前&#xff0c;我们要先创建贪吃蛇的节点&#xff0c;再由此创建整个贪吃蛇所包含的一些信息&#…