vm.c pde_t* setupkvm(void)
トップページ
jupiteroak.hatenablog.com
vm.c
https://github.com/mit-pdos/xv6-public/blob/master/vm.c#L118
pde_t* setupkvm(void) { pde_t *pgdir; struct kmap *k; if((pgdir = (pde_t*)kalloc()) == 0) return 0; memset(pgdir, 0, PGSIZE); if (P2V(PHYSTOP) > (void*)DEVSPACE) panic("PHYSTOP too high"); for(k = kmap; k < &kmap[NELEM(kmap)]; k++) if(mappages(pgdir, k->virt, k->phys_end - k->phys_start, (uint)k->phys_start, k->perm) < 0) { freevm(pgdir); return 0; } return pgdir; }
setupkvm関数は、カーネル空間のマッピングのみが設定されたページディレクトリを作成します。
戻り値 pde_t *pgdir または 0
ページディレクトリの作成に成功した場合は、そのページディレクトリのアドレスです。
ページディレクトリの作成に失敗した場合は、0が戻り値となります。
処理の内容
- ページディレクトリとして使用する4KBのメモリ領域を割り当てる
- ページディレクトリとして使用する4KBのメモリ領域を初期化する
- xv6が利用できる物理アドレスの上限値がメモリマップドI/Oのアドレス範囲に及んでいないことを確認する。
- ページディレクトリにカーネル空間のマッピングを設定する
- ページディレクトリの先頭アドレスを戻り値としてリターンする
ページディレクトリとして使用する4KBのメモリ領域を割り当てる
if((pgdir = (pde_t*)kalloc()) == 0) return 0;
kalloc関数を呼び出して、ページディレクトリとして使用する4KBのメモリ領域を割り当てます。
メモリ領域の割り当てに成功した場合は、そのメモリ領域の先頭アドレスをpgdirに保存します。
メモリ領域の割り当てに失敗した場合は、0を戻り値として処理を終了します。
ページディレクトリとして使用する4KBのメモリ領域を初期化する
memset(pgdir, 0, PGSIZE);
memset関数を呼び出して、ページディレクトリとして使用する4KB(#define PGSIZE 4096)のメモリ領域を0で初期化します。
xv6が利用できる物理アドレスの上限値がメモリマップドI/Oのアドレス範囲に及んでいないことを確認する。
if (P2V(PHYSTOP) > (void*)DEVSPACE) panic("PHYSTOP too high");
PHYSTOP(0xE00 0000)はxv6が利用することができる物理アドレスの上限値です。DEVSPACE(#define DEVSPACE 0xFE000000)はメモリマップドI/Oで利用されるアドレス範囲の先頭アドレスです。
P2Vマクロを使って、物理アドレスPHYSTOPを仮想アドレスP2V(PHYSTOP)に変換してから、DEVSPACEと比較します。
xx6のメモリレイアウトでは、メモリマップドI/Oで利用されるアドレス範囲については、仮想アドレスと物理アドレスが同じアドレス値になるようマッピングされています。P2V(PHYSTOP)がDEVSPACEを越えている場合→xv6が利用できる物理アドレスの上限値がメモリマップドI/Oのアドレス範囲に及んでいる場合は、panic関数を呼び出してメッセージを出力します。
ページディレクトリにカーネル空間のマッピングを設定する
for(k = kmap; k < &kmap[NELEM(kmap)]; k++) if(mappages(pgdir, k->virt, k->phys_end - k->phys_start, (uint)k->phys_start, k->perm) < 0) { freevm(pgdir); return 0; }
kmap構造体の配列とmappages関数を使って、ページディレクトリにカーネル空間のマッピングを設定します。
kmap構造体について
static struct kmap { void *virt; uint phys_start; uint phys_end; int perm; } kmap[] = { { (void*)KERNBASE, 0, EXTMEM, PTE_W}, // I/O space { (void*)KERNLINK, V2P(KERNLINK), V2P(data), 0}, // kern text+rodata { (void*)data, V2P(data), PHYSTOP, PTE_W}, // kern data+memory { (void*)DEVSPACE, DEVSPACE, 0, PTE_W}, // more devices };
kmap構造体は、カーネル空間にマッピングさせるメモリ領域についての情報を格納したデータ構造です。
virtメンバはマッピング対象となる仮想アドレスの範囲における先頭アドレス、phy_startメンバはマッピング対象となる物理アドレスの範囲(メモリ領域)における先頭アドレス、phy_endメンバはマッピング対象となる物理アドレスの範囲(メモリ領域)における終端アドレス、permメンバはマッピングの際に使用するPTE(ページテーブルエントリ)のフラグ・フィールドに設定するパラメータです。
カーネル空間にマッピングさせるメモリ領域は、BIOS領域、カーネルプログラムのtext・rodata領域、カーネルプログラムのdata・メモリ管理領域、メモリマップドI/Oで利用されるメモリ領域、の4つに分類されており、それぞれがkmap[0]、kmap[1]、kmap[2]、kmap[3]に対応しています。
virt | phys_start | phys_end | perm | |
---|---|---|---|---|
kmap[0] | 0x8000 0000(KERNBASE) | 0x0000 0000 | 0x0010 0000(EXTMEM) | PTE_W→0x002 |
kmap[1] | 0x8010 0000(KERNLINK) | 0x0010 0000(V2P(KERNLINK)) | 0x0010 7000(V2P(data)) | 0 |
kmap[2] | 0x8010 7000(data) | 0x0010 7000(V2P(data)) | 0x0E00 0000( PHYSTOP) | PTE_W→0x002 |
kmap[3] | 0xFE00 0000(DEVSPACE) | 0xFE00 0000(DEVSPACE) | 0x0000 0000 | PTE_W→0x002 |
mappages(pgdir, k->virt, k->phys_end - k->phys_start, (uint)k->phys_start, k->perm)について
mappages関数を呼び出して、カーネル空間と4つのメモリ領域のマッピングを設定します。
各ループで、mappages関数に渡す引数は以下のようになります。
k->virt | k->phys_end - k->phys_start | k->phys_start | k->perm | |
---|---|---|---|---|
ループ1 | KERNBASE→0x8000 0000 | 0x0010 0000 | 0x0000 0000 | PTE_W→0x002 |
ループ2 | KERNLINK→0x8010 0000 | 0x0000 7000 | V2P(KERNLINK)→0x0010 0000 | 0 |
ループ3 | data→0x8010 7000 | 0x0DEF 9000 | V2P(data)→0x0010 7000 | PTE_W→0x002 |
ループ4 | DEVSPACE→0xFE00 0000 | 0x0200 0000 | DEVSPACE→0xFE00 0000 | PTE_W→0x002 |
各ループでマッピングを設定した結果、カーネル空間における仮想アドレスと物理アドレスの対応関係は以下のようになります。
仮想アドレス | 物理アドレス | |
---|---|---|
0x8000 0000~0x800F FFFF | 0x0000 0000~0x000F FFFF | |
0x8010 0000~0x8010 6FFF | 0x0010 0000~0x0010 6FFF | |
0x8010 7000~0x8DFF FFFF | 0x0010 7000~0x0DFF FFFF | |
0xFE00 0000~0xFFFF FFFF | 0xFE00 0000~0xFFFF FFFF |