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

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

kernel linux virtualization

φ(・・*)ゞ ウーン jailhouseのコードを読んでみるの3」の続きを。

まずはhypervisor/paging.cpaging_init()から。

paging_init()を上から順に見ていきます。

int paging_init(void)
{
        unsigned long per_cpu_pages, config_pages, bitmap_pages;
        unsigned long n;
        u8 *addr;
        int err;

        mem_pool.pages =
                (hypervisor_header.size - (__page_pool - __start)) / PAGE_SIZE;

まず、いきなり出てきたmem_poolはファイルの上の方で定義されていてpage_poolという構造体の変数。

struct page_pool mem_pool;

構造体の内容は以下の通り。

struct page_pool {
        void *base_address;
        unsigned long pages;
        unsigned long used_pages;
        unsigned long *used_bitmap;
        unsigned long flags;
};

名前が示すようにページの管理に使う構造体ですね。
ソースコード上では特にメンバ変数の意味については書かれていませんが、各変数の使用用途は素直に考えて良いのかな。base_addressはページプールで管理するメモリ領域の先頭アドレスで、このプールにはpages個のページが有ってそのうちused_pages個のページが使われている。使用しているページはビットマップ(used_bitmap)で調べることが出来て、ページの属性をflagsで管理する見たいな。

ということでここはページ数の計算をしているはずですね。
__page_poolはリンカスクリプトhypervisor/hypervisor.lds.Sにあります。

        . = ALIGN(PAGE_SIZE);
        __page_pool = .;

__startも同様にリンカスクリプトhypervisor/hypervisor.lds.Sにあります。

        . = 0;
        __start = .;

__startの方はhypervisor/setup.cでextern宣言されてます。

extern u8 __start[];
extern u8 __bss_start[], __bss_end[];

と、ここまででmem_poolのpagesの設定に関する部分が終了。
次は名前からしてcpu毎のページ数設定

        per_cpu_pages = hypervisor_header.possible_cpus *
                sizeof(struct per_cpu) / PAGE_SIZE;

hypervisor_header.possible_cpusにはnum_possible_cpus()の結果が入っている。例えば8コアのマシンで特に何もしていなければ8になっている。
PAGE_SIZEは通常なら4KiB(4096byte)でper_cpu構造体のサイズは調べて無いけど結構大きめ。構造体の定義はhypervisor/arch/x86/include/asm/percpu.hにあり、スタックとしてPAGE_SIZE分のメモリを確保していたりするので。

ビットマップが使用するページ数。

        bitmap_pages = (mem_pool.pages + BITS_PER_PAGE - 1) / BITS_PER_PAGE;

BITS_PER_PAGEはpaging.cでこのようになってました。

#define BITS_PER_PAGE                (PAGE_SIZE * 8)

ここはポインタ変数のsystem_configが指すアドレスを設定。

        system_config = (struct jailhouse_system *)
                (__page_pool + per_cpu_pages * PAGE_SIZE);

これはグローバル変数hypervisor/include/jailhouse/control.hにてextern宣言されてる。

extern struct jailhouse_system *system_config;

ここもまたページ数の設定。

        config_pages = (jailhouse_system_config_size(system_config) +
                        PAGE_SIZE - 1) / PAGE_SIZE;

ページの設定が正しく出来ているかチェック。

        if (mem_pool.pages <= per_cpu_pages + config_pages + bitmap_pages)
                goto error_nomem;

プールが管理するメモリ領域のアドレスのセットとビットマップで使うアドレスの設定、使用中のページ数設定、ビットマップに使用しているページのビットを立てる等。

        mem_pool.base_address = __page_pool;
        mem_pool.used_bitmap =
                (unsigned long *)(__page_pool + per_cpu_pages * PAGE_SIZE +
                                  config_pages * PAGE_SIZE);
        mem_pool.used_pages = per_cpu_pages + config_pages + bitmap_pages;
        for (n = 0; n < mem_pool.used_pages; n++)
                set_bit(n, mem_pool.used_bitmap);
        mem_pool.flags = PAGE_SCRUB_ON_FREE;

ここではremap_poolという変数のビットマップとかの設定。

        remap_pool.used_bitmap = page_alloc(&mem_pool, NUM_REMAP_BITMAP_PAGES);
        remap_pool.used_pages =
                hypervisor_header.possible_cpus * NUM_FOREIGN_PAGES;
        for (n = 0; n < remap_pool.used_pages; n++)
                set_bit(n, remap_pool.used_bitmap);

この変数はhypervisor/paging.cの上の方で定義されており、ある程度はコンパイル時にデータが設定されています。

struct page_pool remap_pool = {
        .base_address = (void *)REMAP_BASE_ADDR,
        .pages = BITS_PER_PAGE * NUM_REMAP_BITMAP_PAGES,
};

ここでbase_addressに設定しているはhypervisor/arch/x86/include/asm/paging.hにて定義。

#define REMAP_BASE_ADDR                0x0000000000100000UL

ここからはページテーブルの設定に。

        hv_page_table = page_alloc(&mem_pool, 1);
        if (!hv_page_table)
                goto error_nomem;

hv_page_tableもhypervisor/paging.cで定義されてます。

pgd_t *hv_page_table;

型から分るようにページグローバルディレクトリです。page_alloc()でページプールより1ページ確保します。ここで使っているpage_alloc()はhypervisor/paging.cにあります。2番目の引数は確保したいページ数です(alloc_pages()や__get_free_pages()みたいに2のべき乗で計算してないです)。

ページグローバルディレクトリのメモリが確保できたら次はpage_map_create()でPGDの個々の要素を設定。

        /* Replicate hypervisor mapping of Linux */
        for (addr = __start; addr < __start + hypervisor_header.size;
             addr += PAGE_SIZE) {
                err = page_map_create(hv_page_table, page_map_hvirt2phys(addr),
                                      PAGE_SIZE, (unsigned long)addr,
                                      PAGE_DEFAULT_FLAGS, PAGE_DEFAULT_FLAGS,
                                      PAGE_DIR_LEVELS);
                if (err)
                        goto error_nomem;
        }

        return 0;

error_nomem:
        printk("FATAL: page pool much too small\n");
        return -ENOMEM;
}

