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のメモリ領域を割り当てる

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

マッピングに失敗した場合は、メモリ領域を解放して処理を終了する

mappages関数の戻り値が-1になる場合→マッピングに失敗した場合は、freevm関数を呼び出して、ユーザー空間に関わる、ページディレクトリのメモリ領域、全てのページテーブルのメモリ領域、全てのページフレーム(4KBのメモリ領域)を解放します。
最後に、0を戻り値として処理を終了します。

ページディレクトリの先頭アドレスを戻り値としてリターンする

return pgdir;

カーネル空間のマッピングのみが設定されたページディレクトリのアドレスを戻り値としてリターンします。