per-CPU是2.6内核中引入的,访问per-CPU变量几乎不需要锁,每个处理器都在其自己的副本上工作。这些副本是如何生成的呢?本文尝试解答这个问题。
静态per-CPU结构设计思路大体可以分为两个阶段:编译阶段和运行时阶段
在编译阶段,实际上只生成了一个CPU原本。系统中所有per-CPU结构都放到了一个叫做"data.percpu"的section中,在ld.S链接脚本有如下内容:
. = ALIGN(32);
__per_cpu_start = .;
.data.percpu : { *(.data.percpu) }
__per_cpu_end = .;
. = ALIGN(4096);
__init_end = .;
由这个链接脚本知道, .data.percpu Section是处于init数据段的,在系统初始化结束后将被回收。那么,系统如何维持per-CPU数据呢?这个任务在运行时完成。在系统初始化阶段有一个函数会分配 NR_CPU * ( __per_cpu_end - __per_cpu_start)大小的内存,然后将
可见,静态per-CPU变量的locality非常好,CPU之间在Cache级都不彼此干扰。对于静态生成的per-CPU变量需要使用get_cpu_var来访问。
在讲述动态per-CPU结构之前不妨思考下,如何能把动态per-CPU的locality设计得跟静态的一样呢? 由于不知道系统中将会有多少动态结构出现,所以不宜采用预留内存的方式,这为我们的设计带来了很大挑战。实际上,Linux也没有完全解决这个问题,但还是做了最大程度的优化,手法也比较赞。考虑到per-CPU变量的访问模式,效率应该和静态方式不相上下。下面看看linux的处理方式。
动态per-CPU结构相对于静态结构来说,设计上更直观,但效率上要低一些。每次调用alloc_percpu(type)的时候会生成一个维度为NR_CPUS的指针数组,每个指针指向一个kzalloc/kmalloc_node出来的type型对象。Linux在这里采取了一个优化手段:如果第i个cpu在线(linux支持cpu的hot-plug),那么就采用kmalloc_node来分配空间,这个空间与cpu i的亲和性很高;如果cpu i不在线,则采用通用的kzalloc分配了。下面是空间分配代码:
int node = cpu_to_node(cpu);
BUG_ON(pdata->ptrs[cpu]);
if (node_online(node))
pdata->ptrs[cpu] = kmalloc_node(size, gfp|__GFP_ZERO, node);
else
pdata->ptrs[cpu] = kzalloc(size, gfp);
对于动态生成的per-CPU变量需要用per_cpu_ptr来访问。
Ref: http://blog.chinaunix.net/u/12325/showart.php?id=1274548