Per-cpu data
在linux和xen中都大量使用到了per_cpu这个宏,用来得到某颗cpu的数据地址,进行读/写操作
这里对xen中的per_cpu进行分析,跟linux中的per_cpu是一样的
为了方便起见,我们分析在csched_vcpu_wake函数的一个片段
per_cpu()使用
- static void
- csched_vcpu_wake(struct vcpu *vc)
- {
- struct csched_vcpu * const svc = CSCHED_VCPU(vc);
- const unsigned int cpu = vc->processor;
-
- BUG_ON( is_idle_vcpu(vc) );
-
- if ( unlikely(per_cpu(schedule_data, cpu).curr == vc) )
- {
- CSCHED_STAT_CRANK(vcpu_wake_running);
- return;
- }
|
可以看到per_cpu的作用就是得到编号为cpu的处理器的相应数据地址,使用起来相当方便的
为了搞清楚per_cpu的工作原理,先来看看per_cpu()的定义
- #define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))
|
再看看RELOC_HIDE的定义
- #define RELOC_HIDE(ptr, off) \
- ({ unsigned long __ptr; \
- __asm__ ("" : "=r"(__ptr) : "0"(ptr)); \
- (typeof(ptr)) (__ptr + (off)); })
|
整个合起来,对于per_cpu(schedule_data, cpu)来说,就是
*({ unsigned long __ptr; \
__asm__ ("" : "=r"(__ptr) : "0"(&per_cpu__schedule_data)); \
(typeof(&per_cpu__schedule_data)) (__ptr + ( __per_cpu_offset[cpu])); })
这里的&per_cpu__schedule_data就是per_cpu的schedule_data数据类型的基地址,__per_cpu_offset[n]是目标地址-基地址的偏移量,所以基地址+偏移量,就能得到想要的数据了.在讲到数据初始化之前,我们先来看看xen在编译的时候,如何往per_cpu里注册变量
-
- #define DEFINE_PER_CPU(type, name) \
- __attribute__((__section__(".data.percpu"))) \
- __typeof__(type) per_cpu__##name
|
上面的过程很简单,对我们给出的例子来说,就是声明一个名为per_cpu__schedule_data的变量,特别的是编译器会在连接的时候将这个变量放到.data.percpu区块.编译完成之后,来看下xen的ld脚本,它决定了per_cpu在内存的分布情况
-
- percpu : { } :percpu
- . = ALIGN(PERCPU_PAGE_SIZE);
- __phys_per_cpu_start = .;
- .data.percpu PERCPU_ADDR : AT(__phys_per_cpu_start - LOAD_OFFSET)
- {
- __per_cpu_start = .;
- *(.data.percpu)
- __per_cpu_end = .;
- }
- . = __phys_per_cpu_start + PERCPU_PAGE_SIZE;
- * into percpu page size
-
|
下面是per_cpu的初始化
- void *
- per_cpu_init (void)
- {
- void *cpu_data;
- int cpu;
-
-
- * get_free_pages() cannot be used before cpu_init() done. BSP
- * allocates "NR_CPUS" pages for all CPUs to avoid that AP calls
- * get_zeroed_page().
-
- if (smp_processor_id() == 0) {
- #ifdef XEN
- cpu_data = get_per_cpu_area();
- if (cpu_data == NULL)
- panic("can't allocate per cpu area.\n");
- #else
- cpu_data = __alloc_bootmem(PERCPU_PAGE_SIZE * NR_CPUS,
- PERCPU_PAGE_SIZE, __pa(MAX_DMA_ADDRESS));
- #endif
- for (cpu = 0; cpu < NR_CPUS; cpu++) {
- memcpy(cpu_data, __phys_per_cpu_start, __per_cpu_end - __per_cpu_start);
- __per_cpu_offset[cpu] = (char *) cpu_data - __per_cpu_start;
- cpu_data += PERCPU_PAGE_SIZE;
- per_cpu(local_per_cpu_offset, cpu) = __per_cpu_offset[cpu];
- }
- }
- return __per_cpu_start + __per_cpu_offset[smp_processor_id()];
- }
|
|_________|_ . . . . . _|__CPU0__|__CPU1__|__CPU2__|__...|__CPUNR_|
p0 p1 p2 p3 p4 p5 pn
p0:__phys_per_cpu_start
p1:__phys_per_cpu_start
p2:__per_cpu_start+__per_cpu_offset[0]
p3:__per_cpu_start+__per_cpu_offset[1]
p4:__per_cpu_start+__per_cpu_offset[2]
p5:__per_cpu_start+__per_cpu_offset[3]
pn:__per_cpu_start+__per_cpu_offset[NR-1]
__per_cpu_offset的定义
- #ifdef CONFIG_SMP
- unsigned long __per_cpu_offset[NR_CPUS];
- EXPORT_SYMBOL(__per_cpu_offset);
- #endif
|