当前位置: 首页 > news >正文

临界区(Critical Section)与原子操作

为什么有时候看似正确的 C 语言代码,在加上中断后,运行结果会偶尔出错?

在上一期中,我们讨论了中断优先级的“抢占”。今天我们要讨论一种更隐蔽、更危险的情况:当主程序和中断(或两个中断)同时修改同一个变量时,会发生什么?

这不是逻辑错误,而是时序错误。这种错误可能让你的设备运行一个月都很正常,然后在某个特定的时刻突然死机或数据错乱。

1. 看似原子,实则三步:RMW 陷阱

很多初学者认为,C 语言里的一行代码就是一步操作,是不可分割的(原子的)。

比如:count++;

但在 CPU 的眼中,这行简单的代码会被翻译成三条汇编指令:

Read (读):把 count 变量的值从内存读到 CPU 寄存器。

Modify (改):在寄存器中把这个值加 1。

Write (写):把寄存器里的新值写回内存中的 count 地址。

这就是著名的 读-改-写 (Read-Modify-Write, RMW) 流程。

事故现场推演: 假设全局变量 count 当前是 100。

主程序想执行 count++。它刚执行完 Read,把 100 读到了寄存器里,还没来得及加。

就在这一瞬间,一个中断来了!

中断服务程序里也有一句 count++。因为中断打断了主程序,它完整地执行了读(100)、改(101)、写(101)的全过程。此时内存里的 count 变成了 101。

中断结束,回到主程序。

主程序继续执行。注意!主程序之前读到的值是 100,它并不知道内存已经被改过了。它继续执行 Modify (100+1=101) 和 Write (写入 101)。

结果:主程序加了一次,中断加了一次,理论上 count 应该是 102。但实际上,内存里是 101。一次累加就这样凭空消失了。

2. 什么是“临界区”?

为了防止上面的情况发生,我们需要引入一个概念:临界区 (Critical Section)。

临界区是指访问共享资源(如全局变量、硬件外设寄存器)的那段代码。 在上面的例子中,count++ 就是临界区。

核心规则: 临界区内的代码,必须一次性执行完,中间绝对不能被其他修改同一资源的任务打断。这就好比上厕所,进去之后必须锁门,不论外面谁在敲门,都得等你出来。

3. 方法一:简单粗暴的“关中断”

这是最传统、最通用的保护方法。既然怕中断打扰,那我在操作变量前,先把所有中断都关掉,操作完再打开。

// 进入临界区

__disable_irq(); // 关总中断 (PRIMASK 寄存器)

count++; // 安全地进行读改写

// 退出临界区

__enable_irq(); // 开总中断

优点:

逻辑简单,绝对安全。

适用于任何架构的单片机。

缺点与风险:

实时性受损:你关中断的这段时间,外面的世界发生了什么 CPU 完全不知道。如果关的时间太长(比如你在临界区里算了个浮点除法,或者更糟糕,加了个延时),可能会导致串口丢数据、定时器计时不准。

“开”过头了:如果你在一个子函数里关了中断,还没退出时又调用了另一个子函数,那个子函数里也有一对开关中断。结果内层子函数一退出,把中断打开了,外层函数还没执行完——保护失效。

工程中建议:关中断的时间要极短。只包住那几行赋值代码,千万别包住复杂的逻辑运算。

4. 方法二:硬件级的优雅——原子操作 (LDREX/STREX)

为了解决“关中断”太暴力的问题,ARM Cortex-M3/M4/M7 等内核提供了一套特殊的硬件指令:LDREX (Load Exclusive) 和 STREX (Store Exclusive)。

这是一套“带监控的读写”机制。

工作流程:

LDREX:CPU 读取变量 count,并且在硬件上给这个内存地址打一个“独占标记”。

修改:CPU 在寄存器里计算新值。

STREX:CPU 尝试把新值写回内存。

关键点:在写入前,硬件会检查:“从我刚才读走到现在写入,有没有其他东西(中断或DMA)改过这个地址的数据?”

如果没被改过:写入成功,返回 0。

如果被改过(标记丢失):写入失败,不修改内存,返回 1。

