panda's tech note

Advent Calendar 2018: advos

Day 11: メモリ管理(バディシステムの初期化)

アーキテクチャ依存コードの整理

カーネルはアーキテクチャ依存のコードがたくさん出てきます。アーキテクチャ依存コードはsrc/kernel/arch/x86_64に置くようにします。src/kernel/kernel.cのアーキテクチャ依存コードも徐々に移していこうと思います。まず,手始めに,src/kernel/kasm.Ssrc/kernel/arch/x86_64/asm.Sに移動します。

バディシステムの実装

今日は昨日の残課題になっていたバディシステムの実装をしていきます。

昨日説明した以下の3つのゾーンのうち,ZONE_DMAとZONE_KERNELをコアゾーンと呼ぶことにします。

  • ZONE_DMA: 0–16 MiB
  • ZONE_KERNEL: 16–64 MiB
  • ZONE_NUMA_AWARE: 64– MiB

コアゾーンは簡単のため,メモリ管理システム用のメモリ領域(コアゾーンのバディシステムのポインタ情報などを保存する領域)として,0x00068000-0x00068fffを割り当てます。

Start End Description
00000500 00007bff ブートローダのスタック領域
00007c00 00007dff MBR
00007e00 00007fff 未使用
00008000 00008fff ブート情報(ドライブ番号など)
00009000 0000cfff ブートモニタ (16 KiB)
0000d000 0000ffff 未使用 (ブートモニタ拡張用に予約)
00010000 0002ffff カーネル (最大128 KiB)
00030000 00067fff 予約
00068000 00068fff コアゾーン管理用領域
00069000 0006ffff カーネルのベースページテーブル
00070000 00073fff 予約:trampoline (16 KiB)
00074000 00075fff 予約:GDT (8 KiB)
00076000 00077fff 予約:IDT (8 KiB)
00078000 00078fff 予約
00079000 0007ffff ロングモード用ページテーブル (24 KiB = 6 * 4 KiB以上必要)

バディシステムの初期化

最もストレートフォワードなバディシステムの初期化方法は,INT 15h, AX=E820hのメモリマップでusableな領域を1ページずつ追加する方法です。しかしこの方法は,大容量のメモリの初期化に非常に時間がかかります。コアゾーンだけなら問題無いですが,NUMA-awareなゾーンにもコードを再利用できるようにするため,初期化は工夫します。

あるメモリ領域baseからnext-1までをバディーシステムに登録する関数phys_mem_buddy_add_regions()を以下の用に実装します。以下のコードは全て仮想メモリのアドレスとして扱います。昨日説明した通り,リニアマッピングを採用しているため,物理メモリアドレスは仮想メモリへのリニアマッピングのオフセットから計算できます。

void
phys_mem_buddy_add_region(phys_memory_buddy_page_t **buddy, uintptr_t base,
                          uintptr_t next)
{
    _add_region_order(buddy, MEMORY_PHYS_BUDDY_ORDER, base, next);
}

第一引数のbuddyはバディシステムの最大オーダー+1個のポインタの配列です。コアゾーンについては,src/kernel/memory.hに以下のように構造体のメンバとして定義しています。

#define MEMORY_ZONE_CORE_NUM            2
    phys_memory_buddy_page_t *heads[MEMORY_PHYS_BUDDY_ORDER + 1];

関数phys_mem_buddy_add_regions()から今回のバディシステムでサポートする最大のブロック長1 GiB(オーダー=18)に,追加するメモリ領域が当てはまるかどうかを確認し,当てはまらなければ一段下のオーダーを再帰的に確認するコードが以下の部分です。

static void
_add_region_order(phys_memory_buddy_page_t **buddy, int order,
                  uintptr_t base, uintptr_t next)
{
    uint64_t blocksize;
    uintptr_t base_aligned;
    uintptr_t next_aligned;
    uintptr_t ptr;
    int i;
    int nr;

    /* May happen if base or next is not 4 KiB-alinged */
    if ( order < 0 ) {
        return;
    }

    /* The size of the block for this order */
    blocksize = (1ULL << order) * MEMORY_PAGESIZE;

    /* Align the base and next addresses */
    base_aligned = (base + (blocksize - 1)) & ~(blocksize - 1);
    next_aligned = next & ~(blocksize - 1);

    /* Add smaller chunk to the lower order of the buddy system */
    if ( base != base_aligned ) {
        ptr = base_aligned < next ? base_aligned : next;
        _add_region_order(buddy, order - 1, base, ptr);
    }
    if ( next != next_aligned && next_aligned >= base ) {
        ptr = next_aligned > base ? next_aligned : base;
        _add_region_order(buddy, order - 1, ptr, next);
    }

    /* Add pages to this zone */
    nr = ((next_aligned - base_aligned) >> order) / MEMORY_PAGESIZE;
    for ( i = 0; i < nr; i++ ) {
        _add_block(buddy, order, base_aligned + i * blocksize);
    }
}

ブロック長でアライメントを取り,そのアライメントにはまらなかった領域を一段下のオーダーで再確認します。このオーダーでアライメントの取れる領域は,ブロックとして追加します。以下のコードは,オーダーorderの連結リストをアドレス順でのソートを保ったまま,addrのブロックを連結リストの適切な場所に挿入するロジックです。

static void
_add_block(phys_memory_buddy_page_t **buddy, int order, uintptr_t addr)
{
    phys_memory_buddy_page_t *page;
    phys_memory_buddy_page_t **cur;

    page = (phys_memory_buddy_page_t *)addr;
    page->next = NULL;

    /* Keep the linked list sorted */
    cur = &buddy[order];
    while ( *cur ) {
        if ( addr < (uint64_t)(*cur) ) {
            /* Insert here */
            page->next = *cur;
            *cur = page;
            return;
        }

        cur = &(*cur)->next;
    }

    /* If reaches at the end of the list */
    *cur = page;
}

まとめと明日の予定

今日はバディーシステムの初期化を実装しました。明日はバディシステムから物理メモリの割り当てと解放を実装しようと思います。