読者です 読者をやめる 読者になる 読者になる

φ(・・*)ゞ ウーン jailhouseのコードを読んでみるの7

linux virtualization kernel

今回はcpu_init()から。

cpu_init()はこのような関数。他の関数を読んでいくだけで、この関数自体は特別なことはしてないですね。

static void cpu_init(struct per_cpu *cpu_data)
{
        int err;

        printk(" CPU %d... ", cpu_data->cpu_id);

        err = register_linux_cpu(cpu_data);
        if (err)
                goto failed;

        err = arch_cpu_init(cpu_data);
        if (err)
                goto failed;

        printk("OK\n");

        /* If this CPU is last, make sure everything was committed before we
         * signal the other CPUs spinning on initialized_cpus that they can
         * continue. */
        memory_barrier();
        initialized_cpus++;
        return;

failed:
        printk("FAILED\n");
        if (!error)
                error = err;
}

arch_cpu_init()はコメントがわかりやすいですね。ファイルはhypervisor/arch/x86/setup.cです。

int arch_cpu_init(struct per_cpu *cpu_data)
{
        struct desc_table_reg dtr;
        int err, n;

        /* read GDTR */
        read_gdtr(&cpu_data->linux_gdtr);

        /* read TR and TSS descriptor */
        asm volatile("str %0" : "=m" (cpu_data->linux_tss.selector));
        read_descriptor(cpu_data, &cpu_data->linux_tss);

        /* save CS as long as we have access to the Linux page table */
        asm volatile("mov %%cs,%0" : "=m" (cpu_data->linux_cs.selector));
        read_descriptor(cpu_data, &cpu_data->linux_cs);

        /* save segment registers - they may point to 32 or 16 bit segments */
        asm volatile("mov %%ds,%0" : "=m" (cpu_data->linux_ds.selector));
        read_descriptor(cpu_data, &cpu_data->linux_ds);

        asm volatile("mov %%es,%0" : "=m" (cpu_data->linux_es.selector));
        read_descriptor(cpu_data, &cpu_data->linux_es);

        asm volatile("mov %%fs,%0" : "=m" (cpu_data->linux_fs.selector));
        read_descriptor(cpu_data, &cpu_data->linux_fs);
        cpu_data->linux_fs.base = read_msr(MSR_FS_BASE);

        asm volatile("mov %%gs,%0" : "=m" (cpu_data->linux_gs.selector));
        read_descriptor(cpu_data, &cpu_data->linux_gs);
        cpu_data->linux_gs.base = read_msr(MSR_GS_BASE);

        /* read registers to restore on first VM-entry */
        for (n = 0; n < NUM_ENTRY_REGS; n++)
                cpu_data->linux_reg[n] =
                        ((unsigned long *)cpu_data->linux_sp)[n];
        cpu_data->linux_ip = ((unsigned long *)cpu_data->linux_sp)[6];

read_descriptor()は2番目の引数で指定されたセグメントのGDTのエントリを読み結果がそこに入ります。関数自体はarch_cpu_init()と同じくhypervisor/arch/x86/setup.chypervisor/arch/x86/setup.cにあります。
このあたりまでは今のCPUの設定を読み込んでper_cpu構造体のcpu_data変数に保存していってます。

そして、今のページテーブルの先頭アドレスをcpu_data->linux_cr3に保存しておきハイパーバイザのページテーブルに置き換え。

        /* swap CR3 */
        cpu_data->linux_cr3 = read_cr3();
        write_cr3(page_map_hvirt2phys(hv_page_table));

cpuにGDTを再設定。

        /* set GDTR */
        dtr.limit = NUM_GDT_DESC * 8 - 1;
        dtr.base = (u64)&gdt;
        write_gdtr(&dtr);

        set_cs(GDT_DESC_CODE * 8);

突然出てきた変数gdtは同ファイルの上の方にて定義済み。

static u64 gdt[] = {
        [GDT_DESC_NULL]   = 0,
        [GDT_DESC_CODE]   = 0x00af9b000000ffff,
        [GDT_DESC_TSS]    = 0x0000890000000000,
        [GDT_DESC_TSS_HI] = 0x0000000000000000,
};


ふむふむ。

        /* paranoid clearing of segment registers */
        asm volatile(
                "mov %0,%%es\n\t"
                "mov %0,%%ds\n\t"
                "mov %0,%%ss"
                : : "r" (0));

TSSのBフラグの解除。

        /* clear TSS busy flag set by previous loading, then set TR */
        gdt[GDT_DESC_TSS] &= ~TSS_BUSY_FLAG;
        asm volatile("ltr %%ax" : : "a" (GDT_DESC_TSS * 8));

なんでこんなことやってんだっけ?と思い我らがバイブルIntel SDMの「6.3. TASK SWITCHING」を見ると、タスクスイッチがJMPもしくはIRET命令によって起きた場合はプロセッサがBフラグをクリアし、CALL命令・割り込み・例外の場合はプロセッサはこのフラグをクリアしないと言っているのでそのために実施。このフラグはタスクが実行中・実行待機中に1になるのでそれは解除しておく必要ありと。

割り込み設定。既存の内容は保存した上で再設定というのは今までと一緒。

        /* swap IDTR */
        read_idtr(&cpu_data->linux_idtr);
        dtr.limit = NUM_IDT_DESC * 16 - 1;
        dtr.base = (u64)&idt;
        write_idtr(&dtr);

idtはarch_init_early()で設定したやつです。

ここはModel-Specific Registerからのデータ読み込みで、読み込んでいる内容はIntelのSDM(Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes:1, 2A, 2B, 2C, 3A, 3B and 3C)の「SYSENTER—Fast System Call Vol. 2B 4-399」に説明があります。が、このページの176HはIA32_SYSENTER_EIPのことだと思うんだけど。。。

        cpu_data->linux_efer = read_msr(MSR_EFER);

        cpu_data->linux_sysenter_cs = read_msr(MSR_IA32_SYSENTER_CS);
        cpu_data->linux_sysenter_eip = read_msr(MSR_IA32_SYSENTER_EIP);
        cpu_data->linux_sysenter_esp = read_msr(MSR_IA32_SYSENTER_ESP);

この時点で初期化完了で良いんだ。

        cpu_data->initialized = true;

残りは初期化系の関数を2個呼んで終了。

        err = apic_cpu_init(cpu_data);
        if (err)
                goto error_out;

        err = vmx_cpu_init(cpu_data);
        if (err)
                goto error_out;

        return 0;

error_out:
        arch_cpu_restore(cpu_data);
        return err;
}

