技術

[xv6 #12] Chapter 1 – The first process – Code: creating an address space

テキストの21〜22ページ

本文

main関数は、カーネルを実行するために必要とされるKERNBASEより上位への対応付けを持つページテーブルを、生成したり切り替えたりするために、kvmalloc関数を呼ぶ。
(kvmalloc関数のソースは前回参照)
kvmalloc関数の機能の大部分をsetupkvm関数が担っている。
setupkvm関数はまず、ページディレクトリを保持するためのメモリ領域のページを割り当てる。
そしたら、カーネルが必要とするkmap配列の中に記された変換をインストールするために、mappages関数を呼ぶ。その変換は、カーネルの命令とデータ、PHYSTOPまでの物理メモリ、実際のI/Oデバイス用のメモリ範囲を含む。
setupkvm関数は、ユーザメモリに関する対応付けはインストールしない。
ユーザメモリに関する対応付けは後で行われる。
(この段落で言及されている部分をvm.cから抜粋)

// This table defines the kernel's mappings, which are present in
// every process's page table.
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}, // kernel text+rodata
  { (void*) data,     V2P(data),     PHYSTOP,   PTE_W},  // kernel data, memory
  { (void*) DEVSPACE, DEVSPACE,      0,         PTE_W},  // more devices
};

// Set up kernel part of a page table.
pde_t*
setupkvm()
{
  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)
      return 0;
  return pgdir;
}

// Allocate one page table for the machine for the kernel address
// space for scheduler processes.
void
kvmalloc(void)
{
  kpgdir = setupkvm();
  switchkvm();
}

mappages関数は、仮想アドレスの範囲から物理アドレスに対応する範囲のための対応付けをページテーブルへインストールする。
対応付けられる仮想アドレス全てに対して、mappages関数はそのアドレスのPTEのアドレスを探すためにwalkpgdir関数を呼ぶ。
そして、適切な物理ページ番号を保持するようにするためにそのPTEを適切なパーミッション(PTE_WとPTE_U)で初期化し、そして準備完了ということでそのPTEにPTE_Pを設定する。
(この段落で言及されている部分をvm.cから抜粋)

// Return the address of the PTE in page table pgdir
// that corresponds to virtual address va.  If alloc!=0,
// create any required page table pages.
static pte_t *
walkpgdir(pde_t *pgdir, const void *va, int alloc)
{
  pde_t *pde;
  pte_t *pgtab;

  pde = &pgdir[PDX(va)];
  if(*pde & PTE_P){
    pgtab = (pte_t*)p2v(PTE_ADDR(*pde));
  } else {
    if(!alloc || (pgtab = (pte_t*)kalloc()) == 0)
      return 0;
    // Make sure all those PTE_P bits are zero.
    memset(pgtab, 0, PGSIZE);
    // The permissions here are overly generous, but they can
    // be further restricted by the permissions in the page table 
    // entries, if necessary.
    *pde = v2p(pgtab) | PTE_P | PTE_W | PTE_U;
  }
  return &pgtab[PTX(va)];
}

// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa. va and size might not
// be page-aligned.
static int
mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm)
{
  char *a, *last;
  pte_t *pte;
  
  a = (char*)PGROUNDDOWN((uint)va);
  last = (char*)PGROUNDDOWN(((uint)va) + size - 1);
  for(;;){
    if((pte = walkpgdir(pgdir, a, 1)) == 0)
      return -1;
    if(*pte & PTE_P)
      panic("remap");
    *pte = pa | perm | PTE_P;
    if(a == last)
      break;
    a += PGSIZE;
    pa += PGSIZE;
  }
  return 0;
}

walkpgdir関数は、仮想アドレス変換のためにx86のページングハードウェアがPTEを参照する動きをエミュレートする。(図1-1参照)
walkpgdirは、ページディレクトリエントリを見つけるために、仮想アドレスの上位10ビットを利用する。
(pde = &pgdir[PDX(va)]; の部分。マクロPDX()で上位10ビットを取得している。)
ページディレクトリエントリが準備出来ていなければ、要求されたページテーブルのページはまだ割り当てられていないということになる。
もし引数allocがセットされていれば、walkpgdir関数はそれを割り当て、そのページディレクトリの中の物理アドレスに配置する。
最後に、ページテーブルのページの中のPTEのアドレスを見つけるために、仮想アドレスの次の10ビットを利用する。
(return &pgtab[PTX(va)]; の部分。マクロPTX()で上位10ビットのさらに次の10ビットを取得している。)

図1-1 x86上のページテーブル(以前も載せましたがもう一回載せときます)

感想

xv6がアドレス空間をどう作ってるかですね。
ソースは普通のCなんで読みやすいですが、どういうメモリ操作をしてるのか具体的にイメージしながら読まないと説明との対応がよく分からないかと思います。

ここはまだ起動途中の話なのでそれを忘れないようにしないと。
(OSが起動した後、プロセスを起動するときなどに動く処理ではない)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です



※画像をクリックして別の画像を表示

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください