最好的同步技术是把设计不需要同步的临界资源放在首位,这是一种思维方法,因为每一种显式的同步原语都有不容忽视的性能开销。
最简单也是最重要的同步技术包括把内核变量或数据结构声明为每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); })