さて、前回の日記で後で読むにしといたhttps://github.com/siemens/jailhouse/blob/e4e7fbe3c618e52acad49eb90794be8134d50363/hypervisor/paging.c:title=hypervisor/paging.c:title=page_map_create()]をこの流れで続けていこう。

int page_map_create(pgd_t *page_table, unsigned long phys, unsigned long size,
                    unsigned long virt, unsigned long flags,
                    unsigned long table_flags, unsigned int levels)
{

levelsは何段階のページテーブルかというのが渡されます。paging_init()ではPAGE_DIR_LEVELSというマクロを渡していて、x86_64の場合は4段階なのでPAGE_DIR_LEVELSは4となっています。定義はhypervisor/arch/x86/include/asm/paging.hにあります。

#define PAGE_DIR_LEVELS                4

変数定義は飛ばして、

        unsigned long offs = hypervisor_header.page_offset;
        pgd_t *pgd;
        pud_t *pud;
        pmd_t *pmd;
        pte_t *pte;

for文で使われているsizeはpage_map_create()の3番目の引数ですね。呼出側はPAGE_SIZEを渡してます。virtは4番目の引数です。

        for (size = PAGE_ALIGN(size); size > 0;
             phys += PAGE_SIZE, virt += PAGE_SIZE, size -= PAGE_SIZE) {

そして4段階のページングの場合はcase 4が実行され、ページグローバルディレクトリの設定とページアッパーディレクトリが作成される。

                switch (levels) {
                case 4:
                        pgd = pgd_offset(page_table, virt);
                        if (!pgd_valid(pgd)) {
                                pud = page_alloc(&mem_pool, 1);
                                if (!pud)
                                        return -ENOMEM;
                                set_pgd(pgd, page_map_hvirt2phys(pud),
                                        table_flags);
                        }
                        pud = pud4l_offset(pgd, offs, virt);
                        break;

ここは省略

                case 3:
                        pud = pud3l_offset(page_table, virt);
                        break;
                default:
                        return -EINVAL;
                }

次にページミドルディレクトリとページテーブルの設定をする。

                if (!pud_valid(pud)) {
                        pmd = page_alloc(&mem_pool, 1);
                        if (!pmd)
                                return -ENOMEM;
                        set_pud(pud, page_map_hvirt2phys(pmd), table_flags);
                }

                pmd = pmd_offset(pud, offs, virt);
                if (!pmd_valid(pmd)) {
                        pte = page_alloc(&mem_pool, 1);
                        if (!pte)
                                return -ENOMEM;
                        set_pmd(pmd, page_map_hvirt2phys(pte), table_flags);
                }

                pte = pte_offset(pmd, offs, virt);
                set_pte(pte, phys, flags);
        }

最後にTLBのフラッシュ。

        flush_tlb();

        return 0;
}

これでpage_map_create()終了。
余談ですが、pgd_offset、set_pgd、set_pud、set_pmd、set_pte、flush_tlbなどはLinuxカーネルにある関数/マクロではなくてhypervisor/arch/x86/include/asm/paging.hにて定義されている物が使われていると思います。

Linuxカーネル2.6解読室

Linuxカーネル2.6解読室