news 2026/4/23 23:23:16

Linux内核并发编程避坑指南:atomic_add和atomic_sub到底怎么用才安全?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux内核并发编程避坑指南:atomic_add和atomic_sub到底怎么用才安全?

Linux内核并发编程避坑指南:atomic_add和atomic_sub的安全使用实践

在Linux内核开发中,原子操作是处理并发问题的基石。许多开发者虽然知道如何使用atomic_add和atomic_sub这类基本原子操作,却常常忽略它们背后的内存模型和可见性问题。我曾在一个网络驱动项目中,因为错误使用atomic_sub导致引用计数异常,花了整整两天时间才定位到这个隐蔽的竞态条件。

1. 原子操作的本质与常见误区

原子操作的核心特性是不可分割性——这些操作要么完全执行,要么完全不执行,不会出现中间状态。但很多开发者误以为只要使用了原子操作,就万事大吉了。实际上,原子性只是解决了操作完整性的问题,并没有解决数据可见性和执行顺序的问题。

Linux内核中atomic_t类型的典型定义如下:

typedef struct { volatile int counter; } atomic_t;

这里的volatile关键字告诉编译器不要对这个变量进行优化,每次访问都必须从内存中读取或写入。

常见的原子操作API包括:

  • atomic_read(v):读取原子变量的值
  • atomic_set(v, i):设置原子变量的值
  • atomic_add(i, v):原子地增加v的值
  • atomic_sub(i, v):原子地减少v的值

最常见的误区是认为atomic_addatomic_sub已经足够安全。实际上,在多核(SMP)系统中,这些操作虽然保证了原子性,但没有提供内存屏障(memory barrier),可能导致其他CPU核心看不到最新的值。

2. 内存屏障与带返回值的原子操作

内存屏障是确保多核系统中内存访问顺序和可见性的关键机制。在ARM架构中,原子操作通常通过ldrexstrex指令对实现:

static inline void atomic_add(int i, atomic_t *v) { unsigned long tmp; int result; __asm__ __volatile__("@ atomic_add\n" "1: ldrex %0, [%3]\n" " add %0, %0, %4\n" " strex %1, %0, [%3]\n" " teq %1, #0\n" " bne 1b" : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter) : "r" (&v->counter), "Ir" (i) : "cc"); }

带返回值的原子操作如atomic_add_return与普通版本的关键区别在于内存屏障:

static inline int atomic_add_return(int i, atomic_t *v) { unsigned long tmp; int result; smp_mb(); // 内存屏障 __asm__ __volatile__("@ atomic_add_return\n" "1: ldrex %0, [%2]\n" " add %0, %0, %3\n" " strex %1, %0, [%2]\n" " teq %1, #0\n" " bne 1b" : "=&r" (result), "=&r" (tmp) : "r" (&v->counter), "Ir" (i) : "cc"); smp_mb(); // 内存屏障 return result; }

内存屏障确保了:

  1. 屏障前的所有内存操作在屏障后的操作开始前完成
  2. 操作结果对其他CPU核心立即可见

3. 引用计数实现的正确姿势

引用计数是原子操作的典型应用场景,但实现中有许多陷阱需要避免。以下是几种常见的实现方式及其问题:

3.1 错误实现示例

// 不安全的引用获取 void get_ref(struct obj *o) { atomic_add(1, &o->refcnt); // 没有内存屏障 } // 不安全的引用释放 void put_ref(struct obj *o) { if (atomic_sub_and_test(1, &o->refcnt)) // 同样没有内存屏障 kfree(o); }

这种实现在单核系统上可能工作正常,但在SMP系统中可能导致:

  • 一个CPU增加了引用计数,但另一个CPU看不到这个变化
  • 引用计数为0时对象被释放,但实际上仍有引用存在

3.2 正确实现方式

// 安全的引用获取 void get_ref(struct obj *o) { atomic_inc_return(&o->refcnt); // 使用带内存屏障的版本 } // 安全的引用释放 void put_ref(struct obj *o) { if (atomic_dec_and_test(&o->refcnt)) // 使用带测试的递减操作 kfree(o); }

关键区别在于使用了atomic_inc_returnatomic_dec_and_test,这些函数内部包含了必要的内存屏障。

4. 复杂场景下的原子操作组合

有时我们需要实现比简单增减更复杂的同步原语。例如,实现一个只有当计数器达到特定值时才触发操作的函数:

// 不安全的实现 bool unsafe_check_and_trigger(atomic_t *counter, int threshold) { if (atomic_read(counter) >= threshold) { atomic_sub(threshold, counter); return true; } return false; }

这个实现存在竞态条件:在atomic_readatomic_sub之间,其他CPU可能修改了计数器的值。正确的做法是使用atomic_cmpxchg

// 安全的比较交换实现 bool safe_check_and_trigger(atomic_t *counter, int threshold) { int old, new; do { old = atomic_read(counter); if (old < threshold) return false; new = old - threshold; } while (atomic_cmpxchg(counter, old, new) != old); return true; }

atomic_cmpxchg会原子地比较counter的值是否为old,如果是则替换为new,否则不修改。这个操作在ARM上的实现同样基于ldrex/strex指令对。

5. 性能考量与替代方案

虽然原子操作比锁更轻量级,但在高竞争场景下仍然有性能开销。一些优化策略包括:

  1. 减少共享数据:尽可能设计无共享或最小化共享的架构
  2. 使用每CPU变量:对于每个CPU独立计数的场景
  3. 退避策略:在竞争激烈时使用cpu_relax()短暂让步
// 使用cpu_relax的忙等待示例 while (!atomic_add_unless(&var, 1, MAX)) { cpu_relax(); // 降低CPU功耗和总线争用 }

在Linux内核中,cpu_relax()通常实现为一条简单的pause指令,它告诉CPU这是一个忙等待循环,可以优化执行流水线。

6. 调试与验证技巧

原子操作相关的问题往往难以复现和调试。以下是一些实用技巧:

  1. 使用LOCKDEP:内核的锁依赖检查器可以帮助发现潜在的原子操作误用
  2. KCSAN:内核并发检测工具,可以捕捉数据竞争
  3. 人工代码审查:特别注意:
    • 是否有遗漏的内存屏障
    • 原子操作是否被正确配对使用
    • 是否存在ABA问题
// 示例:使用atomic_add_return调试计数问题 int old = atomic_add_return(1, &counter); pr_debug("Counter increased from %d to %d\n", old - 1, old);

在调试引用计数问题时,可以在每次增减时打印旧值和新值,这有助于追踪计数异常的原因。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 23:12:31

面向车载冰箱高效可靠需求的功率器件选型策略与器件适配手册

随着车载出行场景的拓展与消费升级&#xff0c;车载冰箱已成为保障旅途生活品质的关键设备。其电源与压缩机驱动系统作为整机“能量心脏”&#xff0c;需在严苛的车载电气环境下实现高效、稳定、低噪声运行&#xff0c;功率器件的选型直接决定系统转换效率、热管理难度、EMC性能…

作者头像 李华
网站建设 2026/4/23 23:00:19

IEC 61850 核心概念精讲

1. IEC 61850标准概述 第一次接触IEC 61850标准时&#xff0c;我也被它复杂的文档体系搞得晕头转向。直到参与了一个智能变电站项目后&#xff0c;我才真正理解这套标准的精妙之处。IEC 61850是国际电工委员会制定的变电站通信网络和系统系列标准&#xff0c;在国内对应DL/T 86…

作者头像 李华