では、まずはapic_cpu_init()を見よう。

int apic_cpu_init(struct per_cpu *cpu_data)
{
        unsigned int apic_id = phys_processor_id();
        unsigned int cpu_id = cpu_data->cpu_id;

        printk("(APIC ID %d) ", apic_id);

        if (apic_id > APIC_MAX_PHYS_ID)
                return -ERANGE;
        if (apic_to_cpu_id[apic_id] != APIC_INVALID_ID)
                return -EBUSY;
        /* only flat mode with LDR corresponding to logical ID supported */
        if (!using_x2apic && (apic_ops.read(APIC_REG_DFR) != 0xffffffff ||
            apic_ops.read(APIC_REG_LDR) != 1UL << (cpu_id + 24)))
                return -EINVAL;

        apic_to_cpu_id[apic_id] = cpu_id;
        cpu_data->apic_id = apic_id;
        return 0;
}

phys_processor_id()の内容はこんな感じでMSRからの読み込み。

static u32 read_x2apic_id(void)
{
        return read_msr(MSR_X2APIC_BASE + APIC_REG_ID);
}

MSR_X2APIC_BASEは0x800、APIC_REG_IDは0x02で0x802は何かをIntel SDMで調べると「10.12.1.2 x2APIC Register Address Space」の「Table 10-6. Local APIC Register Address Map Supported by x2APIC」で「Local APIC ID register」と定義されています。

(´-`).。oO(vmx_cpu_init()はちょっと長めなのでまた別途

Linuxカーネル解析入門 (I・O BOOKS)

Linuxカーネル解析入門 (I・O BOOKS)