可重入函数、volatile关键字、SIGCHLD信号

news2024/11/15 23:21:44

目录

一、可重入函数

二、volatile关键字

三、SIGCHLD信号


一、可重入函数

以一个链表头插为例子 

  • main函数调用insert函数像一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的时候因为硬件中断使进程切换到内核,再次回到用户态之前检测到有信号待处理,于是就切换到sighandle函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入的两步都完成之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续往下执行,先前做第一步之后被打断,现在继续做完第二步。结果就如同上面的图4,只有一个节点被插入进去了。对于不理解内核态和用户态的小伙伴可以看看这篇文章:深度理解信号处理阶段
  • 像上例子这样,insert函数被不同的控制流调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成混乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或者参数,则可称之为可重入函数。

以下条件符合其一就是不可重入函数:

1、调用了malloc或者free因为,malloc也是用全局链表来管理堆的。

2、调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

二、volatile关键字

volatile关键字的作用:保持内存的可见性,告知编译器,被该关键字修饰的变量不允许被优化,对该变量的任何操作都应该在真实的内存中进行操作。

上面提到的可见性、优化、和编译器的关系不太了解的就来通过一个例子来学习这个关键字:

我要写一个代码,一个条件为1的死循环代码,当我输入ctrl+c的指令,条件由1变为0,那么循环结束。代码如下:


int flag=0;

void myhandler(int sig)
{
    printf("turn flag to 1\n");
    flag=1;
}

int main()
{   
    signal(2,myhandler);
    while(!flag);
    printf("process nomaly qiut\n");
    return 0;
}

它的执行结果就会如同我所期望的:一输入ctrl+c程序循环结束,程序退出

 但是如果我改变一下编译条件在后面添加一个-O2,这样编译器就会优化代码,让编译器替我优化一下代码:

gcc -o mysigchld mysigchld.cc -O2

那么结果就会变成下面这样,我连续按下了几个ctrl+c,它对我的回应也是将flag改变为1了。但是循环就是不结束。

这是为什么呢?

我们来了解一下优化原理:

 我们知道,数据不先加载到内存里cpu根本看不到,os系统将这个程序的代码和数据都加载进来了,上面的是数据区,下面是存放代码的代码区

首先cpu先从数据区读入flag数据

 然后执行while条件的真假逻辑计算,对比!flag是否大于0。

此外还有一个pc指针指向代码区,来获取下一步指令如果结束了死循环,就由pc来获取下一个代码:

 已上就是底层理解这个代码的运行过程,那么这个过程有什么值得优化的呢,或者说编译器它优化了什么呢?

有小伙伴会说是这个死循环,答案比较接近了,死循环是biu要执行的,这个优化不了,但是我们每次死循环都要进行一次条件判断,这个条件判断正常来说不是直接和cpu里的flag进行运算,而是需要先从内存里的那个flag获取,然后再进行运算。

编译器优化的点其实就在数据拷贝的这个点,毕竟是在两个硬件上拷贝,效率不是很高,编译器就将这个过程优化成了只需要拷贝第一次,后面的每次比较就直接在cpu里获取了。这样自然而然的在每次我们改变flag的值时它还是无法结束死循环。

以下就是汇编语言的区别:因为编译器优化工作的本质就是对代码动手脚,而cpu只是一个硬件没有自己的想法的,别人给什么任务就执行就可以了。

三、SIGCHLD信号

在学习进程的时候我们知道,我们可以用wait和waitpid来清理僵尸进程。

使用第一个方法,父进程可以阻塞的等待子进程的结束,第二种方式就可以给通过轮询的方式查询子进程是否已经结束等待清理。第一种方式,父进程阻塞了就不能继续做自己的任务了,第二种方式在处理自己的工作时还要是不是询问一下,这样不仅代码复杂还效率低下。

