DEFINE_PER_CPU(struct runqueue, runqueues)宏

 首先,在arch/i386/kernel/vmlinux.lds中有
  /* will be freed after init */
  . = ALIGN(4096);		/* Init code and data */
  __init_begin = .;

  /* 此處省略若干行:) */
  . = ALIGN(32);
  __per_cpu_start = .;
  .data.percpu  : { *(.data.percpu) }
  __per_cpu_end = .;

  . = ALIGN(4096);
  __init_end = .;
  /* freed after init ends here */



這說明__per_cpu_start和__per_cpu_end標識.data.percpu這個section的開頭和結尾
並且,整個.data.percpu這個section都在__init_begin和__init_end之間,
也就是說,該section所佔內存會在系統啓動後釋放(free)掉

因爲有
#define DEFINE_PER_CPU(type, name) \
__attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name

所以
static DEFINE_PER_CPU(struct runqueue, runqueues);
會擴展成
__attribute__((__section__(".data.percpu"))) __typeof__(struct runqueue) per_cpu__runqueues;
也就是在.data.percpu這個section中定義了一個變量per_cpu__runqueues,
其類型是struct runqueue。事實上,這裏所謂的變量per_cpu__runqueues,
其實就是一個偏移量,標識該變量的地址。

--------------------
其次,系統啓動後,在start_kernel()中會調用如下函數
unsigned long __per_cpu_offset[NR_CPUS];
static void __init setup_per_cpu_areas(void)
{
	unsigned long size, i;
	char *ptr;
	/* Created by linker magic */
	extern char __per_cpu_start[], __per_cpu_end[];

	/* Copy section for each CPU (we discard the original) */
	size = ALIGN(__per_cpu_end - __per_cpu_start, SMP_CACHE_BYTES);

#ifdef CONFIG_MODULES
	if (size < PERCPU_ENOUGH_ROOM)
		size = PERCPU_ENOUGH_ROOM;
#endif
	ptr = alloc_bootmem(size * NR_CPUS);

	for (i = 0; i < NR_CPUS; i++, ptr += size) {
		__per_cpu_offset[i] = ptr - __per_cpu_start;
		memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);
	}
}

在該函數中,爲每個CPU分配一段專有數據區,並將.data.percpu中的數據拷貝到其中,
每個CPU各有一份。由於數據從__per_cpu_start處轉移到各CPU自己的專有數據區中了,
因此存取其中的變量就不能再用原先的值了,比如存取per_cpu__runqueues
就不能再用per_cpu__runqueues了,需要做一個偏移量的調整,
即需要加上各CPU自己的專有數據區首地址相對於__per_cpu_start的偏移量。
在這裏也就是__per_cpu_offset[i],其中CPU i的專有數據區相對於
__per_cpu_start的偏移量爲__per_cpu_offset[i]。
這樣,就可以方便地計算專有數據區中各變量的新地址,比如對於per_cpu_runqueues,
其新地址即變成per_cpu_runqueues+__per_cpu_offset[i]。

經過這樣的處理,.data.percpu這個section在系統初始化後就可以釋放了。

--------------------
再看如何存取per cpu的變量
/* This macro obfuscates arithmetic on a variable address so that gcc
   shouldn't recognize the original var, and make assumptions about it */

#define RELOC_HIDE(ptr, off)					\
  ({ unsigned long __ptr;					\
    __asm__ ("" : "=g"(__ptr) : "0"(ptr));		\
    (typeof(ptr)) (__ptr + (off)); })

/* var is in discarded region: offset to particular copy we want */
#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())
#define get_cpu_var(var) (*({ preempt_disable(); &__get_cpu_var(var); }))

對於__get_cpu_var(runqueues),將等效地擴展爲
__per_cpu_offset[smp_processor_id()] + per_cpu__runqueues
並且是一個lvalue,也就是說可以進行賦值操作。
這正好是上述per_cpu__runqueues變量在對應CPU的專有數據區中的新地址。

由於不同的per cpu變量有不同的偏移量,並且不同的CPU其專有數據區首地址不同,
因此,通過__get_cpu_var()便訪問到了不同的變量。

--END

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章