FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RISC-Vのメモリ仮想化の方式について

RISC-Vのテストパタン(riscv-tests)をパスさせるにあたり、RISC-Vの持つ仮想化の方式について勉強する必要が生じた。

これを機に、RISC-Vの仮想化の方式についてまとめてみよう。

RISC-Vの持つ仮想化のモード

RISC-Vには、オペレーションモードに応じていくつかの仮想化方式がある。 これらは、Privileged Architecture マニュアルに説明がされている。

http://riscv.org/spec/riscv-compressed-spec-v1.9.pdf

表3.3がRISC-Vの持つアドレス仮想化方式の一覧だ。

f:id:msyksphinz:20160115020151p:plain

この中で、今回riscv-testsのVMモードで主に利用されているのは、Sv32とSv39のようだ。これらについてまとめてみよう。

RISC-Vはvmの立ち上げで何が置きているのか

riscv-testsのvm.cを見てみよう。vm_bootという関数が記述されている。

void vm_boot(long test_addr, long seed)
{
...
  // map kernel to uppermost megapage
  l1pt[PTES_PER_PT-1] = ((pte_t)kernel_l2pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V | PTE_TYPE_TABLE;
  kernel_l2pt[PTES_PER_PT-1] = ((pte_t)kernel_l3pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V | PTE_TYPE_TABLE;
  // map user to lowermost megapage
  l1pt[0] = ((pte_t)user_l2pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V | PTE_TYPE_TABLE;
  user_l2pt[0] = ((pte_t)user_l3pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V | PTE_TYPE_TABLE;
  write_csr(sptbr, l1pt);
...
  // virtual memory off; set user mode upon eret
  clear_csr(mstatus, MSTATUS_VM | MSTATUS_PRV1);
  // virtual memory to Sv39
  set_csr(mstatus, (long)VM_SV39 << __builtin_ctzl(MSTATUS_VM));
...

l1ptは仮想化テーブルのベースアドレスになっており、このベースアドレスはsptbrレジスタに格納されている。 このテストベンチでは、l1ptは0xa000となっており、sptbrが0xa000に設定されている。 また、mstatusのVMビットをSV39に設定することで、仮想アドレスから物理アドレスへの変換がSV39の方式で実行されることが分かる。

また、物理アドレスと仮想アドレスの変換は、以下のpa2kvmで定義されているようだ。

#define pa2kva(pa) ((void*)(pa) - MEGAPAGE_SIZE)

では、次にSV39、SV32などの変換方式がどのようになっているのかを確認していこう。

変換の概要は下記の図のようになっている。これはSv32の変換方式だが、Sv39では、この変換のレベルが一つ上がったり、変換するアドレスの幅が変わっているだけで、基本的なところは同一になっている。

f:id:msyksphinz:20160115021935p:plain

  1. i=LEVELS-1 (LEVEL=2)からスタートする。つまり、上位の変換ブロックから変換を始める。
  2. va.vpn[i]×PTSIZE(=4)+sptbr のアドレスを計算し(これが変換テーブルのアドレスとなる。つまり、va.vpn[i]をインデックスとして変換テーブルを参照している)、ページテーブルエントリ(=pte)を参照する。
  3. pte.Vビットを確認し、0であれば無効テーブルエントリなのでアドレスエラーを出力する。
  4. pte.type が2以上であれば、テーブルの参照の終端まで行ったことになるので、ステップ5に進む。ここでpte.typeの意味は以下のように定義されているようだ (Privileged Reference Manual参照)。

f:id:msyksphinz:20160115022437p:plain

つまり、Type=0 or 1であれば次のテーブルへの参照となるが、それ以外は最終的なページテーブルとなる。 ここでテーブルのアクセスエラーを確認する。 Type=0,1のときは次のアドレスを計算する。a=pte.ppn×PAGESIZEとし(つまりベースアドレスを更新する)、再度ステップ2に進む。また、i<0となるとこれもテーブル不良のためアドレスエラーとなる。

  1. ステップ5まで来たといことは、i>=0の間にページテーブルの終端に到達したことを意味する。ここでpte.typeのアクセス権限を確認し、アクセス権限が足りなければエラーとなる。 そうでなければ、以下の方針でアドレス変換を実行する。

  2. オフセットはそのまま利用する。 pa.pgoff = va.pgoff

  3. i>0(=つまり、インデックスの参照を0まで使い切らない)場合はi-1から0まで(つまり到達しなかったレベル)は、仮想アドレスをそのまま物理アドレスとして利用する。 pa.ppn[i-1:0]=va.vpn[i-1:0]。
  4. それ以外のインデックスはページテーブルを参照して変換する。pa.ppn[LEVELS-1:i] = pte.ppn[LEVELS-1:i]

さて、次はこれを実装していこう。そしていよいよテストパタンを通すぞ!