最好的同步技術是把設計不需要同步的臨界資源放在首位,這是一種思維方法,因為每一種顯式的同步原語都有不容忽視的效能開銷。
最簡單也是最重要的同步技術包括把核心變數或資料結構聲明為每CPU變數(per-cpu variable)。每CPU變數主要是資料結構的數組,系統的每個CPU對應數組的一個元素。
一個CPU不應該訪問與其他CPU對應的數組元素,另外,它可以隨意讀或修改它自己的元素而不用擔心出現競爭條件,因為它是唯一有資格這麼做的CPU。但是,這也意味著每CPU變數基本上只能在特殊情況下使用,也就是當它確定在系統的CPU上的資料在邏輯上是獨立的時候。
每CPU的數組元素在主存中被排列以使每個資料結構存放在硬體快取的不同行,因此,對每CPU數組的並發訪問不會導致快取行的竊用和失效(這種操作會帶來昂貴的系統開銷)。
雖然每CPU變數為來自不同CPU的並發訪問提供保護,但對來自非同步函數(中斷處理常式和可延遲函數)的訪問不提供保護,在這種情況下需要另外的同步技術。
此外,在單一處理器和多處理器系統中,核心搶佔都可能使每CPU變數產生競爭條件。總的原則是核心控制路徑應該在禁用搶佔的情況下訪問每CPU變數。因為當一個核心控制路徑獲得了它的每CPU變數本機複本的地址,然後它因被搶佔而轉移到另外一個CPU上,但仍然引用原來CPU元素的地址,這是非常危險的。
本博,我們介紹一些每CPU方面有用的宏。
1 DEFINE_PER_CPU(type, name)
靜態分配一個每CPU數組,數組名為name,結構類型為type:
#define DECLARE_PER_CPU(type, name) extern __typeof__(type) per_cpu__##name
2 per_cpu(name, cpu)
獲得一個為用DEFINE_PER_CPU宏為CPU選擇的一個每CPU數組元素,CPU由cpu指定,數組名稱為name:
#define per_cpu(var, cpu) (*((void)(cpu), &per_cpu__##var))
3 __get_cpu_var(name)
選擇每CPU數組name的本地CPU元素:
#define __get_cpu_var(var) per_cpu__##var
4 get_cpu_var(name)
先禁用核心搶佔,然後在每CPU數組name中,為本地CPU選擇元素:
#define get_cpu_var(var) (*({ preempt_disable(); &__get_cpu_var(var); }))
5 put_cpu_var(name)
啟用核心搶佔(不使用name):
#define put_cpu_var(var) preempt_enable()
6 alloc_percpu(type)
動態分配type類型資料結構的每CPU數組,並返回它的地址:
#define alloc_percpu(type) ((type *)(__alloc_percpu(sizeof(type))))
static inline void *__alloc_percpu(size_t size)
{
void *ret = kmalloc(size, GFP_KERNEL);
if (ret)
memset(ret, 0, size);
return ret;
}
7 free_percpu(pointer)
釋放動態分配的每CPU數組,pointer指示其地址:
static inline void free_percpu(const void *ptr)
{
kfree(ptr);
}
8 per_cpu_ptr(pointer, cpu)
返回每CPU數組中與cpu對應CPU元素地址,pointer給出數組地址:
#define per_cpu_ptr(ptr, cpu) ({ (void)(cpu); (ptr); })