基於Linux 2.6.32內核進行分析,看本篇文章前,建議先看看percpu變量這篇文章
smp_processor_id()用來獲取當前cpu的id,首先來看smp_processor_id的定義:
# define smp_processor_id() raw_smp_processor_id()
接下來:
#define raw_smp_processor_id() (percpu_read(cpu_number))
有必要解釋一下這里的cpu_number的由來:
DEFINE_PER_CPU(int, cpu_number);//每個CPU的cpuid是放置在cpu_number這個percpu變量中
后面最終調用了:
percpu_from_op("mov", per_cpu__##var, "m" (per_cpu__##var))
// 這里的var為cpu_number
這個宏展開后,實質上等價於:
#define percpu_from_op(op, var, constraint)
({
typeof(per_cpu_cpu_number) ret__;
switch (sizeof(per_cpu_cpu_number)) {
case 4:
asm(“movl %%fs:%P1, %0" //在64位體系結構下為 movl %%gs:%P1, %0
: "=r" (ret__)
: "m" (per_cpu_cpu_number));
break;
...
})
這里fs寄存器里面是什么東西?為什么這里就可以獲取到cpu的id?請看完后文后到這里進行回答:
smp_processor_id實際要去讀取的是cpu_number這個percpu變量,因為在系統初始化的時候,把每個cpu的id都設置到了cpu_number這個percpu變量中,請看代碼:
setup_per_cpu_areas()
{
for_each_possible_cpu(cpu) {
.....
per_cpu(cpu_number, cpu) = cpu;//看到沒,在這里設置進去了,等下我們就要去取里面的內容
setup_percpu_segment(cpu);
.....
}
}
至於per_cpu的實現,請參考文章開頭給出的那篇文章。總之這里就是把各個CPU的cpuid設置到了cpu_number這個percpu變量中。這里是為了給大家說明:我們不是要去cpu_number里面取cpuid嗎?那什么時候放進去的呢。現在大家明白了吧。
接下來正真看fs寄存器里有什么東西:
//在/arch/x86/kernel/head_32.S中 movl $(__KERNEL_PERCPU), %eax movl %eax,%fs //fs段寄存器指向GDT的27偏移項
那GDT的27偏移項描述符是誰呢?
static inline void setup_percpu_segment(int cpu)
{
#ifdef CONFIG_X86_32
struct desc_struct gdt;
pack_descriptor(&gdt, per_cpu_offset(cpu), 0xFFFFF,
0x2 | DESCTYPE_S, 0x;
gdt.s = 1;
write_gdt_entry(get_cpu_gdt_table(cpu),
GDT_ENTRY_PERCPU, &gdt, DESCTYPE_S);
#endif
}
這里設置的在 GDT 表中的偏移 GDT_ENTRY_PERCPU 與 前面fs的__KERNEL_PERCPU一樣都為27,所以fs段選擇子對應的GDT描述符項基地址為per_cpu_offset(cpu)。也就是說每個CPU的fs段選擇子都是一樣的27,但是設置的段描述符的基地址每個CPU都是不一樣的(因為base指定為per_cpu_offset(cpu))。
再回到最開始的嵌入式匯編:
asm("movb %%fs:%P1, %0"
:"=q"(ret__)
:"m"(per_cpu__cpu_number));
不難看出:per_cpu_offset[cpu] + &per_cpu__cpu_number 便得到了相應 cpu 的每 per_cpu__cpu_number變量的地址,與正常percpu變量存取方式不同,cpu_number的offset是通過GDT表項得到的。關於 per_cpu_offset數組的由來 請參考 setup_per_cpu_areas 函數。
整個smp_processor_id()取CPUid的示意圖:

FQA:
在start_kernel的開始調用boot_cpu_init,且boot_cpu_init在setup_per_cpu_areas()之前調用,在這個函數里面,已經在使用smp_processor_id了!但是此時運行時per_cpu_areas還沒初始化,這是怎么回事?
這是因為此時的fs引用的GDT描述符的base為0(此時GDT的GDT_ENTRY_PERCPU描述符base為0,參考arch/x86/kernel/cpu/common.c:DEFINE_PER_CPU_PAGE_ALIGNED),smp_processor_id()直接引用cpu_number的地址來獲取cpuid,cpu_number在編譯時分配到.data.percpu段,所以此時smp_processor_id返回為0。且此時 AP (應用處理器)還沒有啟動,BP(引導處理器)完成啟動,所以就是 cpu 0。
2.6.24內核中運行時cpu_number的值是在哪里初始化的?percpu變量專用的GDT描述符項在哪里設置的?
/* Initialize the CPU's GDT. This is either the boot CPU doing itself
(still using the master per-cpu area), or a CPU doing it for a
secondary which will soon come up. */
__cpuinit void init_gdt(int cpu)
{
struct desc_struct *gdt = get_cpu_gdt_table(cpu);
pack_descriptor((u32 *)&gdt[GDT_ENTRY_PERCPU].a,//依然在27(GDT_ENTRY_PERCPU)偏移項
(u32 *)&gdt[GDT_ENTRY_PERCPU].b,
__per_cpu_offset[cpu], 0xFFFFF,
0x80 | DESCTYPE_S | 0x2, 0x8);
per_cpu(this_cpu_off, cpu) = __per_cpu_offset[cpu];
per_cpu(cpu_number, cpu) = cpu;
}
參考整個系統GDT:
- ——- start of kernel segments: *
- 12 - kernel code segment <==== new cacheline
- 13 - kernel data segment
- 14 - default user CS
- 15 - default user DS
- 16 - TSS
- 17 - LDT
- 18 - PNPBIOS support (16->32 gate)
- 19 - PNPBIOS support
- 20 - PNPBIOS support
- 21 - PNPBIOS support
- 22 - PNPBIOS support
- 23 - APM BIOS support
- 24 - APM BIOS support
- 25 - APM BIOS support *
- 26 - ESPFIX small SS
- 27 - per-cpu [ offset to per-cpu data area ]
- 28 - stack_canary-20 [ for stack protector ]
- 29 - unused
- 30 - unused
- 31 - TSS for double fault handler
