ハイパーバイザーの勉強をしている中で、2段ページテーブルの実装の中でそもそも私は普通のスーパーバイザーでのページテーブルの仕組みをしっかり理解できていないことに気が付いた。
ここでは、riscv_testsに実装されているページテーブルの実装と仮想アドレスを読みながら、その仕組みを理解していこうと思う。

vm_boot()での動作
// map user to lowermost megapage l1pt[0] = ((pte_t)user_l2pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V;
l1ptとl2ptというのが定義されているが、これはptの配列の1つの目と2つ目が指定されているらしい。
#define l1pt pt[0] #define user_l2pt pt[1] pte_t pt[NPT][PTES_PER_PT] __attribute__((aligned(PGSIZE))); #if __riscv_xlen == 64 # define NPT 4 #define kernel_l2pt pt[2] # define user_l3pt pt[3] #else # define NPT 2 # define user_l3pt user_l2pt #endif // riscv_test.h typedef unsigned long pte_t; #define LEVELS (sizeof(pte_t) == sizeof(uint64_t) ? 3 : 2) #define PTIDXBITS (PGSHIFT - (sizeof(pte_t) == 8 ? 3 : 2)) #define VPN_BITS (PTIDXBITS * LEVELS) #define VA_BITS (VPN_BITS + PGSHIFT) #define PTES_PER_PT (1UL << RISCV_PGLEVEL_BITS) #define MEGAPAGE_SIZE (PTES_PER_PT * PGSIZE)
RV64の場合は、
pte_t pt[NPT][PTES_PER_PT] __attribute__((aligned(PGSIZE))); unsigned long pt[4][1UL << 9] __attribute__((aligned(PGSIZE)));
という構成になる。ptが4つから構成されており、それぞれがVPNのサイズだけ定義されている。ptの1つのエントリに1つのPTEが格納されるという感じだろうか。ダンプしてみるとptは16KiBだけアサインされているので、1ブロックが4KiBで、512エントリ、ということは1エントリが8バイトということになる。
12: 0000000000000000 0 FILE LOCAL DEFAULT ABS vm.c 13: 0000000080002230 44 FUNC LOCAL DEFAULT 3 terminate 14: 000000008000218c 28 FUNC GLOBAL HIDDEN 3 strcpy 15: 00000000800021a8 136 FUNC GLOBAL HIDDEN 3 atol 16: 0000000080003000 16384 OBJECT GLOBAL HIDDEN 5 pt // 16KiBアサインされている 17: 0000000080002000 92 FUNC GLOBAL HIDDEN 3 memcpy 18: 0000000080007000 1008 OBJECT GLOBAL HIDDEN 5 freelist_nodes 19: 00000000800028d4 500 FUNC GLOBAL HIDDEN 3 vm_boot 20: 000000008000259c 824 FUNC GLOBAL HIDDEN 3 handle_trap
4ブロックがそれぞれ
l1ptuser_l2ptkernel_l2ptuser_l3pt
となっているが、今のところ意味が分からない。
という所でマップをもう一度読み直してみると、最初のマップは、user_l2ptへのポインタとなっているので、これは普通にジャンプするためのページテーブルとなっている。
// map user to lowermost megapage l1pt[0] = ((pte_t)user_l2pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V;
user_l2ptの先頭はuser_l3ptへのジャンプとなっている。
l1ptの最後のエントリは、kernel_l2ptのへのジャンプとなっている。
さらにkernel_l2ptの最後のエントリはDRAM_BASE(つまり0x80000000)へのテーブルとなっており、これはLeafテーブルとなっている。
#if __riscv_xlen == 64 l1pt[PTES_PER_PT-1] = ((pte_t)kernel_l2pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V; kernel_l2pt[PTES_PER_PT-1] = (DRAM_BASE/RISCV_PGSIZE << PTE_PPN_SHIFT) | PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D; user_l2pt[0] = ((pte_t)user_l3pt >> PGSHIFT << PTE_PPN_SHIFT) | PTE_V; uintptr_t vm_choice = SATP_MODE_SV39; #else
SATP(sptbr)にl1ptの先頭アドレスを格納されることで仮想アドレス変換の先頭アドレスとして使用されるようになる。
write_csr(sptbr, ((uintptr_t)l1pt >> PGSHIFT) | (vm_choice * (SATP_MODE & ~(SATP_MODE<<1))));

スーパーバイザー向けのトラップは以下のようになっていた。これはどういう計算だ?
// set up supervisor trap handling write_csr(stvec, pa2kva(trap_entry)); write_csr(sscratch, pa2kva(read_csr(mscratch))); write_csr(medeleg, (1 << CAUSE_USER_ECALL) | (1 << CAUSE_FETCH_PAGE_FAULT) | (1 << CAUSE_LOAD_PAGE_FAULT) | (1 << CAUSE_STORE_PAGE_FAULT)); // FPU on; accelerator on; allow supervisor access to user memory access write_csr(mstatus, MSTATUS_FS | MSTATUS_XS); write_csr(mie, 0);
#define pa2kva(pa) ((void*)(pa) - DRAM_BASE - MEGAPAGE_SIZE) #define MEGAPAGE_SIZE (PTES_PER_PT * PGSIZE)
trap_entryは0x0800000c4に配置されているので、0x800000c4 - 0x80000000 - 512 x 4KiB = 0x8000000c4 - (0x80000000 + 512 x 16KiB)となり、 0xFFFF_FFFF_FFE0_00C4となる。この計算の仕組みはどういうことだろう?0x8000_0000から512番目のページテーブルにページを配置するということかな?
次に、malloc()で割り当てるメモリマップを確保しているものと思われる。
freelist_head = pa2kva((void*)&freelist_nodes[0]); freelist_tail = pa2kva(&freelist_nodes[MAX_TEST_PAGES-1]); for (long i = 0; i < MAX_TEST_PAGES; i++) { freelist_nodes[i].addr = DRAM_BASE + (MAX_TEST_PAGES + random)*PGSIZE; freelist_nodes[i].next = pa2kva(&freelist_nodes[i+1]); random = LFSR_NEXT(random); } freelist_nodes[MAX_TEST_PAGES-1].next = 0;
最後にtrapframeにtest_addr - DRAM_BASEを設定している。test_addrはuserstartに設定されているので、0x80002ac8 - 0x80000000 = 0x2ac8が設定されてトラップフレームを経由してスーパーバイザーモードに移行する。
22: 000000008000225c 16 FUNC GLOBAL HIDDEN 3 wtf
23: 00000000800077e0 8 OBJECT GLOBAL HIDDEN 5 freelist_tail
24: 0000000080002ac8 0 NOTYPE GLOBAL DEFAULT 3 userstart
25: 00000000800077e8 8 OBJECT GLOBAL HIDDEN 5 freelist_head
26: 0000000080003000 0 NOTYPE GLOBAL DEFAULT 4 begin_signature