1 .. include:: ../disclaimer-zh_CN.rst 2 3 :Original: Documentation/core-api/local_ops.rst 4 5 :翻译: 6 7 司延腾 Yanteng Si <siyanteng@loongson.cn> 8 9 .. _cn_local_ops: 10 11 ======================== 12 本地原子操作的语义和行为 13 ======================== 14 15 :作者: Mathieu Desnoyers 16 17 18 本文解释了本地原子操作的目的,如何为任何给定的架构实现这些操作,并说明了 19 如何正确使用这些操作。它还强调了在内存写入顺序很重要的情况下,跨CPU读取 20 这些本地变量时必须采取的预防措施。 21 22 .. note:: 23 24 注意,基于 ``local_t`` 的操作不建议用于一般内核操作。请使用 ``this_cpu`` 25 操作来代替使用,除非真的有特殊目的。大多数内核中使用的 ``local_t`` 已 26 经被 ``this_cpu`` 操作所取代。 ``this_cpu`` 操作在一条指令中结合了重 27 定位和类似 ``local_t`` 的语义,产生了更紧凑和更快的执行代码。 28 29 30 本地原子操作的目的 31 ================== 32 33 本地原子操作的目的是提供快速和高度可重入的每CPU计数器。它们通过移除LOCK前 34 缀和通常需要在CPU间同步的内存屏障,将标准原子操作的性能成本降到最低。 35 36 在许多情况下,拥有快速的每CPU原子计数器是很有吸引力的:它不需要禁用中断来保护中 37 断处理程序,它允许在NMI(Non Maskable Interrupt)处理程序中使用连贯的计数器。 38 它对追踪目的和各种性能监测计数器特别有用。 39 40 本地原子操作只保证在拥有数据的CPU上的变量修改的原子性。因此,必须注意确保只 41 有一个CPU写到 ``local_t`` 的数据。这是通过使用每CPU的数据来实现的,并确 42 保我们在一个抢占式安全上下文中修改它。然而,从任何一个CPU读取 ``local_t`` 43 数据都是允许的:这样它就会显得与所有者CPU的其他内存写入顺序不一致。 44 45 46 针对特定架构的实现 47 ================== 48 49 这可以通过稍微修改标准的原子操作来实现:只有它们的UP变体必须被保留。这通常 50 意味着删除LOCK前缀(在i386和x86_64上)和任何SMP同步屏障。如果架构在SMP和 51 UP之间没有不同的行为,在你的架构的 ``local.h`` 中包括 ``asm-generic/local.h`` 52 就足够了。 53 54 通过在一个结构体中嵌入一个 ``atomic_long_t`` , ``local_t`` 类型被定义为 55 一个不透明的 ``signed long`` 。这样做的目的是为了使从这个类型到 56 ``long`` 的转换失败。该定义看起来像:: 57 58 typedef struct { atomic_long_t a; } local_t; 59 60 61 使用本地原子操作时应遵循的规则 62 ============================== 63 64 * 被本地操作触及的变量必须是每cpu的变量。 65 66 * *只有* 这些变量的CPU所有者才可以写入这些变量。 67 68 * 这个CPU可以从任何上下文(进程、中断、软中断、nmi...)中使用本地操作来更新 69 它的local_t变量。 70 71 * 当在进程上下文中使用本地操作时,必须禁用抢占(或中断),以确保进程在获得每 72 CPU变量和进行实际的本地操作之间不会被迁移到不同的CPU。 73 74 * 当在中断上下文中使用本地操作时,在主线内核上不需要特别注意,因为它们将在局 75 部CPU上运行,并且已经禁用了抢占。然而,我建议无论如何都要明确地禁用抢占, 76 以确保它在-rt内核上仍能正确工作。 77 78 * 读取本地cpu变量将提供该变量的当前拷贝。 79 80 * 对这些变量的读取可以从任何CPU进行,因为对 “ ``long`` ”,对齐的变量的更新 81 总是原子的。由于写入程序的CPU没有进行内存同步,所以在读取 *其他* cpu的变 82 量时,可以读取该变量的过期副本。 83 84 85 如何使用本地原子操作 86 ==================== 87 88 :: 89 90 #include <linux/percpu.h> 91 #include <asm/local.h> 92 93 static DEFINE_PER_CPU(local_t, counters) = LOCAL_INIT(0); 94 95 96 计数器 97 ====== 98 99 计数是在一个signed long的所有位上进行的。 100 101 在可抢占的上下文中,围绕本地原子操作使用 ``get_cpu_var()`` 和 102 ``put_cpu_var()`` :它确保在对每个cpu变量进行写访问时,抢占被禁用。比如 103 说:: 104 105 local_inc(&get_cpu_var(counters)); 106 put_cpu_var(counters); 107 108 如果你已经在一个抢占安全上下文中,你可以使用 ``this_cpu_ptr()`` 代替:: 109 110 local_inc(this_cpu_ptr(&counters)); 111 112 113 114 读取计数器 115 ========== 116 117 那些本地计数器可以从外部的CPU中读取,以求得计数的总和。请注意,local_read 118 所看到的跨CPU的数据必须被认为是相对于拥有该数据的CPU上发生的其他内存写入来 119 说不符合顺序的:: 120 121 long sum = 0; 122 for_each_online_cpu(cpu) 123 sum += local_read(&per_cpu(counters, cpu)); 124 125 如果你想使用远程local_read来同步CPU之间对资源的访问,必须在写入者和读取者 126 的CPU上分别使用显式的 ``smp_wmb()`` 和 ``smp_rmb()`` 内存屏障。如果你使 127 用 ``local_t`` 变量作为写在缓冲区中的字节的计数器,就会出现这种情况:在缓 128 冲区写和计数器增量之间应该有一个 ``smp_wmb()`` ,在计数器读和缓冲区读之间 129 也应有一个 ``smp_rmb()`` 。 130 131 下面是一个使用 ``local.h`` 实现每个cpu基本计数器的示例模块:: 132 133 /* test-local.c 134 * 135 * Sample module for local.h usage. 136 */ 137 138 139 #include <asm/local.h> 140 #include <linux/module.h> 141 #include <linux/timer.h> 142 143 static DEFINE_PER_CPU(local_t, counters) = LOCAL_INIT(0); 144 145 static struct timer_list test_timer; 146 147 /* IPI called on each CPU. */ 148 static void test_each(void *info) 149 { 150 /* Increment the counter from a non preemptible context */ 151 printk("Increment on cpu %d\n", smp_processor_id()); 152 local_inc(this_cpu_ptr(&counters)); 153 154 /* This is what incrementing the variable would look like within a 155 * preemptible context (it disables preemption) : 156 * 157 * local_inc(&get_cpu_var(counters)); 158 * put_cpu_var(counters); 159 */ 160 } 161 162 static void do_test_timer(unsigned long data) 163 { 164 int cpu; 165 166 /* Increment the counters */ 167 on_each_cpu(test_each, NULL, 1); 168 /* Read all the counters */ 169 printk("Counters read from CPU %d\n", smp_processor_id()); 170 for_each_online_cpu(cpu) { 171 printk("Read : CPU %d, count %ld\n", cpu, 172 local_read(&per_cpu(counters, cpu))); 173 } 174 mod_timer(&test_timer, jiffies + 1000); 175 } 176 177 static int __init test_init(void) 178 { 179 /* initialize the timer that will increment the counter */ 180 timer_setup(&test_timer, do_test_timer, 0); 181 mod_timer(&test_timer, jiffies + 1); 182 183 return 0; 184 } 185 186 static void __exit test_exit(void) 187 { 188 timer_shutdown_sync(&test_timer); 189 } 190 191 module_init(test_init); 192 module_exit(test_exit); 193 194 MODULE_LICENSE("GPL"); 195 MODULE_AUTHOR("Mathieu Desnoyers"); 196 MODULE_DESCRIPTION("Local Atomic Ops");
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.