per cpu data シンボルのアドレス

kallsymsを見てみると,メモリ先頭のユーザ空間のアドレスにあるシンボルが複数存在していることが確認できます.

% sudo cat /proc/kallsyms | head
0000000000000000 A irq_stack_union
0000000000000000 A __per_cpu_start
0000000000004000 A exception_stacks
0000000000009000 A gdt_page
000000000000a000 A espfix_waddr
000000000000a008 A espfix_stack
000000000000a020 A cpu_llc_id
000000000000a028 A cpu_llc_shared_map
000000000000a030 A cpu_core_map
000000000000a038 A cpu_sibling_map

なんかおかしいなと思ってましたが,__per_cpu_start__per_cpu_endに囲まれた領域にあるシンボルはコアごとに存在するper cpu dataのシンボルのようです.

ソースは追ってませんが,ドキュメントをみると,

DEFINE_PER_CPU(int, x);
int z;

z = this_cpu_read(x);

このコードは,

mov ax, gs:[x]

のようにgsセグメントを介したアクセスになります. x86_64だとgsはIA32_GS_BASE_MSRに設定します(GDTは32bitしか対応してないため).

簡単なカーネルモジュールでIA32_GS_BASE_MSRの確認してみました.

#define MSR_GS_BASE             0xc0000101

static unsigned long long rdmsr(int msr)
{
    unsigned long msrl = 0, msrh = 0;

    asm volatile ("rdmsr" : "=a"(msrl), "=d"(msrh) : "c"(msr));

    return ((unsigned long long)msrh << 32) | msrl;
}

static int hello_init(){
    long long gs = rdmsr(MSR_GS_BASE);
    pr_info("gs: %llx\n", gs);
    return 0;
}
% for i in `seq 0 1 7`; sudo taskset -c $i insmod hello.ko && sudo rmmod hello && dmesg|tail -n1
[35721.154116] gs: ffff8a729f200000
[35721.207429] gs: ffff8a729f240000
[35721.267899] gs: ffff8a729f280000
[35721.323081] gs: ffff8a729f2c0000
[35721.374967] gs: ffff8a729f300000
[35721.432406] gs: ffff8a729f340000
[35721.479421] gs: ffff8a729f380000
[35721.532804] gs: ffff8a729f3c0000

コアごとに0x40000 (=256KB)の領域が確保されているようです. またこの空間はメモリマップ的にはdirect mapping領域内に存在しています.