【Linux】可重入函数 volatile关键字 以及SIGCHLD信号

news2024/12/25 12:34:39

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

  • 一、可重入函数
    • 1、引入
    • 2、可重入函数的判断
  • 二、volatile关键字
    • 1、引入
    • 2、关于编译器的优化的简单讨论
  • 三、SIGCHLD信号

一、可重入函数

1、引入

我们来先看一个例子来帮助我们理解什么是可重入函数:

假设我们现在要对一个链表进行头插,在执行到第10行代码时,突然进程的时间片到了,进程被切换了,一会等进程再度切换回来时,当前进程要处理信号,而信号处理函数是sighandler,而sighandler里面也进行了头插,等进程从内核态返回到用户态时,继续执行第11行的代码,这时我们再观察链表的结构会发现链表中出现了节点丢失的问题,而造成这种问题的根源是我们的insert函数同时被两个执行流给进入了。

node_t node1, node2, *head;
int main()
{
	...
	insert(&node1);
	...
}

void insert(node_t*p)
{
	p->next = head;
	head = p;
}

void sighandler(int signo)
{
	insert(&node2);
}

在这里插入图片描述

由这个问题衍生出了一种函数分类的方式:

  • 如果一个函数同时被多个执行流进入所产生的结果没有问题,该函数被称为可重入函数
  • 如果一个函数同时被多个执行流进入所产生的结果有问题,该函数被称为不可重入函数
  • 可重入函数主要用于多任务环境中,一个可重入的函数通常来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;
  • 不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

2、可重入函数的判断

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

  1. 函数体内使用了静态(static)的数据结构或者变量;
  2. 调用了mallocfree,因为malloc也是用全局链表来管理堆的。
  3. 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

二、volatile关键字

1、引入

volatile是C语言的一个关键字,该关键字的作用是保证内存数据的可见性

我们来先来看一段代码,这里我们不加入volatile关键字并开启编译器优化选项,优化级别是-O2

这段代码的意思是:我们让进程一直运行,直到我们给进程发送2号信号以后,进程再退出。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int flag = 0;

void handler(int signo)
{
    printf("捕捉到了%d号信号\n", signo);
    // 将flag置为1
    flag = 1;
    printf("已经将flag置为%d\n", flag);
}

int main()
{
    signal(2, handler);
    printf("进程正在运行...\n");
    while (!flag);  // 当flag == 1时,进程退出。
    printf("运行结束!\n");
    return 0;
}

运行结果:

在这里插入图片描述
可以看到,我们明明都已经让flag = 1了但是进程中的循环依然没有结束,这时为什么呢?下面我们一起来分析这个过程:


代码中的main函数和handler函数在触发时是两个独立的执行流,而while循环是在main函数当中的,而且main执行流里面并没有使用过handler函数(signal函数只是对2号信号进行了捕捉,没有调用过handler函数),所以在编译器编译时检测到在main函数中对flag变量没有做过修改操作,而且由于while循环运行时需要频繁使用flag变量,所以编译器可以将flag变量的值用一个寄存器进行保存,以后每次使用flag变量直接去寄存器里面取数据,不必每次都要将内存中的flag搬运到寄存器里面然后让CPU去计算。

可是不巧的是我们给当前进程发送了2号信号,让另外一个执行流更改了内存中的flag变量,而由于编译器的优化,认为flag变量不会改变导致内存中的flag变量改变以后也没有将寄存器中的数据同步修改,而CPU运算使用的数据又是寄存器中的数据,这就导致了内存数据的不可见,于是while循环就会一直运行,导致了上面的问题。

在这里插入图片描述

为了让编译器每次都要去内存取数据来进行计算,我们可以在flag变量前面加上volatile关键字。

#include <stdio.h>
...
volatile int flag = 0;

void handler(int signo)
{
   ...
}
int main()
{
    ...
}

再次运行程序,发现运行结果符合预期!

在这里插入图片描述

2、关于编译器的优化的简单讨论

上面的代码如果我们不开启优化,就算不加上volatile关键字也是能正常运行的,可见编译器的优化不是越高越好。

如何理解编译器的优化?

编译器的本质是将代码翻译成01的二进制序列,所以编译器的优化是在你编写的代码上动手脚,也就是说编译器的优化其实改变了一些最终翻译成01二进制以后的执行逻辑。

三、SIGCHLD信号

在一前我们讲过用waitwaitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,也很麻烦。
《wait与waitpid的使用介绍》

上面使用waitwaitpid其实都是父进程主动检查子进程是否处于僵尸状态,那么有没有一种方法能够让子进程主动告诉父进程自己处于僵尸状态呢?

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

下面就是一个对SIGCHLD信号的一个使用:

在父进程中我们创建了10个子进程,这10个子进程退出时都会给父进程发送SIGCHLD信号,由于父进程回收其中一个子进程时,其他子进程也有可能同时给父进程发送SIGCHLD信号,而pending表又没有办法同时存储多个信号,所以我们就要进行循环回收子进程,而为了不影响父进程的执行流程我们可以选择非阻塞等待。

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

