前言
通过Per-cpu变量除了可以分配内存,还有一个最大的好处就是不需要考虑同步。最好的同步技术就是把不需要同步的内核放在首位,因为每种显示的同步原语都有不容忽视的开销。
本质:Per-cpu变量主要是数据结构的数组,每个cpu对应数组中的一个元素。
位置:Per-cpu数组在主存中被排列,以使每个数据结构放在硬件高速缓存中的不同行(参考第二章硬件高速缓存)。这样,对每cpu数组的并发访问不会造成高速缓存行的窃用和实效(这种操作会带来昂贵的系统性能开销)。
应用条件
- 一个cpu不应该访问其它cpu对应的数组元素,另外,它可以随意读或者修改它自己的元素而不担心出现竞争条件。这样也意味者它只能在特殊的情况下使用,即确保在系统中cpu上的数据在逻辑上是独立的。
- 每cpu变量为来自不同cpu的并发访问提供保护,但对于来自异步函数(比如:本地中断和软中断)的访问不提供保护,在这种情况下需要提供额外的同步原语。
- 在单处理或多处理器系统中,内核抢占都会是每cpu变量产生竞争条件。总的原则是,内核控制路径应该在禁止内核抢占的情况下方法每cpu变量。
Eg:1.本地cpu上的任务切换。 2.一个内核控制路径获得了它的每cpu变量副本的地址,然后因为抢占内核控制路径被转移到另外一个cpu上(一个内核控制因为发生内核抢占而被转移到其他cpu上执行),但仍然引用原来cpu元素的地址。
优点:每个处理器访问自己的副本,几乎无需加锁(需要禁止抢占),可以放入自己的cache中,极大地提高了访问与更新效率。常用于计数器,tasklet、timer_list等机制都使用了per-CPU技术。
实现原理
"include/asm-generic/percpu.h"
#define DEFINE_PER_CPU(type, name) \
__attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
在GCC中__typeof__相当于typeof(检查数据类型),用__typeof__为了兼容性。
系统如何为每个CPU保留这些私有数据的?
在start_kernel函数中调用执行函数setup_per_cpu_areas( ),setup_per_cpu_areas( )的功能就是将.data.percpu中的数据拷贝到每个CPU的数据段中,每个CPU一份。其中CPU n 对应的专有数据区的首地址为__per_cpu_offset[n]。在系统启动的时候会生成以__per_cpu_start标识开头和__per_cpu_end标识结尾的数据段,在执行完函数setup_per_cpu_areas( )后,将.data.percpu中的数据拷贝到每个CPU数据段后,这个数据段就会释放(free)掉。
.data.percpu定义如下(在arch/ia64/kernel/vmlinux.lds.S):
{
__per_cpu_start = .;
*(.data.percpu)
*(.data.percpu.shared_aligned)
__per_cpu_end = .;
}
如何存取每个CPU的这些私有数据呢?
#define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))
#define __get_cpu_var(var) per_cpu(var, smp_processor_id())
相关函数和宏
"include/asm-generic/percpu.h"
DEFINE_PER_CPU(type,name) //静态分配一个每cpu数组,参数为类型和名字
per_cpu(name, cpu) //为cpu选择一个每cpu数组元素
_ _get_cpu_var(name) //选择每cpu数组中名字为name的本地cpu元素
"include\linux\percpu.h"
get_cpu_var(name) //先禁止抢占,然后选择每cpu数组中名字为name的本地cpu元素
put_cpu_var(name) //启用抢占,不使用name
alloc_percpu(type) //动态分配type类型数据结构的每cpu数组,并返回它的地址;就是在普通内存中分配的,详见代码
free_percpu(pointer) //释放动态分配的每cpu数组
per_cpu_ptr(pointer,cpu) //返回每cpu数组中与参数cpu对应的cpu元素地址,pointer为返回的地址。
例子
#include <linux/module.h>
#include <linux/percpu.h>
/* 需要掌握的内容:
* 1.怎么为每个cpu分配对应的变量,静态分配和动态分配,
* 需要指定变量类型和名称,也可以赋初始值。
* 2.怎么改变变量值,通过或per_cpu或者per_cpu_ptr.
* 3.怎么获取变量值,也可以通过per_cpu或者per_cpu_ptr.
*/
/* 方式一:静态的分配每cpu数组
* 实际上是根据系统的cpu个数来分配数组,这个数组包含有cpu个数个元素,
* 每个cpu在这个数组中都拥有一个对应的元素。
*/
static DEFINE_PER_CPU(int, val) = 8;
/* 动态分配的指针 */
static int __percpu *aval;
static int __init my_init(void)
{
int cpu,i=1;
pr_info("module init\n");
/* 获得本地cpu的per-cpu变量值 */
pr_info("local cpu:%d static per-cpu:%d\n", cpu, get_cpu_var(val));
put_cpu_var(val);
/* 打印每个cpu的per-cpu变量值 */
for_each_possible_cpu(cpu){
pr_info("each cpu:%d static per-cpu:%d\n", cpu, per_cpu(val,cpu));
}
/* 遍历每个可能的cpu */
for_each_possible_cpu(cpu){
/* 选择相应的cpu的per-cpu变量并赋值 */
per_cpu(val, cpu) = cpu;
/* 获取相应的cpu的per-cpu变量并显示 */
pr_info("cpu:%d assignment per-cpu:%d\n", cpu, per_cpu(val,cpu));
/* 使能内核抢占,因为上面的函数禁止内核抢占了 */
}
/* 方式二:动态分配per-cpu变量 */
aval = alloc_percpu(int);
/* 遍历每个可能的cpu */
for_each_possible_cpu(cpu){
/* 通过指针为动态分配的per-cpu变量赋值 */
*per_cpu_ptr(aval, cpu) = cpu;
/* 获取相应的cpu的per-cpu变量并显示 */
pr_info("cpu:%d alloc per-cpu:%d\n", cpu, *per_cpu_ptr(aval, cpu));
}
return 0;
}
static void __exit my_exit(void)
{
pr_info("module exit\n");
free_percpu(aval);
}
module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR("andy.chen");
MODULE_LICENSE("GPL v2");
结果如下: