refcount_t API 与 atomic_t 的比较

简介

refcount_t API 的目标是为实现对象的引用计数器提供一个最小化的 API。虽然 lib/refcount.c 中通用的、与架构无关的实现底层使用了原子操作,但在内存排序保证方面,一些 refcount_*()atomic_*() 函数之间存在一些差异。本文档概述了这些差异并提供了相应的示例,以帮助维护者根据这些内存排序保证的变化来验证他们的代码。

本文档中使用的术语试图遵循 tools/memory-model/Documentation/explanation.txt 中定义的正式 LKMM。

memory-barriers.txt 和 atomic_t.txt 提供了关于内存排序的一般背景知识,以及特别是关于原子操作的更多背景知识。

相关内存排序类型

注意

以下部分仅涵盖了与原子操作和引用计数器相关并贯穿本文档使用的部分内存排序类型。要了解更广泛的内容,请查阅 memory-barriers.txt 文档。

在没有任何内存排序保证(即完全无序)的情况下,原子操作和引用计数器仅提供原子性以及(在同一 CPU 上的)程序顺序 (po) 关系。它保证每个 atomic_*()refcount_*() 操作都是原子的,并且指令在单个 CPU 上以程序顺序执行。这通过使用 READ_ONCE()/WRITE_ONCE() 和比较-交换原语来实现。

强(完全)内存排序保证同一 CPU 上所有先前的加载和存储(所有 po-早期指令)在同一 CPU 上执行任何 po-后续指令之前完成。它还保证同一 CPU 上所有 po-早期的存储以及从其他 CPU 传播的所有存储必须在原始 CPU 上执行任何 po-后续指令之前传播到所有其他 CPU(A-累积属性)。这通过使用 smp_mb() 来实现。

释放 (RELEASE) 内存排序保证同一 CPU 上所有先前的加载和存储(所有 po-早期指令)在操作之前完成。它还保证同一 CPU 上所有 po-早期的存储以及从其他 CPU 传播的所有存储必须在释放操作之前传播到所有其他 CPU(A-累积属性)。这通过使用 smp_store_release() 来实现。

获取 (ACQUIRE) 内存排序保证同一 CPU 上所有后续的加载和存储(所有 po-后续指令)在获取操作之后完成。它还保证同一 CPU 上所有 po-后续的存储必须在获取操作执行后传播到所有其他 CPU。这通过使用 smp_acquire__after_ctrl_dep() 来实现。

引用计数器的控制依赖(成功时)保证,如果成功获取了对象的引用(引用计数器递增或添加发生,函数返回 true),则后续存储与此操作有序。对存储的控制依赖不是使用任何显式屏障实现的,而是依赖 CPU 不对存储进行推测。这只是一个单 CPU 关系,不为其他 CPU 提供任何保证。

函数比较

情况 1) - 非“读/修改/写” (RMW) 操作

函数变化

内存排序保证变化

  • 无(两者都完全无序)

情况 2) - 带有释放 (RELEASE) 排序的非“读/修改/写” (RMW) 操作

函数变化

内存排序保证变化

  • 无(两者都提供释放 (RELEASE) 排序)

情况 3) - 不返回值的基于增量的操作

函数变化

内存排序保证变化

  • 无(两者都完全无序)

情况 4) - 不返回值的基于减量的 RMW 操作

函数变化

内存排序保证变化

  • 完全无序 --> 释放 (RELEASE) 排序

情况 5) - 返回值的基于增量的 RMW 操作

函数变化

内存排序保证变化

  • 完全有序 --> 存储成功时控制依赖

注意

我们在此确实假设,获取对象指针会提供必要的排序!

情况 6) - 带有获取 (ACQUIRE) 排序且返回值的基于增量的 RMW 操作

函数变化

内存排序保证变化

  • 完全有序 --> 成功时获取 (ACQUIRE) 排序

情况 7) - 返回值的通用 dec/sub 基于减量的 RMW 操作

函数变化

内存排序保证变化

  • 完全有序 --> 释放 (RELEASE) 排序 + 成功时获取 (ACQUIRE) 排序

情况 8) 返回值的其他基于减量的 RMW 操作

函数变化

  • 无原子对应项 --> refcount_dec_if_one()

  • atomic_add_unless(&var, -1, 1) --> refcount_dec_not_one(&var)

内存排序保证变化

  • 完全有序 --> 释放 (RELEASE) 排序 + 控制依赖

注意

atomic_add_unless() 仅在成功时提供完全有序。

情况 9) - 基于锁的 RMW

函数变化

内存排序保证变化

  • 完全有序 --> 释放 (RELEASE) 排序 + 控制依赖 + 成功时持有 spin_lock()