pid_t id = 0;

void WaitProcess(int signo)
{
    printf("捕捉到了%d号信号,正在处理...\n", signo);
    
    while (1)
    {
        pid_t ret = waitpid(-1, NULL, WNOHANG);
        if (ret > 0)
        {
            printf("等待子进程%d成功,父进程%d\n", ret, id);
        }
        else
        {
            break;
        }
    }
    printf("WaitProcess, done\n");
}

int main()
{
    signal(SIGCHLD, WaitProcess);
    int i = 0;
    // 创建10个子进程
    for (i = 0; i < 10; i++)
    {
        id = fork();
        // 子进程
        if (id == 0)
        {
            int cnt = 5;
            //睡眠cnt秒以后退出
            while (cnt--)
            {
                printf("我是子进程,我的pid是:%d,ppid是:%d\n", getpid(), getppid());
                sleep(1);
            }
            exit(0);
        }
    }

    // 父进程一直休眠
    while (1)
    {
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

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

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

pid_t id = 0;

int main()
{
	// 对SIGCHLD设置为忽略,这样产生的子进程退出时不会形成僵尸状态。
    signal(SIGCHLD, SIG_IGN);
    int i = 0;
    // 创建10个子进程
    for (i = 0; i < 10; i++)
    {
        id = fork();
        // 子进程
        if (id == 0)
        {
            int cnt = 5;
            //睡眠cnt秒以后退出
            while (cnt--)
            {
                printf("我是子进程,我的pid是:%d,ppid是:%d\n", getpid(), getppid());
                sleep(1);
            }
            exit(0);
        }
    }

    // 父进程一直休眠
    while (1)
    {
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

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

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

相关文章

基于Yolov5与LabelMe训练自己数据的图像分割完整流程

基于Yolov5与LabelMe训练自己数据的实例分割完整流程 1. Yolov5配置2. 创建labelme虚拟环境4. 接下来开始使用labelme绘制分割数据集4.1 json to txt4.2 划分数据集(可分可不分) 5. 训练 1. Yolov5配置 参照这边文章&#xff1a; https://blog.csdn.net/ruotianxia/article/de…

Python3,lmproof库,你不知道的小技能,这一篇给安排。

lmproof技能介绍 1、引言2、代码实战2.1 定义2.2 常用语法2.3 安装2.4 示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c;鱼哥&#xff0c;我要考考你 小鱼&#xff1a;你要考考我&#xff1f; 小屌丝&#xff1a;是的啊&#xff0c; 我要考你&#xff0c; 小鱼&#…

ZooKeeper的应用场景(数据发布订阅、负载均衡)

ZooKeeper是一个典型的发布/订阅模式的分布式数据管理与协调框架&#xff0c;开发人员可以使用它来进行分布式数据的发布与订阅。另一方面&#xff0c;通过对ZooKeeper中丰富的数据节点类型进行交叉使用&#xff0c;配合Watcher事件通知机制&#xff0c;可以非常方便地构建一系…

DaVinci Resolve Studio 18 for Mac 达芬奇调色

DaVinci Resolve Studio 18是一款专业的视频编辑和调色软件&#xff0c;适用于电影、电视节目、广告等各种视觉媒体的制作。它具有完整的后期制作功能&#xff0c;包括剪辑、调色、特效、音频处理等。 以下是DaVinci Resolve Studio 18的主要特点&#xff1a; - 提供了全面的视…

【AGC】发布后应用信息支持设备不能删除问题

【关键字】 AGC、应用发布、兼容设备类型 【问题描述】 有开发者反馈发布新版本应用&#xff0c;应用信息可支持设备不能删除原有在架应用已选择的设备类型。发布应用问题&#xff0c;目前应用是面向车机开发的&#xff0c;在上一个开放性测试版本中&#xff0c;支持设备除了…

【vue】简洁优雅的火花线、趋势线

来由 在github发现个好看易用的vue趋势线组件&#xff0c;特此记录。 效果 趋势图生成后效果如上&#xff0c;线条为渐变色&#xff0c;可设置是否平滑。具体线条走势&#xff0c;根据数据动态生成。 使用 安装 npm i vuetrend -S 引入 import Vue from "vue"…

[已解决]使用sqlplus连接oracle,提示ORA-01034和ORA-27101

具体内容如下 PL/SQL Developer 处 登录时 终端处 登录时 ERROR: ORA-01034: ORACLE not available ORA-27101: shared memory realm does not exist Process ID: 0 Session ID: 0 Serial number: 0 解决方法是执行以下命令 sqlplus /nolog conn / as sysdba startup …

linux目录文件系统:磁盘分区情况

查看系统中的磁盘分区情况 使用命令&#xff1a; fdisk -l由上图可知&#xff0c;一共有4个磁盘分区&#xff1a; 分区1 &#xff1a; /dev/sdb分区2 : /dev/sda分区3: /dev/mapper/centos-root分区4&#xff1a; /dev/mapper/centos-swap查看有哪些目录/文件挂载在上面…

使用 Ploomber、Arima、Python 和 Slurm 进行时间序列预测

推荐&#xff1a;使用 NSDT场景编辑器助你快速搭建可二次编辑的3D应用场景 简短的笔记本说明 笔记本由 8 个任务组成&#xff0c;如下图所示。它包括建模的大多数基本步骤 - 获取数据清理、拟合、超参数调优、验证和可视化。作为捷径&#xff0c;我拿起笔记本并使用Soorgeon工具…

ES 概念

es 概念 Elasticsearch是分布式实时搜索、实时分析、实时存储引擎&#xff0c;简称&#xff08;ES&#xff09;成立于2012年&#xff0c;是一家来自荷兰的、开源的大数据搜索、分析服务提供商&#xff0c;为企业提供实时搜索、数据分析服务&#xff0c;支持PB级的大数据。 -- …

ssm社区文化宣传网站源码和论文

ssm社区文化宣传网站源码和论文019 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 研究或设计的目的和意义&#xff1a; (一)研究目的&#xff1a; 通过本次课题能够将所学的Java编程知识以及Mysql数据库知…

msvcp140.dll如何重新安装?快速安装msvcp140.dll的方法分享

msvcp140.dll是Windows操作系统的一个动态链接库文件&#xff0c;它是Microsoft Visual C Redistributable的一部分。这个文件在运行某些应用程序时非常重要。然而&#xff0c;在某些情况下&#xff0c;msvcp140.dll文件可能会损坏或遗失&#xff0c;导致应用程序无法正常运行。…

甲基化系列 4. 基于芯片甲基化数据寻找简单的CpG甲基化标志物 (CimpleG)

甲基化系列分析教程 桓峰基因公众号推出甲基化系列分析教程&#xff0c;整理如下&#xff1a; 甲基化系列 1. 甲基化之前世今生&#xff08;Methylation&#xff09; 甲基化系列 2. 甲基化芯片数据介绍与下载&#xff08;GEO&#xff09; 甲基化系列 3. 甲基化芯片数据分析完整…

见证马斯克的钞能力,AI.com再次易主,OpenAI投掷1100万美金购买AI.com刚满五个月

我们又一次见证了马斯克的钞能力。上次是去年他用440亿美元买下推特。 高价值的AI.com域名在2021年易主后&#xff0c;闲置过一段时间&#xff0c;今年2月份突然重定向到ChatGPT。 对于ChatGPT用户来说&#xff0c;每次访问都要在浏览器里敲这些字符&#xff1a;https://chat.o…

Java继承详解

目录 继承 为什么需要继承 继承的概念 继承的语法 父类成员的访问 子类中访问父类的成员变量 1.子类和父类不存在同名的成员变量 2.子类和父类成员变量同名 子类中访问父类的成员方法 1.成员方法名字不同 2.成员方法名字相同 super关键字 子类构造方法 super和thi…

【Redis】Redis三种集群模式-主从、哨兵、集群各自架构的优点和缺点对比

文章目录 前言1. 单机模式2. 主从架构3. 哨兵4. 集群模式总结 前言 如果Redis的读写请求量很大&#xff0c;那么单个实例很有可能承担不了这么大的请求量&#xff0c;如何提高Redis的性能呢&#xff1f;你也许已经想到了&#xff0c;可以部署多个副本节点&#xff0c;业务采用…

FPGA应用学习笔记------系统复位一(同异复位)

要满足复位恢复时间才能正常复位&#xff0c;不然会产生输出准稳态&#xff0c;输出逻辑错误 复位恢复时间只会存在复位释放时刻&#xff0c;不会出现在确立时刻&#xff0c;则不推荐完全异步复位 完全同步复位&#xff0c;肯定是同步于时钟滴&#xff0c;并将总是满足时钟条件…

视觉SLAM十四讲---【第三讲-三维空间刚体运动】

坐标系和位姿变换 坐标系 在三维空间中&#xff0c;三根不共面的轴&#xff0c;坐标系能用他的基来表示。 机器人中各种坐标系&#xff1a; 世界系、惯性系机体系传感器参考系 点、向量、坐标系 坐标系分为左左手系和右手系 下面讨论有关向量的运算&#xff1a; 内积(对应坐…

6.物联网操作系统信号量,二值信号量,计数信号量

一。信号量的概念与应用 信号量定义 FreeRTOS信号量介绍 FreeRTOS信号量工作原理 1.信号量的定义 多任务环境下使用&#xff0c;用来协调多个任务正确合理使用临界资源。 2.FreeRTOS信号量介绍 Semaphore包括Binary&#xff0c;Count&#xff0c;Mutex&#xff1b; Mutex包…

jmeter返回值中的中文显示为????问号处理解决方案

jmeter返回值中的中文显示为????问号 查找解决方案时&#xff0c;发现了以下两种解决方案&#xff1a; 一、1.打开jmter配置文件bin/jmeter.properties 2.修改配置文件&#xff0c;查找“sampleresult.default.encoding”将其改为utf8&#xff0c;注意要去掉“#”号 sample…