不论怎么说,让父进程阻塞等待子进程结束是不是有点太浪费资源了,那为什么我们不用其他的方法来结束子进程呢——因为我们之前不知道,子进程结束后其实是会向父进程发送一个信号的,也就是SIGCHLD信号。这个信号的默认处理动作是忽略,既然这样我们可以利用这个信号,自定义一个子进程已结束才提醒父进程去清理子进程。如果不是很了解如何写自定义函数处理信号的同学可以看看以下这篇文章:信号原理解析   中的第二小节

一个任务:写出一段代码,生成一个子进程并在五秒后退出,然后利用子进程退出时发送的SIGCHLD信号结束子进程。

#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

void myhandler(int sig)
{
    waitpid(-1,NULL,WNOHANG);
    printf("成功回收子进程\n");
}

int main()
{
    signal(SIGCHLD,myhandler);
    pid_t cid;
    cid=fork();
    if(cid==0)
    {
        //child
        int cnt=5;
        while(cnt>0)
        {
            printf("pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(0);
    }
    //father
    while(1)
    {
        printf("father is doing something\n");
        sleep(1);
    }

    return 0;
}

我来解析一下上面这个代码:一开始父进程生成了一个子进程,然后子进程开始输出自己以及父进程的pid持续五秒,在五秒后子进程结束,然后向父进程发送一个信号,父进程接受到信号后就跳转到myhandler函数清理子进程。

看看结果是不是入我们所想:

 确实如此。

到这里我们就学会了一个非常管用的方法去处理子进程,这样也不会占用太多的资源。但是上面这段代码真的正确吗,或者说这个思路没问题但是代码实现细节真的普适吗?

如果一个父进程同时生成多个子进程

以下面这个代码为例,父进程申请了十个子进程:

#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

void myhandler(int sig)
{
    waitpid(-1,NULL,WNOHANG);
    printf("成功回收子进程\n");
}

int main()
{
    signal(SIGCHLD,myhandler);
    pid_t cid;
    // cid=fork();
    for(int i=1;i<=10;i++)
    {
        int cid=fork();
        if(cid==0)
        {
            //child
            int cnt=5;
            while(cnt>0)
            {
                //printf("pid:%d,ppid:%d\n",getpid(),getppid());
                sleep(1);
                cnt--;
            }
            exit(0);
        }
    }
    

    while(1)
    {
        printf("father is doing something\n");
        sleep(1);
    }

    return 0;
}

按照我们之前的思路,十个子进程退出就应该会向父进程发送十个信号,并且会出现十个:“成功回收子进程”这一字符串。

但是结果是什么呢?

 父进程只回收了三个子进程,为什么会出现这样的情况呢?就像我们刚刚说的一样,十个进程同时退出,同时向进程发送同一个信号。信号储存是通过位图的方式储存的,所以这十个进程需要占用同一个bit位,而父进程一次却只能处理一个信号,对于信号储存原理不是很了解的小伙伴可以先看看这个文章:信号储存解析,因此

    waitpid(-1,NULL,WNOHANG);
    printf("成功回收子进程\n");

这段代码很有可能会遗漏掉一些信号,所以我们可以将这段代码改成:

 while (1)
     {
         pid_t res = waitpid(-1, NULL, WNOHANG);
         if (res > 0)
         {
             printf("wait success, res: %d, id: %d\n", res, id);
         }
         else break; // 如果没有子进程了?
     }

这样handler函数不处理完SIGCHLD信号就不会退出循环。

执行结果:

到这里为止,我们才算学会了回收子进程的第二种方式。

那么我们要如何使用这两种方法呢?

  • 如果父进程没有什么任务要执行就可使用wait这种阻塞的回收方式。
  • 如果父进程自己也有任务需要执行,就使用利用信号这一方式来回收子进程。 

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

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

相关文章

Visual studio 2015下载安装以及缺包提示的处理方法

最近要加入的比赛团队需要用到Visual studio 2015&#xff0c;百度后找到很多资源&#xff0c;自己也转到了百度网盘。中英文都有&#xff0c;需要的可以下载。 链接&#xff1a;https://pan.baidu.com/s/12gpVwXfQxfdkXub-IwhWFw?pwds325 提取码&#xff1a;s325 --来自百…

linux安装交叉编译环境

1、安装系统自带的交叉编译环境 sudo apt install gcc-arm-linux-gnueabihf sudo apt install g-arm-linux-gnueabihf 2、选择特定交叉编译器版本 版本路径&#xff1a;Linaro Releases 当前选择版本&#xff1a;4.9 、64位的版本

ArcGIS栅格影像数据处理

ArcGIS栅格影像数据处理 文章目录 ArcGIS栅格影像数据处理1. 栅格影像数据坐标系转换2. 栅格数据16bit转8bit3. 栅格数据波段变换参考链接 1. 栅格影像数据坐标系转换 点击【ArcToolbox】>【数据管理工具】>【投影和变换】>【栅格】>【投影栅格】。 2. 栅格数据16…

Jetpack compose——深入了解recomposition的工作原理

一、compose怎么 实现响应式编程的 Jetpack Compose 是 Android 的现代 UI 工具包&#xff0c;它使用 Kotlin 语言的声明式 UI 模式来简化 UI 开发。在这种模式中&#xff0c;你只需描述 UI 应该如何根据应用的状态进行显示&#xff0c;而 Compose 会在状态发生变化时自动更新…

【Linux】oh-my-zsh终端配置

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍oh-my-zsh终端配置。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷路&am…

Spring Boot发送QQ邮件

Spring Boot发送QQ邮件 1. 创建Spring Boot项目2. 引入发邮件的starter3. 必要配置4. 编写邮件内容5. 测试其他&#xff1a; Spring Boot简单引入一个包就能轻松发邮件&#xff0c;仅需5分钟就能实现 1. 创建Spring Boot项目 略 2. 引入发邮件的starter <dependency>&l…

学习在外部Python脚本中运行Houdini的Python接口(hou模块)

0. 目标 学习在外部Python脚本&#xff08;而非Houdini编辑器内部&#xff09;使用 hou 。 主要参考Houdini官方文档 Command-Line Scripting 中的【Accessing hou from a Regular Python Shell】部分。我将要点记录在下&#xff1a; 1. 将Houdini的dll加入搜索路径 为了能…

FPGA实验五:信号发生器设计

目录 一、实验目的 二、设计要求 三、实验代码 1.代码原理分析 2.代码设计思路 3.IP核的设计与配置 四、实验结果及分析 1、引脚锁定 2、仿真波形及分析 &#xff08;1&#xff09;关于波形一些指标的介绍 &#xff08;2&#xff09;对波形转换功能的验证 &#xf…

【LeetCode】225. 用队列实现栈

225. 用队列实现栈&#xff08;简单&#xff09; 思路 要使用一个队列来实现栈的功能。 具体来说&#xff0c;实现了以下几个方法&#xff1a; push(int x)&#xff1a;将元素 x 入栈&#xff0c;即将元素 x 插入到队列的末尾。pop()&#xff1a;弹出栈顶元素&#xff0c;即将队…

空天遥感守护自然资源底线,擦亮生态底色

上期介绍了航天宏图采用“空天地”一体化监测监管手段&#xff0c;打造基于“SAR卫星耕地变化遥感智能解译”、 基于“遥感大模型的耕地分类技术”等智慧耕地保护解决方案&#xff0c;本篇文章主要讲述“遥感如何助力自然保护地监测与保护”和“如何助力山水林田湖草沙生态修复…

Spanner: Google的全球分布级数据库----论文摘要

Spanner中一个新奇的time api揭示了时钟的不确定性。该api及其实现对于支持外部一致性&#xff08;外部观察一致性&#xff09;以及一系列强力的特性至关重要&#xff0c;这些特性包括&#xff1a;对过去版本数据的无阻塞读&#xff08;对于历史数据的读不加锁&#xff0c;且不…

密码学入门——DES与AES

文章目录 参考书目一、编码与异或1.1 编码1.2 异或 二、DES与三重DES三、AES 参考书目 图解密码技术&#xff0c;第三版 一、编码与异或 1.1 编码 计算机的操作对象并不是文字&#xff0c;而是由0和1排列而成的比特序列。无论是文字、图像、声音、视频还是程序&#xff0c;…

短视频矩阵管理系统源码开发:视频批量剪辑,分发功能开发示例

短视频矩阵管理系统开发&#xff0c;首先对服务器要求&#xff1a; 源码所需服务器配置 1、规格&#xff1a;最低8核16G 2、硬盘&#xff1a;系统盘40-100G&#xff0c;数据盘不低于100G 3、带宽&#xff1a;10M 4、系统&#xff1a;CentOS7(务必选择7.*) 部署过程中&…

设计模式之结构型模式---代理模式

目录 1.概述2.代理模式类图3.应用场景3.1 功能增强3.2 控制访问 4.实现4.1 静态代理的实现4.1.1 实现静态代理模式的步骤4.1.2 静态代理的缺点 4.2 动态代理的实现4.2.1 Java JDK 动态代理的实现MethodInvocationHandlerProxy 4.2.2 JDK动态代理的使用方法4.2.3 JDK动态代理的实…

本内容由【想发APP】创始人编写:uniAPP申请百度广告后,开发者必须要空包签名验证应用的所有权,实现的详细内容如下

Android获取签名 注意先安装好jdk和jre&#xff0c;然后配置好环境变量 说明 空包签名需要本地配置jdk环境 Windows 安装JDK及环境变量的配置 命令行方式 命令格式&#xff1a; jarsigner -verbose -keystore [签名文件路径] -signedjar [签名后apk的文件路径] [未签名apk的文…

对顶堆学习笔记

对顶堆学习笔记 文章目录 **对顶堆学习笔记**介绍例题洛谷 P1168 中位数code 介绍 对顶堆是由一个大顶堆和一个小顶堆组合而成的数据结构&#xff0c;与传统堆维护最大数不同&#xff0c;对顶堆用于动态维护第k大的数。 对于对顶堆&#xff0c;我们可以用两个优先队列来表示两…

IllegalArgumentException: OnNoRibbonDefaultCondition异常与Maven Helper插件解决jar包冲突

在搭建Spring Cloud项目时&#xff0c;引入了不同版本的jar&#xff0c;导致项目启动时报错: main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.Bea…

Python 制作英文学习词典(简易版)

前言 今日分享 Python 制作英文学习词典&#xff0c;大家都可以尝试。 题目 制作英文学习词典。编写程序制作英文学习词典&#xff0c;词典有3个基本功能&#xff1a;添加、查询和退出。程序读取源文件路径下的txt格式词典文件&#xff0c;若没有就创建一个。词典文件存储方…

arcgis js 4.x加载自定义坐标系的arcgis server发布的wms服务

一、问题描述 一般城市本身用的都是自己定义的城市&#xff08;本地&#xff09;坐标系&#xff0c;没有对应公开的EPSG代码&#xff0c;如下图如果直接加载自定义坐标系的wms服务&#xff0c;直接会报错。 var customProjLayer new WMSLayer({url: "http://10.1.8.37:6…

封装一个带el-form的,带el-table的,带分页的,带搜索查询的dialog组件,很使用的二次封装组件。

#封装dialog小案例 提示&#xff1a;这是我工作中封装的代码&#xff0c;很使用&#xff0c;需要的可以拿去&#xff0c; 在我们的代码中往往会出现点击按钮出现弹窗进行操作&#xff0c;那么我们就需要对dialog进行一个二次封装。 下边是大概的一个样式。 ##对组件进行二次…