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

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

linux kernel virtualization

φ(・・*)ゞ ウーン jailhouseのコードを読んでみるの3」で残した4つの関数のうち、paging_init()
とpage_map_create()は「φ(・・*)ゞ ウーン jailhouseのコードを読んでみるの4」で確認したので
まずはhypervisor/arch/x86/setup.carch_init_early()を。

この関数自体はそんなに複雑では無いです。

int arch_init_early(struct cell *linux_cell,
                    struct jailhouse_cell_desc *config)
{
        unsigned long entry;
        unsigned int vector;
        int err;

        err = apic_init();
        if (err)
                return err;

apicの初期化を行い、次にIDTの初期化。

        entry = (unsigned long)exception_entries;
        for (vector = 0; vector < NUM_IDT_DESC; vector++) {
                if (vector == NMI_VECTOR || vector == 15)
                        continue;
                idt[vector * 4] = (entry & 0xffff) |
                        ((GDT_DESC_CODE * 8) << 16);
                idt[vector * 4 + 1] = 0x8e00 | (entry & 0xffff0000);
                idt[vector * 4 + 2] = entry >> 32;
                entry += 16;
        }

        entry = (unsigned long)nmi_entry;
        idt[NMI_VECTOR * 4] = (entry & 0xffff) | ((GDT_DESC_CODE * 8) << 16);
        idt[NMI_VECTOR * 4 + 1] = 0x8e00 | (entry & 0xffff0000);
        idt[NMI_VECTOR * 4 + 2] = entry >> 32;

exception_entriesやnmi_entryはhypervisor/arch/x86/entry.Sで定義。

idtの定義はファイル先頭の方で以下のような感じに。

#define NUM_IDT_DESC                20

static u32 idt[NUM_IDT_DESC * 4];

IDTの設定内容をコードだけで見るとちょっと分かり辛いのでIntel SDM Volume 3: System Programmingで見るとこんな感じの構造にデータを入れてます。サイズは64bitで上の2個が下位32bit、下の6個が上位32bit分。

bit 内容
0-15 割り込みを受けるエントリポイントまでのオフセットの下位16bit
16-31 セグメントセレクタ
0-4 予約済み
5-7 0をセット
8-12 8~10ビットまでは1、11ビット目のDビットはゲートのサイズが32bitなら1、16bitなら0、12ビット目は0
13-14 DPL 特権レベル
15 P セグメントの存在フラグ
16-31 割り込みを受けるエントリポイントまでのオフセットの上位16bit

hypervisor/arch/x86/vmx.cで定義されてる関数。この関数はMSRにx2APICを使用するための設定をするだけ。

        vmx_init();

hypervisor/arch/x86/vmx.cで定義されてる関数。EPTで使うメモリの設定が行われる。

        err = vmx_cell_init(linux_cell, config);
        if (err)
                return err;

        return 0;
}

apic_init()はこのような関数。

int apic_init(void)
{
        unsigned long apicbase;
        int err;

        apicbase = read_msr(MSR_IA32_APICBASE);

        if (apicbase & APIC_BASE_EXTD) {
                /* set programmatically to enable address fixup */
                apic_ops.read = read_x2apic;
                apic_ops.read_id = read_x2apic_id;
                apic_ops.write = write_x2apic;
                apic_ops.send_ipi = send_x2apic_ipi;
                using_x2apic = true;
        } else if (apicbase & APIC_BASE_EN) {
                xapic_page = page_alloc(&remap_pool, 1);
                if (!xapic_page)
                        return -ENOMEM;
                err = page_map_create(hv_page_table, XAPIC_BASE, PAGE_SIZE,
                                      (unsigned long)xapic_page,
                                      PAGE_DEFAULT_FLAGS | PAGE_FLAG_UNCACHED,
                                      PAGE_DEFAULT_FLAGS, PAGE_DIR_LEVELS);
                if (err)
                        return err;
                apic_ops.read = read_xapic;
                apic_ops.read_id = read_xapic_id;
                apic_ops.write = write_xapic;
                apic_ops.send_ipi = send_xapic_ipi;
        } else
                return -EIO;

        printk("Using x%sAPIC\n", using_x2apic ? "2" : "");

        return 0;
}

