当内核不需要支持smp的时候,per_cpu是无关紧要的,因为数据只有一份。但是当需要支持smp的时候,per_cpu就显示相当重要了,它可以为每个CPU定义属于自己的专有数据。
在kernel/shed.c中有这样一个定义:
/*
* This is the main, per-CPU runqueue data structure.
*
* Locking rule: those places that want to lock multiple runqueues
* (such as the load balancing or the thread migration code), lock
* acquire operations must be ordered by ascending &runqueue.
*/
struct rq {
…
};
static DEFINE_PER_CPU(struct rq, runqueues) ____cacheline_aligned_in_smp;
从注释中可以看出rq这个结构体保存了运行时的任务队列信息,当然每个CPU都有自己的任务队列,因而自然需要为每个CPU都定义一个私有的数据。
其中DEFINE_PER_CPU的定义在include/asm-generic/percpu.h中:
/* Separate out the type, so (int[3], foo) works. */
#define DEFINE_PER_CPU(type, name) /
__attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
满简单的,无非就是定义一个指定类型的变量,再将它放到一个特定的数据段中。为此,我们需要在ldf文件中添加这样一段:
//.percpu
INPUT_SECTION_ALIGN(64)
. = (. + 63) / 64 * 64;
___per_cpu_start = .;
INPUT_SECTIONS($LIBRARIES_UCLINUX(.data.percpu))
___per_cpu_end = .;
到这里,我们还看不出是如何为每个CPU保留自己的数据的。
在start_kernel()函数中有这样一个调用:
setup_per_cpu_areas();
这个函数实现如下所示:
unsigned long __per_cpu_offset[NR_CPUS] __read_mostly;
EXPORT_SYMBOL(__per_cpu_offset);
static void __init setup_per_cpu_areas(void)
{
unsigned long size, i;
char *ptr;
unsigned long nr_possible_cpus = num_possible_cpus();
/* Copy section for each CPU (we discard the original) */
size = ALIGN(PERCPU_ENOUGH_ROOM, PAGE_SIZE);
ptr = alloc_bootmem_pages(size * nr_possible_cpus);
for_each_possible_cpu(i) {
__per_cpu_offset[i] = ptr - __per_cpu_start;
memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);
ptr += size;
}
}
也就是说,A核从内核为自己申请了一块空间,把percpu这个段的内容复制了一遍,自然也就有了一块自己的数据。当然,当内核运行到这里的时候,B核还没有启动, for_each_possible_cpu只会执行一次,但是可以想像,当B核启动时,应该也会将这个段的内容复制一遍。
在这里,还使用了一个__per_cpu_offset数组来保存每个核的私有数据离__per_cpu_start的偏移量。
当bf561核需要访问自己的私有数据时,它会这样:
volatile struct rq *rq;
rq = cpu_rq(i);
这里,cpu_rq定义为:
#define cpu_rq(cpu) (&per_cpu(runqueues, (cpu)))
而per_cpu的定义则为:
# define RELOC_HIDE(ptr, off) /
({ unsigned long __ptr; /
__ptr = (unsigned long) (ptr); /
(typeof(ptr)) (__ptr + (off)); })
/* var is in discarded region: offset to particular copy we want */
#define per_cpu(var, cpu) (*({ /
extern int simple_identifier_##var(void); /
RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]); }))
从而保证访问的是自己的私有数据,而不是.data.per_cpu这个段中的数据内容。