本地原子操作的語義與行為¶
- 作者:
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");