色々やっているけど主な処理としてはAPIC関連の操作に対する処理を実行する関数を登録するというのが目的ですね。

vmx_cell_init()はページングの設定がメイン。

int vmx_cell_init(struct cell *cell, struct jailhouse_cell_desc *config)
{
        struct jailhouse_memory *mem;
        u32 page_flags, table_flags;
        u32 pio_bitmap_size, size;
        u8 *pio_bitmap;
        int n, err;

まずはページを1ページ確保。

        /* build root cell EPT */
        cell->vmx.ept = page_alloc(&mem_pool, 1);
        if (!cell->vmx.ept)
                return -ENOMEM;

このcellのメモリに関する設定を読み込む。

        mem = (void *)config + sizeof(struct jailhouse_cell_desc) +
                config->cpu_set_size;

この構造体の内容は以下の通り。定義はhypervisor/include/jailhouse/cell-config.hに。各変数の内容は名前見れば分かりますね。

struct jailhouse_memory {
        __u64 phys_start;
        __u64 virt_start;
        __u64 size;
        __u64 access_flags;
};

ループしながらページングの設定。

        for (n = 0; n < config->num_memory_regions; n++, mem++) {
                page_flags = EPT_FLAG_WB_TYPE;
                if (mem->access_flags & JAILHOUSE_MEM_READ)
                        page_flags |= EPT_FLAG_READ;
                if (mem->access_flags & JAILHOUSE_MEM_WRITE)
                        page_flags |= EPT_FLAG_WRITE;
                if (mem->access_flags & JAILHOUSE_MEM_EXECUTE)
                        page_flags |= EPT_FLAG_EXECUTE;
                table_flags = page_flags & ~EPT_FLAG_WB_TYPE;

                err = page_map_create(cell->vmx.ept, mem->phys_start,
                                      mem->size, mem->virt_start, page_flags,
                                      table_flags, PAGE_DIR_LEVELS);
                if (err)
                        /* FIXME: release vmx.ept */
                        return err;
        }

ページの属性にはread・write・execute可として、ページテーブルの属性からはAビット、Dビットをクリア(俺が間違えていなければ)・・・
page_map_create()は「φ(・・*)ゞ ウーン jailhouseのコードを読んでみるの4」で見た通りでx86_64環境なので4段階のページテーブルを作成する。

次にアドレスにapic_access_pageを指定してページテーブルの作成。

        page_flags = EPT_FLAG_READ | EPT_FLAG_WRITE | EPT_FLAG_WB_TYPE;
        table_flags = EPT_FLAG_READ | EPT_FLAG_WRITE;
        err = page_map_create(cell->vmx.ept,
                              page_map_hvirt2phys(apic_access_page),
                              PAGE_SIZE, XAPIC_BASE, page_flags, table_flags,
                              PAGE_DIR_LEVELS);
        if (err)
                /* FIXME: release vmx.ept */
                return err;

この辺はやっていることはともかく、どのようなところで使われるのかまだ不明orz

        pio_bitmap = (void *)mem +
                config->num_irq_lines * sizeof(struct jailhouse_irq_line);
        pio_bitmap_size = config->pio_bitmap_size;

        memset(cell->vmx.io_bitmap, -1, sizeof(cell->vmx.io_bitmap));

        for (n = 0; n < 2; n++) {
                size = pio_bitmap_size <= PAGE_SIZE ?
                        pio_bitmap_size : PAGE_SIZE;
                memcpy(cell->vmx.io_bitmap + n * PAGE_SIZE, pio_bitmap, size);
                pio_bitmap += size;
                pio_bitmap_size -= size;
        }

        return 0;
}

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版