本地原子操作的语义和行为¶
- 作者:
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));
如果要在 CPU 之间使用远程 local_read 来同步对资源的访问,则必须分别在写入器和读取器 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");