【linux学习指南】可重入函数与volatile

news2025/1/5 15:56:38

请添加图片描述

文章目录

  • 📝可重⼊函数
  • 🌠 volatile
  • 🚩总结


📝可重⼊函数

在这里插入图片描述

  • main函数调⽤insert函数向⼀个链表head中插⼊节点node1,插⼊操作分为两步,刚做完第⼀步的时候,因为硬件中断使进程切换到内核,再次回⽤⼾态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调⽤insert函数向同⼀个链表head中插⼊节点node2,插⼊操作的两步都做完之后从sighandler返回内核态,再次回到⽤⼾态就从main函数调⽤的insert函数中继续往下执⾏,先前做第⼀步之后被打断,现在继续做完第⼆步。结果是,main函数和sighandler先后向链表中插⼊两个节点,⽽最后只有⼀个节点真正插⼊链表中了。
  • 像上例这样,insert函数被不同的控制流程调⽤,有可能在第⼀次调⽤还没返回时就再次进⼊该函数,这称为重⼊,insert函数访问⼀个全局链表,有可能因为重⼊⽽造成错乱,像这样的函数称为不可重⼊函数,反之,如果⼀个函数只访问⾃⼰的局部变量或参数,则称为可重⼊(Reentrant)函数。

想⼀下,为什么两个不同的控制流程调⽤同⼀个函数,访问它的同⼀个局部变量或参数就不会造成错乱?

  1. 假设以下是一个简单的func函数示例(C语言):
    int func(int a) {
        int b = a * 2;
        return b;
    }
    
    • 当这个函数被调用时,会发生以下情况:
      • 栈帧创建
        • 首先,调用func的函数(假设是main函数或者其他函数)会将参数a的值通过某种方式(例如将a的值压栈或者通过寄存器传递等,这里假设是压栈)传递给func
        • 然后,在栈上为func开辟一个栈帧。这个栈帧包含了func的局部变量b的存储空间。
      • 函数执行
        • func函数内部,b = a * 2这一操作是在当前栈帧的范围内进行的。它从栈帧中获取参数a的值,计算a * 2后将结果存储到栈帧中局部变量b的存储空间。
        • 当函数返回时,会从栈帧中取出b的值(通过某种返回机制,如将b的值放入寄存器等)返回给调用者。
  2. 当有两个不同的控制流程调用func时:
    • 假设第一个控制流程是在main函数中调用func,传入参数a = 3
      • 会创建一个栈帧,在这个栈帧中,参数a的值为3,计算得到b = 6,这个过程都在这个栈帧内完成。
    • 假设第二个控制流程是在一个信号处理函数(类似于之前提到的sighandler)中调用func,传入参数a = 5
      • 会为这个调用重新创建一个栈帧。在这个新的栈帧中,参数a的值为5,计算得到b = 10。这个过程和第一个控制流程调用func时是完全独立的,因为它们有各自独立的栈帧。
    • 这两个控制流程对func的调用不会相互干扰,因为它们操作的是各自栈帧中的参数和局部变量,从而体现了可重入函数访问自己的局部变量或参数不会造成错乱的特性。

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

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

🌠 volatile

  1. volatile关键字的基本概念
    • 在编程语言(如C和C++)中,volatile是一个类型修饰符。它用于告诉编译器,被修饰的变量是易变的,编译器不应对该变量进行优化。
    • 例如,对于一个普通的变量int a;,编译器可能会根据代码的上下文对变量a的访问进行优化。假设代码中有a = 1;a = 2;两条语句,编译器可能会认为这两条语句是连续的赋值操作,中间没有其他代码改变a的值,于是可能会将这两条语句合并或者优化访问路径。
    • 但是,当a被声明为volatile int a;时,编译器就不能进行这样的优化。因为volatile表示变量a的值可能会在编译器无法预知的情况下发生变化,比如被硬件(如外部设备通过内存映射I/O)或者其他异步执行的代码(如中断服务程序)改变。
  2. volatile在并发或异步环境中的作用
    • 考虑一个简单的嵌入式系统场景,有一个全局变量用于和外部设备通信。
    volatile unsigned char *device_register = (volatile unsigned char *)0x1000;
    
    • 这里将一个指针device_register声明为volatile,它指向一个内存地址0x1000,这个地址可能是外部设备的寄存器地址。
    • 当读取*device_register的值时,由于它是volatile的,每次读取编译器都会真正地从内存地址0x1000获取数据,而不会使用之前缓存的值。同样,当向*device_register写入数据时,也会真正地将数据写入到内存地址0x1000,而不会因为优化而忽略这个写入操作。
    • 在多线程或中断环境中,volatile也非常有用。假设一个全局变量volatile int flag;用于在主线程和中断服务程序之间通信。主线程可能会检查flag的值来判断是否有中断发生相关的事件。如果flag不是volatile的,编译器可能会优化掉对flag的检查,导致主线程无法正确地检测到flag的变化,因为编译器可能认为flag的值在没有显式赋值的情况下是不变的。而volatile关键字确保了主线程每次检查flag的值时,都是从内存中获取最新的值。

该关键字在C当中我们已经有所涉猎,今天我们站在信号的⻆度重新理解⼀下
Makefile文件

sig:sig.c 
	gcc -o sig sig.c #-O2 
#在Makefile中,#后面的内容是注释。在gcc -o sig sig.c #-O2这一行中,-O2是被注释掉的内容。
#正常情况下,如果没有被注释,-O2是gcc编译器的一个优化选项。它用于指定编译器进行一定级别的优化,-O2通常会执行较多的#优化,比如指令重排、函数内联等操作,以提高生成的可执行程序的性能。但是在这个Makefile规则里,因为被注释了,所以gcc#编译sig.c生成sig可执行文件时不会使用-O2这个优化选项。

.PHONY:clean 
clean: 
	rm -f sig

sig.c文件

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

int flag = 0;
void handler(int sig)
{
    printf("change flag 0 to 1\n");
    flag = 1;
}

int main()
{
    signal(2, handler);
    while(!flag);
    printf("processs quit normal\n");
    return 0;
}

在这里插入图片描述
标准情况下,键入(CTRL-C ,2号信号被捕捉,执行自定义动作,修改flag=1 , while条件不满足,退出循环,进程退出

第二种:

优化情况下,键入CTRL-C2号信号被捕捉,执行自定义动作,修改flag=1,但是 while条件依旧满足,进程继续运行!但是很明显flag肯定已经被修改了,但是为何循环依旧执行?很明显while循环检查的flag,并不是内存中最新的flag,这就存在了数据二异性的问题。while检测的flag 其实已经因为优化,被放在了CPU寄存器当中。如何解决呢?很明显需要volatile

sig:sig.c 
	gcc -o sig sig.c -O2 
.PHONY:clean 
clean: 
	rm -f sig
#include <stdio.h>
#include <signal.h>

int flag = 0;
void handler(int sig)
{
    printf("change flag 0 to 1\n");
    flag = 1;
}

int main()
{
    signal(2, handler);
    while(!flag);
    printf("processs quit normal\n");
    return 0;
}

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

在这里插入图片描述

2第三种:

sig:sig.c 
	gcc -o sig sig.c -O2 
.PHONY:clean 
clean: 
	rm -f sig
#include <stdio.h>
#include <signal.h>

volatile int flag = 0;
void handler(int sig)
{
    printf("change flag 0 to 1\n");
    flag = 1;
}

int main()
{
    signal(2, handler);
    while(!flag);
    printf("processs quit normal\n");
    return 0;
}

在这里插入图片描述

常用的gcc优化选项:

  1. -O(等价于 -O1)
    • 优化内容
      • 进行简单的优化,如常量折叠,即将编译期可计算的常量表达式直接计算出结果,例如int a = 2 + 3;会直接计算为int a = 5;
      • 简单的公共子表达式消除,当程序中多次出现相同的子表达式(其运算对象在每次出现时都没有变化),编译器会只计算一次,用计算结果代替后续相同子表达式的计算。
      • 优化循环结构,例如将一些可以在循环外计算的表达式移到循环外,减少不必要的计算。
      • 减少函数调用开销,例如对一些简单的函数(如内联函数)进行适当优化,提高执行效率。
    • 适用场景
      • 当对编译速度要求较高,且对程序性能提升有一定需求,但不希望过度优化时可以使用。适用于一些简单的程序或者在开发阶段初步优化的情况。
    • 示例gcc -o output_file input_file.c -O
  2. -O2
    • 优化内容
      • 包含了-O(或-O1)的所有优化。
      • 进行更复杂的指令重排,使程序执行流程更符合CPU的流水线特性,提高CPU的执行效率。
      • 更多的函数内联,将一些短小的函数体直接嵌入到调用它的地方,减少函数调用的开销。不过,过度的函数内联可能会导致代码膨胀。
      • 进一步优化循环,如循环展开,在适当的情况下将循环体展开,减少循环控制的开销,但这可能会增加代码大小。
    • 适用场景
      • 是比较常用的优化级别,适用于大多数需要较好性能的应用程序。在性能和编译时间、代码大小之间取得了较好的平衡。
    • 示例gcc -o output_file input_file.c -O2
  3. -O3
    • 优化内容
      • 包含了-O2的所有优化。
      • 更激进的函数内联,几乎会尝试内联所有可以内联的函数,可能会导致代码大小显著增加。
      • 更多的循环展开,甚至会对一些复杂的循环进行深度展开,进一步减少循环控制开销,但也更容易导致代码膨胀和缓存性能下降。
      • 还会进行一些复杂的优化,如对全局变量和指针的优化,以提高程序的整体性能。
    • 适用场景
      • 适用于对性能要求极高的场景,如一些性能敏感的算法实现、高性能计算等。但需要注意代码大小和可能出现的性能下降(如过度优化导致缓存不命中等情况)。
    • 示例gcc -o output_file input_file.c -O3
  4. -Os
    • 优化内容
      • 主要侧重于优化代码大小。在不显著降低程序性能的前提下,通过多种方式减小生成的可执行文件的大小。
      • 例如,选择更紧凑的指令集,避免一些会导致代码膨胀的优化(如过度的函数内联和循环展开),同时也会进行一些基本的性能优化,如常量折叠等。
    • 适用场景
      • 非常适合在存储空间有限的环境中使用,如嵌入式系统,或者对可执行文件大小有严格限制的应用程序。
    • 示例gcc -o output_file input_file.c -Os

🚩总结

请添加图片描述

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

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

相关文章

Web安全 - “Referrer Policy“ Security 头值不安全

文章目录 概述原因分析风险说明Referrer-Policy 头配置选项1. 不安全的策略no-referrer-when-downgradeunsafe-url 2. 安全的策略no-referreroriginorigin-when-cross-originsame-originstrict-originstrict-origin-when-cross-origin 推荐配置Nginx 配置示例 在 Nginx 中配置 …

FFmpeg:详细安装教程与环境配置指南

FFmpeg 部署完整教程 在本篇博客中&#xff0c;我们将详细介绍如何下载并安装 FFmpeg&#xff0c;并将其添加到系统的环境变量中&#xff0c;以便在终端或命令行工具中直接调用。无论你是新手还是有一定基础的用户&#xff0c;这篇教程都能帮助你轻松完成 FFmpeg 的部署。 一、…

AcWing练习题:平均数1

读取两个浮点数 AA 和 BB 的值&#xff0c;对应于两个学生的成绩。 请你计算学生的平均分&#xff0c;其中 AA 的成绩的权重为 3.53.5&#xff0c;BB 的成绩的权重为 7.57.5。 成绩的取值范围在 00 到 1010 之间&#xff0c;且均保留一位小数。 输入格式 输入占两行&#x…

Unity学习笔记(四)如何实现角色攻击、组合攻击

前言 本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记 实现动画 首先实现角色移动的动画&#xff0c;动画的实现过程在第二篇&#xff0c;这里仅展示效果 attack1 触发攻击动画 实现脚本 接下来我们通过 Animator 编辑动画之间的过渡&#…

如何使用MySQL的group_concat函数快速做关联查询?

当我们需要做一对多的关联查询时&#xff0c;会很容易想到用left join来实现。例如&#xff0c;现有country表和city表之间建立了一对多的关联关系。如果要展示各国家以及城市列表&#xff0c;会很容易想到以下SQL&#xff1a; SELECT country, city FROM country LEFT JOI…

Go快速开发框架2.6.0版本更新内容快速了解

GoFly企业版框架2.6.0版本更新内容较多&#xff0c;为了大家能够快速了解&#xff0c;本文将把更新内容列出详细讲解。本次更新一段时间以来大伙反馈的问题&#xff0c;并且升级后台安全认证机制&#xff0c;增加了RBAC权限管理及系统操作日志等提升后台数据安全性。 更新明细…

行业分析---造车新势力之零跑汽车

1 背景 在之前的博客中&#xff0c;笔者撰写了多篇行业分析的文章&#xff08;科技新能源&#xff09;&#xff1a; 《行业分析---我眼中的Apple Inc.》 《行业分析---马斯克的Tesla》 《行业分析---造车新势力之蔚来汽车》 《行业分析---造车新势力之小鹏汽车》 《行业分析---…

大数据与机器学习(它们有何关系?)

想了解大数据和机器学习吗&#xff1f;我们将为你解释它们是什么、彼此之间有何关联&#xff0c;以及它们为何在数据密集型应用中如此重要。 大数据和机器学习是如何相互关联的&#xff1f; 大数据指的是传统存储方法无法处理的海量数据。机器学习则是计算机系统从观察结果和…

有趣的python绘图介绍

以下介绍了几个有趣的Python绘图&#xff0c;感兴趣可以动手实践下&#xff01; 一、正方形螺旋线 from turtle import *speed("fastest") pensize(2) color["purple","blue","red","black"] for i in range(200):pencolo…

【Mysql】Mysql/Mariadb开启binlog日志

前言 MySQL 的二进制日志&#xff08;Binary Log&#xff0c;简称 binlog&#xff09;用于记录数据库的所有更改操作&#xff0c;包括数据更改&#xff08;如 INSERT, UPDATE, DELETE&#xff09;和结构更改&#xff08;如 CREATE TABLE, ALTER TABLE&#xff09;。二进制日志的…

python检测同心圆

python检测同心圆 原图如下&#xff1a; import cv2 import numpy as np# 读取图像 image_path r"E:\pycharm3\pythonProject\image\image.png" img cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # 灰度图像 img_color cv2.imread(image_path, cv2.IMREAD_C…

萌萌哒的八戒

萌萌哒的八戒 下载压缩包后&#xff0c;打开发现有一张照片 既然是关于猪的&#xff0c;那就用猪圈密码解码 flag{whenthepigwanttoeat}

【人工智能机器学习基础篇】——深入详解监督学习之模型评估:掌握评估指标(准确率、精确率、召回率、F1分数等)和交叉验证技术

深入详解监督学习之模型评估 在监督学习中&#xff0c;模型评估是衡量模型性能的关键步骤。有效的模型评估不仅能帮助我们理解模型在训练数据上的表现&#xff0c;更重要的是评估其在未见数据上的泛化能力。本文将深入探讨监督学习中的模型评估方法&#xff0c;重点介绍评估指…

pytorch基础之注解的使用--003

Title 1.学习目标2.定义3.使用步骤4.结果 1.学习目标 针对源码中出现一些注解的问题&#xff0c;这里专门写一篇文章进行讲解。包括如何自定义注解&#xff0c;以及注意事项&#xff0c;相信JAVA中很多朋友业写过&#xff0c;但是今天写的是Python哦。。。 2.定义 在 Python…

Linux day 1129

家人们今天继续学习Linux&#xff0c;ok话不多说一起去看看吧 三.Linux常用命令 3.1 Linux命令体验 3.1.1 常用命令演示 在这一部分中&#xff0c;我们主要介绍几个常用的命令&#xff0c;让大家快速感 受以下 Linux 指令的操作方式。主要包含以下几个指令&#xff1a; ls命…

【基础篇】二、MySQL数据库的操作

文章目录 前言Ⅰ. 创建数据库1、语法2、举例 Ⅱ. 字符集和校验规则1、查看系统默认字符集以及校验规则2、查看数据库支持的字符集3、查看数据库支持的字符集校验规则4、校验规则对数据库的影响 Ⅲ. 操作数据库1、查看数据库2、显示创建语句3、使用数据库4、删除数据库5、修改数…

NLP中的神经网络基础

一&#xff1a;多层感知器模型 1&#xff1a;感知器 解释一下&#xff0c;为什么写成 wxb>0 &#xff0c;其实原本是 wx > t ,t就是阈值&#xff0c;超过这个阈值fx就为1&#xff0c;现在把t放在左边。 在感知器里面涉及到两个问题&#xff1a; 第一个&#xff0c;特征提…

2025常见的软件测试面试题

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 “ 今天我给大家介绍一些python自动化测试中常见的面试题&#xff0c;涵盖了Python基础、测试框架、测试工具、测试方法等方面的内容&#xff0c;希望能够帮助…

日常编码练习

通过投票对团队排名 题目要求&#xff1a; 解题思路&#xff1a; 思路&#xff1a;以示例1为例 1、我们需要去遍历该数据结构&#xff0c;同时记录每个字符出现的次数。为此需要定义一张哈希表来建立映射关系。 注&#xff1a;哈希表仅仅起到了两个作用 ①&#xff1a;建立映射…

Java - 日志体系_Simple Logging Facade for Java (SLF4J)日志门面_SLF4J实现原理分析

文章目录 官网SLF4J 简单使用案例分析SLF4J 获取 Logger 的原理获取 ILoggerFactory 的过程获取 Logger 的过程SLF4J 与底层日志框架的集成 小结 官网 https://slf4j.org/ Simple Logging Facade for Java &#xff08;SLF4J&#xff09; 用作各种日志记录框架&#xff08;e.g…