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

可重入函数 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.mfbz.cn/a/74723.html

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

相关文章

微软电脑surface键盘无法使用问题解决

昨天下班后&#xff0c;正常关掉电脑&#xff0c;今天来上班发现键盘无法使用了 打人工找到了解决方法 开机->到锁屏页面->使用屏幕键盘输入密码进入电脑 然后右键左下角的win图标 找到设备管理器->键盘 全部右键卸载 再找到设备管理->系统设备 把这个DTX也卸…

母婴即时零售行业数据可视化分析

对新晋父母来说&#xff0c;很多母婴用品如同一位贴心的助手&#xff0c;为他们的宝宝提供温暖和呵护。从婴儿床垫到可爱的拼图玩具&#xff0c;每一件用品都是为宝宝的成长和发展量身定制。对于繁忙的父母们而言&#xff0c;这些用品不仅帮助照顾孩子&#xff0c;更是为他们减…

ISIS技术(第三十七课)

1 分享一下华为官网上的一张地图 官网地址:https://support.huawei.com/hedex/hdx.do?docid=EDOC1000105967&id=ZH-CN_CONCEPT_0000001501534705 2 路由的分类 -直连路由 直接连接的路由,且配置了IP地址之后(在同一网段内),就是直连路由。 -非直连路由 -静态路由…

奥威BI数据可视化工具:报表就是平台,随时自助分析

别的数据可视化工具&#xff0c;报表就只是报表&#xff0c;而奥威BI数据可视化工具&#xff0c;一张报表就约等于一个平台&#xff0c;可随时展开多维动态自助分析&#xff0c;按需分析&#xff0c;立得数据信息。 奥威BI是一款多维立体分析数据的数据可视化工具。它可以帮助…

Hadoop Hbase Hive 版本对照一览

这里写目录标题 一、Hadoop 与 Hbase 版本对照二、Hadoop 与 Hive 版本对照 官网内容记录&#xff0c;仅供参考 一、Hadoop 与 Hbase 版本对照 二、Hadoop 与 Hive 版本对照

10-1_Qt 5.9 C++开发指南_Data Visualization实现数据三维显示

Data Visualization 是 Qt 提供的用于数据三维显示的模块。在 Qt 5.7 以前只有商业版才有此模块&#xff0c;而从Qt5.7 开始此模块在社区版本里也可以免费使用了。Data Visualization 用于数据的三维显示&#xff0c;包括三维柱状图、三维空间散点、三维曲面等。Data Visualiza…

从零开始学极狐GitLab|03 Runner 裸机部署

目录 极狐GitLab SaaS 版&#xff08;无需部署&#xff09; 安装自己的极狐GitLab- Runner 1. macOS ➤ 安装 ➤ 注册 2. Linux ➤ 安装 ➤ 注册 3. Windows ➤ 安装 ➤ 注册 【从零开始学极狐GitLab】专栏由极狐GitLab 社区开发者“雪碧能喝多”投稿&#xff0c;面…

【excel技巧】Excel表格如何取消隐藏行?

Excel工作表中的行列隐藏了数据&#xff0c;如何取消隐藏行列呢&#xff1f;今天分享几个方法给大家 方法一&#xff1a; 选中隐藏的区域&#xff0c;点击右键&#xff0c;选择【取消隐藏】就可以了 方法二&#xff1a; 如果工作表中有多个地方有隐藏的话&#xff0c;还是建…

MongoDB常用命令

什么是MongoDB ? MongoDB 是由C语言编写的&#xff0c;是一个基于分布式文件存储的开源数据库系统。 在高负载的情况下&#xff0c;添加更多的节点&#xff0c;可以保证服务器性能。 MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。 MongoDB 将数据存储为一个…

457. 环形数组是否存在循环

457. 环形数组是否存在循环 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a;经验吸取 原题链接&#xff1a; 457. 环形数组是否存在循环 https://leetcode.cn/problems/circular-array-loop/description/ 完成情况&#xff1a; 解题思路…

HTML详解连载(7)

HTML详解连载&#xff08;7&#xff09; 专栏链接 [link](http://t.csdn.cn/xF0H3)下面进行专栏介绍 开始喽结构伪类选择器作用 :nth-child&#xff08;公式&#xff09;作用举例 伪元素选择器作用注意&#xff1a; PxCoook作用盒子模型-重要组成部分 盒子模型-边框线属性名属性…

win10中Docker安装、构建镜像、创建容器、Vscode连接实例

Docker方便一键构建项目所需的运行环境&#xff1a;首先构建镜像(Image)。然后镜像实例化成为容器(Container)&#xff0c;构成项目的运行环境。最后Vscode连接容器&#xff0c;方便我们在本地进行开发。下面以一个简单的例子介绍在win10中实现&#xff1a;Docker安装、构建镜像…

在 Linux 虚拟机上使用 Azure 自定义脚本扩展版本

参考 azure创建虚拟机,创建虚拟机注意入站端口规则开放80端口、 2.转到资源&#xff0c;点击扩展应用程序&#xff0c;创建存储账户&#xff0c;创建容器&#xff0c;上传文件&#xff0c;选择文件&#xff0c;会自动执行部署。 apt-get update -y && apt-get insta…

【Rust】Rust学习 第十二章一个 I/O 项目:构建一个命令行程序

本章既是一个目前所学的很多技能的概括&#xff0c;也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。 Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择…

Failed to execute goal org.apache.maven.plugins

原因&#xff1a; 这个文件D:\java\maven\com\ruoyi\pg-student\maven-metadata-local.xml出了问题 解决&#xff1a; 最简单的直接删除D:\java\maven\com\ruoyi\pg-student\maven-metadata-local.xml重新打包 或者把D:\java\maven\com\ruoyi\pg-student这个目录下所有文件…

面试热题(每日温度)

请根据每日 气温 列表 temperatures &#xff0c;重新生成一个列表&#xff0c;要求其对应位置的输出为&#xff1a;要想观测到更高的气温&#xff0c;至少需要等待的天数。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 输入: temperatures [73,74,75,71,69…

面试题-React(一):React是什么?它的主要特点是什么?

探索React&#xff1a;前端开发中的重要角色与主要特点 引言&#xff1a; 在现代前端开发领域&#xff0c;React已经成为最受欢迎和广泛使用的JavaScript库之一。它由Facebook开发并于2013年首次发布。随着时间的推移&#xff0c;React在开发社区中获得了强大的支持和认可。本…

Linux NTP原理及配置使用

一、NTP简介 1.NTP简介 NTP&#xff08;Network Time Protocol&#xff0c;网络时间协议&#xff09;是用来使网络中的各个计算机时间同步的一种协议。它的用途是把计算机的时钟同步到世界协调时UTC&#xff0c;其精度在局域网内可达0.1ms&#xff0c;在互联网上绝大多数的…

iptables之iptables表、链、规则 、匹配模式、扩展模块、连接追踪模块(一)

一、iptables的链 1.请求到达本机&#xff1a; PREROUTING --> INPUT --> Local Process &#xff08;本机&#xff09; 2.请求经过本机&#xff1a; PREROUTING --> FORWARD --> POSTROUTING 3.请求从本机发出&#xff1a;local Process&#xff08;本机&#xf…

【PostgreSQL的CLOG解析】

同样还是这张图&#xff0c;之前发过shared_buffer和os cache、wal buffer和work mem的文章&#xff0c;今天的主题是图中的clog&#xff0c;即 commit log&#xff0c;PostgreSQL10之前放在数据库目录的pg_clog下面。PostgreSQL10之后修更名为xact,数据目录变更为pg_xact下面&…
最新文章