本地原子操作的语义与行为¶
- 作者:
Mathieu Desnoyers
本文档解释了本地原子操作的目的,如何为任何给定架构实现它们,并展示了如何正确使用它们。它还强调了当内存写入顺序很重要时,在跨 CPU 读取这些本地变量时必须采取的预防措施。
注意
请注意,不建议在内核中一般使用基于 local_t
的操作。除非有特殊目的,否则请改用 this_cpu
操作。内核中 local_t
的大多数用法已被 this_cpu
操作取代。this_cpu
操作将重定位与类似 local_t
的语义结合在一个指令中,从而产生更紧凑、执行更快的代码。
本地原子操作的目的¶
本地原子操作旨在提供快速且高度可重入的每 CPU 计数器。它们通过移除通常需要同步跨 CPU 的 LOCK 前缀和内存屏障来最小化标准原子操作的性能开销。
拥有快速的每 CPU 原子计数器在许多情况下都很有用:它不需要禁用中断来防止中断处理程序的影响,并且允许在 NMI 处理程序中实现一致的计数器。它对于追踪目的和各种性能监控计数器特别有用。
本地原子操作只保证数据所属 CPU 对变量修改的原子性。因此,必须注意确保只有一个 CPU 写入 local_t
数据。这可以通过使用每 CPU 数据并确保我们在抢占安全的环境中修改它来实现。然而,允许从任何 CPU 读取 local_t
数据:它相对于所有者 CPU 的其他内存写入而言,可能会出现乱序写入的情况。
给定架构的实现¶
这可以通过稍微修改标准原子操作来完成:只需保留它们的 UP 变体。这通常意味着移除 LOCK 前缀(在 i386 和 x86_64 上)和任何 SMP 同步屏障。如果架构在 SMP 和 UP 之间没有不同的行为,那么在你的架构的 local.h
中包含 asm-generic/local.h
就足够了。
local_t
类型被定义为一个不透明的 signed long
类型,通过将一个 atomic_long_t
嵌入到一个结构体中。这样做是为了防止从该类型到 long
的类型转换失败。其定义如下:
typedef struct { atomic_long_t a; } local_t;
使用本地原子操作时应遵循的规则¶
被本地操作触及的变量必须是每 CPU 变量。
只有这些变量的 CPU 所有者才能写入它们。
该 CPU 可以从任何上下文(进程、中断、软中断、NMI 等)使用本地操作来更新其
local_t
变量。在进程上下文中使用本地操作时,必须禁用抢占(或中断),以确保进程在获取每 CPU 变量和执行实际的本地操作之间不会迁移到不同的 CPU。
在中断上下文中使用本地操作时,在主线内核上无需特殊注意,因为它们将在本地 CPU 上运行,并且抢占已禁用。然而,我建议无论如何都明确禁用抢占,以确保它在 -rt 内核上也能正常工作。
读取本地 CPU 变量将提供该变量的当前副本。
这些变量可以从任何 CPU 读取,因为对“
long
”对齐变量的更新总是原子的。由于写入者 CPU 不执行内存同步,当读取其他 CPU 的变量时,可能会读到过时的变量副本。
如何使用本地原子操作¶
#include <linux/percpu.h>
#include <asm/local.h>
static DEFINE_PER_CPU(local_t, counters) = LOCAL_INIT(0);
计数¶
计数是对一个有符号长整型的所有位进行的。
在可抢占上下文中,在本地原子操作周围使用 get_cpu_var()
和 put_cpu_var()
:这确保在对每 CPU 变量的写入访问周围禁用抢占。例如:
local_inc(&get_cpu_var(counters));
put_cpu_var(counters);
如果你已经在抢占安全的环境中,你可以改用 this_cpu_ptr()
local_inc(this_cpu_ptr(&counters));
读取计数器¶
这些本地计数器可以从其他 CPU 读取以进行计数求和。请注意,跨 CPU 的 local_read
所看到的数据必须被视为与拥有数据的 CPU 上发生的其他内存写入的顺序无关
long sum = 0;
for_each_online_cpu(cpu)
sum += local_read(&per_cpu(counters, cpu));
如果你想使用远程 local_read
来在 CPU 之间同步对资源的访问,则必须分别在写入者和读取者 CPU 上使用显式的 smp_wmb()
和 smp_rmb()
内存屏障。例如,如果你将 local_t
变量用作缓冲区中写入字节的计数器:在缓冲区写入和计数器递增之间应该有一个 smp_wmb()
,在计数器读取和缓冲区读取之间也应该有一个 smp_rmb()
。
这是一个使用 local.h
实现基本每 CPU 计数器的示例模块
/* test-local.c
*
* Sample module for local.h usage.
*/
#include <asm/local.h>
#include <linux/module.h>
#include <linux/timer.h>
static DEFINE_PER_CPU(local_t, counters) = LOCAL_INIT(0);
static struct timer_list test_timer;
/* IPI called on each CPU. */
static void test_each(void *info)
{
/* Increment the counter from a non preemptible context */
printk("Increment on cpu %d\n", smp_processor_id());
local_inc(this_cpu_ptr(&counters));
/* This is what incrementing the variable would look like within a
* preemptible context (it disables preemption) :
*
* local_inc(&get_cpu_var(counters));
* put_cpu_var(counters);
*/
}
static void do_test_timer(unsigned long data)
{
int cpu;
/* Increment the counters */
on_each_cpu(test_each, NULL, 1);
/* Read all the counters */
printk("Counters read from CPU %d\n", smp_processor_id());
for_each_online_cpu(cpu) {
printk("Read : CPU %d, count %ld\n", cpu,
local_read(&per_cpu(counters, cpu)));
}
mod_timer(&test_timer, jiffies + 1000);
}
static int __init test_init(void)
{
/* initialize the timer that will increment the counter */
timer_setup(&test_timer, do_test_timer, 0);
mod_timer(&test_timer, jiffies + 1);
return 0;
}
static void __exit test_exit(void)
{
timer_shutdown_sync(&test_timer);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mathieu Desnoyers");
MODULE_DESCRIPTION("Local Atomic Ops");