vm.c pde_t* copyuvm(pde_t *pgdir, uint sz)

トップページ
jupiteroak.hatenablog.com


vm.c
https://github.com/mit-pdos/xv6-public/blob/master/vm.c#L315

pde_t* copyuvm(pde_t *pgdir, uint sz)
{
  pde_t *d;
  pte_t *pte;
  uint pa, i, flags;
  char *mem;

  if((d = setupkvm()) == 0)
    return 0;
  for(i = 0; i < sz; i += PGSIZE){
    if((pte = walkpgdir(pgdir, (void *) i, 0)) == 0)
      panic("copyuvm: pte should exist");
    if(!(*pte & PTE_P))
      panic("copyuvm: page not present");
    pa = PTE_ADDR(*pte);
    flags = PTE_FLAGS(*pte);
    if((mem = kalloc()) == 0)
      goto bad;
    memmove(mem, (char*)P2V(pa), PGSIZE);
    if(mappages(d, (void*)i, PGSIZE, V2P(mem), flags) < 0) {
      kfree(mem);
      goto bad;
    }
  }
  return d;

bad:
  freevm(d);
  return 0;
}

copyuvm関数は、引数pgdirで指定されるページディレクトリから仮想アドレス0~0+sz-1に関連する設定をコピーし、新規にページディレクトリを作成します。

引数 pde_t *pgdir
コピー元となるページディレクトリの先頭アドレスです。

引数 uint sz
仮想アドレス範囲のサイズ(バイト単位)です。

戻り値 pde_t *d または 0
新規に作成したページディレクトリの先頭アドレスです。
ページディレクトリの作成に失敗した場合は、0が戻り値となります。


処理の内容

新規にページディレクトリを作成する

 if((d = setupkvm()) == 0)
    return 0;

setupkvm関数を呼び出して、カーネル空間のマッピングのみが設定されたページディレクトリを作成します。
ページディレクトリの作成に失敗した場合は、戻り値を0として処理を終了します。

仮想アドレス0~0+sz-1に対応するページディレクトリとページテーブルの設定を新規に作成したページディレクトリにコピーする

for(i = 0; i < sz; i += PGSIZE){
    if((pte = walkpgdir(pgdir, (void *) i, 0)) == 0)
      panic("copyuvm: pte should exist");
    if(!(*pte & PTE_P))
      panic("copyuvm: page not present");
    pa = PTE_ADDR(*pte);
    flags = PTE_FLAGS(*pte);
    if((mem = kalloc()) == 0)
      goto bad;
    memmove(mem, (char*)P2V(pa), PGSIZE);
    if(mappages(d, (void*)i, PGSIZE, V2P(mem), flags) < 0) {
      kfree(mem);
      goto bad;
    }
  }

引数pgdirで指定されたページディレクトリからPTE(ページテーブルエントリ)を取得する

 if((pte = walkpgdir(pgdir, (void *) i, 0)) == 0)
      panic("copyuvm: pte should exist");

walkpgdir関数を呼び出して、仮想アドレスiに対応しているpte(ページテーブルエントリ)を取得します。

取得したPTE(ページテーブルエントリ)がページテーブルを参照していることを確認する

if(!(*pte & PTE_P))
      panic("copyuvm: page not present");

pteをPTE_P(#define PTE_P 0x001)でマスク処理することにより、pteのbit0(ページテーブルエントリのPフラグ)を取り出しています。!(*pde & PTE_P)が真となる場合→pteのbit0(ページテーブルエントリのPフラグ)が0→pte(ページテーブルエントリ)が参照しているページフレームが存在しない場合は、panic関数を呼び出してメッセージを出力します。

取得したPTE(ページテーブルエントリ)からページフレームの先頭アドレスを取得する

pa = PTE_ADDR(*pte);

PTE_ADDRマクロを使って、pte(ページテーブルエントリ)からページフレームの先頭アドレスpaを取得します。

取得したPTE(ページテーブルエントリ)から各種のパラメータを取得する

 flags = PTE_FLAGS(*pte);

PTE_FLAGSマクロ絵お使って、pte(ページテーブルエントリ)に設定されている各種のパラメータ(Pフラグ、R/Wフラグ、U/Sフラグ、PWTフラグ、PCDフラグ、Aフラグ、Dフラグ)を取得します。

新規にページテーブルを作成する

 if((mem = kalloc()) == 0)
      goto bad;

kalloc関数を呼び出して、ページフレームとして使用する4KBのメモリ領域を割り当てます。
(mem = kalloc()) == 0 が真となる場合→メモリ領域の割り当てに失敗した場合は、badラベルへジャンプします。

ページテーブルの内容をコピーする

memmove(mem, (char*)P2V(pa), PGSIZE);

memmove関数を呼び出して、既存のページテーブル(先頭アドレスP2V(pa)・サイズPGSIZE(#define PGSIZE 4096))から、新規に作成されたページテーブル(先頭アドレスmem・サイズPGSIZEのメモリ領域)へ、内容をコピーします。

新規に作成されたページディレクトリに仮想アドレスi~i+PGSIZE-1と物理アドレスV2P(mem)~V2P(mem)+PGSIZE-1のマッピングを設定する

if(mappages(d, (void*)i, PGSIZE, V2P(mem), flags) < 0) {
      kfree(mem);
      goto bad;
}

mappages関数を呼び出して、新規に作成されたページディレクトリに、仮想アドレスi~i+PGSIZE-1と物理アドレスV2P(mem)~ V2P(mem)+PGSIZE-1 のマッピングを設定します。mappages(d, (void*)i, PGSIZE, V2P(mem), flags) < 0 が真となる場合→マッピングに失敗した場合は、kfree関数を呼び出してページフレーム(割り当てられていた4KBのメモリ領域)を解放します。最後に、badラベルへジャンプします。

新規に作成したページディレクトリの先頭アドレスを戻り値としてリターン

return d;

メモリ領域を解放する

bad:
  freevm(d);
  return 0;

badラベルへジャンプした場合は、freevm関数を呼び出して、ユーザー空間に関わる、ページディレクトリのメモリ領域、全てのページテーブルのメモリ領域、全てのページフレーム(4KBのメモリ領域)を解放します。最後に、0を戻り値として処理を終了します。