void Safe_Add(volatile int *ptr, int value) { int expected_value; do { // 1. 独占读取,并打上标记 expected_value = __LDREXW(ptr); // 2. 修改 (在寄存器中计算) int new_value = expected_value + value; // 3. 尝试独占写入 // 如果失败(返回非0),说明中间被插队了,循环重试 } while (__STREXW(new_value, ptr) != 0); }

优点:

不用关中断!即使中断来了,中断能正常响应。主程序只是发现“写入失败”,重做一次就行了。

对系统的实时性影响最小。

局限:

稍微复杂一点(需要硬件支持)。

适用于简单的逻辑运算(加减、赋值)。

5. 一个常见的误区:volatile

很多面试或工作中会问:“给变量加上 volatile 关键字,是不是就能解决数据竞争问题?”

答案是:绝对不能。

volatile 的作用是告诉编译器:“这个变量随时会变,不要去优化它,每次都要老老实实从内存读”。 它解决了“编译器优化”导致的问题,但它无法解决中断打断 RMW 时序的问题。 count++ 即使加了 volatile,依然是 读-改-写 三步,依然会被中断劈开。

所以:volatile 是必须的,但不是万能的。原子操作或关中断才是解决竞争的根本。

归纳本章

RMW 竞争:任何涉及“读出旧值 -> 修改 -> 写入新值”的操作,如果会被中断打断,都可能导致数据丢失。

临界区:一段需要独占访问的代码。

关中断:最简单的保护手段,但要快进快出,避免影响系统实时性。

原子指令 (LDREX/STREX):利用硬件机制实现的无锁保护,不用关中断,适合高性能场景。

http://www.jsqmd.com/news/293909/

相关文章:

  • 收藏备用!大模型开发必懂的8个核心技术概念(小白程序员入门指南)
  • 收藏!程序员必看:别让传统技术栈,困住你的职业上升路
  • 论文阅读:CHI 2025 “Don’t Forget the Teachers“: Towards an Educator-CenteredUnderstanding of Harms from L
  • 学霸同款2026 10款AI论文写作软件测评:本科生毕业论文必备工具推荐
  • 跨境卖家增长避坑:从防关联到合规投放的一套可落地SOP
  • 亚马逊自然排名突然下滑:不是“权重掉了”,而是转化链路断了
  • AI公众号排版工具测评:这款微信编辑器如何彻底解放新媒体运营人
  • 盒马鲜生礼品卡回收平台哪个靠谱?实测十大平台后我只推荐这三个
  • 安徽佑帮智能基本信息大汇总,它到底靠不靠谱选哪家好?
  • 恒达管评价如何?华东管道行业年度靠谱企业排名出炉
  • 梳理双层玻璃隔断,北京十大厂家都有谁
  • 网上雅思培训学校哪家好?2026 全方位测评推荐 直播课 + 高分上岸方案拆解
  • 聊聊长春实力强的咖啡培训学校推荐,欧米奇多模式教学
  • 深聊云迹客户精准线索系统,在杭州哪个口碑好呢?
  • 新建vue3项目
  • 2026年上海推荐线路板清洗消泡剂公司排名,金凌消泡剂名列前茅
  • 2025重型货架实力厂家大比拼:品质与口碑并存,中型货架/贯通货架/自动化立体库/阁楼货架,重型货架公司联系电话
  • 【效率提升】跨境电商多语言难做?解析 AI 图像本地化技术如何实现“一套图卖全球”
  • 2026青岛碎拼石深度评测,助你挑选心仪款,碎拼石/天然石/石材/地铺石/贴墙石/砌墙石/冰裂纹,碎拼石源头厂家找哪家
  • 【Python效率工具】多变体 SKU 图片怎么翻?解析 AI 批量图像处理技术,告别重复修图!
  • TTL、RS232、RS485串口通信协议详解与对比 - 实践
  • 基于plc的药片自动装瓶系统设计(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 基于PLC变电站变压器控制系统设计现成(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 基于plc的自动门控制系统设计(s7-200)(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 基于西门子PLC的隧道照明控制系统设计(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 四层电梯PLC设计S7-1200含博途程序H(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 2026年口碑好的陕西矿用防爆泵厂家权威实力榜
  • 滚!!!!!!!!!!!!!!!!!!!!!!!
  • BPE 训练完成后把分词器“固化”下来,(数据管道、模型训练、推理)无缝调用。核心只有三件事:保存、验证、接入。也适用于百度千帆/DeepSeek 这类平台化训练
  • 医院HIS系统CKEDITOR粘贴病历截图如何上传PHP?