支持SMP的现代操作系统使用每个CPU上的数据,对于给定的处理器其数据是唯一的。一般来说,每个CPU的数据存放在一个数组中。数组中的每一项对应着系统上一个存在的处理器。当前处理器号确定这个数组的当前元素:
- unsigned long my_percpu[NR_CPUS];
然后按如下方式访问它:
- int cpu;
- cpu = get_cpu();
- my_percpu[cpu]++;
- put_cpu();
注意,上面的代码中没有出现锁,因为所操作的数据对当前处理器来说是唯一的,除了当前处理器没有其他处理器可以接触这个数据,不存在并发访问。内核抢占成了唯一要关注的问题:
1. 如果你的代码被其他处理器抢占并重新调度,那么这时cpu变量会无效,因为它指向了错误的处理器(通常,代码获得当前处理器后是不能睡眠的)。
2. 如果别的任务抢占了你的代码,那么有可能在同一个处理器上发生并发访问my_percpu的情况。
其实不必担心,因为在获取当前处理器号,即调用get_cpu()时,就已经禁止了内核抢占。相应地,smp_processor_id()在调用put_cpu()时又会重新激活当前处理器号。注意,如果使用smp_processor_id()的调用来获得当前处理器号,只要使用上述方法来包含数据安全,那么内核抢占并不需要自己去禁止。
2.6内核为了方便创建和操作每个CPU数据,从而引进了新的操作接口,称作percpu。头文件<linux/percpu.h>声明了所有的接口操作例程,可以在文件mm/slab.c和asm/percpu.h中找到它们的定义。
1. 编译时的每个CPU数据
在编译时定义每个cpu变量:
在<Percpu.h(include/asm-generic)>中
- #ifdef CONFIG_SMP
- #define DEFINE_PER_CPU(type, name) /
- __attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
- #else /* ! SMP */
- #define DEFINE_PER_CPU(type, name) /
- __typeof__(type) per_cpu__##name
如果需要在别处声明变量,以防范编译时警告,可以使用下面的宏:
在<Percpu.h(include/asm-generic)>中
- #define DECLARE_PER_CPU(type, name) extern __typeof__(type) per_cpu__##name
调用get_cpu_var()返回当前处理器上的指定变量,同时它将禁止抢占;另一方面put_cpu_var()将相应地重新激活抢占。
- 在<Percpu.h(include/linux)>中
- #define get_cpu_var(var) (*({ /
- extern int simple_identifier_##var(void); /
- preempt_disable(); /
- &__get_cpu_var(var); }))
- #define put_cpu_var(var) preempt_enable()
也可以通过per_cpu()函数获得别的处理器上的每个CPU数据,使用时要小心,因为per_cpu()函数既不会禁止内核抢占,也不会提供任何形式的锁保护。如果一些处理器可以接触到其他处理器上的数据,就必须给数据上锁:
在<Percpu.h(include/asm-generic)>中
- #ifdef CONFIG_SMP
- #define per_cpu(var, cpu) (*({ /
- extern int simple_identifier_##var(void); /
- RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]); }))
- #else /* ! SMP */
- #define per_cpu(var, cpu) (*((void)(cpu), &per_cpu__##var))
这些编译时每个CPU数据的例子并不能在模块内使用,因为连接程序实际上将它们创建在一个惟一的可执行段中(.data.percpu),如果需要从模块中访问每个CPU数据,或者如果需要动态创建这些数据,可以看下一节。
内核实现每个CPU数据的动态分配方法类似与kmalloc()。
- 在<Percpu.h(include/linux)>中
- #define __alloc_percpu(size) percpu_alloc_mask((size), GFP_KERNEL, /
- cpu_possible_map)
- #define alloc_percpu(type) (type *)__alloc_percpu(sizeof(type))
- #define free_percpu(ptr) percpu_free((ptr))
- #define percpu_alloc_mask(size, gfp, mask) /
- __percpu_alloc_mask((size), (gfp), &(mask))
- #ifdef CONFIG_SMP
- extern void *__percpu_alloc_mask(size_t size, gfp_t gfp, cpumask_t *mask);
- #else /* CONFIG_SMP */
- static __always_inline void *__percpu_alloc_mask(size_t size, gfp_t gfp, cpumask_t *mask)
- {
- return kzalloc(size, gfp);
- }
内核提供两个宏来利用指针获取每个CPU数据:
最后,函数per_cpu_ptr()返回了指定处理器上的惟一数据。这个函数不会禁止内核抢占,如果需要访问另外的处理器数据,一定要给数据加锁:
- #define per_cpu_ptr(ptr, cpu) percpu_ptr((ptr), (cpu))
- #ifdef CONFIG_SMP
- struct percpu_data {
- void *ptrs[NR_CPUS];
- };
- #define __percpu_disguise(pdata) (struct percpu_data *)~(unsigned long)(pdata)
-
- #define percpu_ptr(ptr, cpu) /
- ({ /
- struct percpu_data *__p = __percpu_disguise(ptr); /
- (__typeof__(ptr))__p->ptrs[(cpu)]; /
- })
- #else /* CONFIG_SMP */
- #define percpu_ptr(ptr, cpu) ({ (void)(cpu); (ptr); })
首先,减少了数据锁定。按照每个处理器访问每个CPU数据的逻辑,可以不再需要任何锁。“只有这个处理器能访问这个数据”纯粹是一个编程约定,需要编程者确保本地处理器只会访问它自己的唯一数据。
第二,使用每个CPU数据可以大大减少缓存失效。失效发生在处理器试图使它们的缓存保持同步时。如果一个处理器操作某个数据,而数据又存放在其他处理器上,那么存放该数据的处理器必须清理或刷新自己的缓存。持续不断的缓存失效称为缓存抖动。使用每个CPU数据将减少缓存抖动。percpu接口缓存--对齐(cache-aligns)所有数据,以便确保在访问一个处理器的数据时不会将另一个处理器的数据带入同一个缓存线上。
每个CPU数据在中断上下文或进程上下文中使用都很安全,但是不能在访问每个CPU数据过程中睡眠。它的唯一要求就是禁止内核抢占。