これが、rv64ui-v-simpleだとどのように使われているのかな?
rv64ui-v-simpleでは、ユーザコードに移る際に Instruction Page Falut が起きているようだ。
この時にジャンプする先はstvecに設定されている。
void vm_boot(uintptr_t test_addr) { /* ... 途中省略 ... */ // set up supervisor trap handling write_csr(stvec, pa2kva(trap_entry));
このvm_boot()はMachine Modeで実行されるので、trap_entryは物理アドレスで表現されているので、これを仮想アドレスに変換して設定しておく。
#define pa2kva(pa) ((void*)(pa) - DRAM_BASE - MEGAPAGE_SIZE)
これは、例えばtrap_entryが物理アドレス0x8000_0144の場合、0x8000_0144 - 0x8000_0000 - 0x20_0000 = 0xffffffffffe00144 となっている。これがカーネル側から見た場合の仮想アドレスとなっている。
計算のからくりとしては、「物理アドレス - DRAMの先頭アドレス」でDRAM中のどこに存在しているのかを識別して、MEGAPAGE_SIZE (PTES_PER_PT * PGSIZEつまり1レベルで用意されるページのサイズ) で引き算することにより、ページテーブルの一番上にアクセスされるようにページが設定される。これがtrap_entryの仮想アドレスとなる。
実際にSRETの際にジャンプするのはユーザコードの存在する仮想アドレスで、これはSEPCに設定される。
test_addr - DRAM_BASEで設定されているので、例えばユーザコードの場所が 0x8000_2988 だと、0x8000_2988 - 0x8000_0000 = 0x2988としてSEPCが設定される。
void vm_boot(uintptr_t test_addr) /* ... 途中省略 ... */ trapframe_t tf; memset(&tf, 0, sizeof(tf)); tf.epc = test_addr - DRAM_BASE; pop_tf(&tf);
SRETによりユーザコードにジャンプすると、まず 0x2988 を物理アドレスに変換しようとするが、存在しないので例外が発生し、trap_entryに飛ぶ。
trap_entryはhandle_trapにジャンプし、例外の要因を判定してhandle_faultに飛ぶ。
handle_fault(addr)の引数は例外を引き起こした仮想アドレス 0x2988が設定される。
そして、ページの作成とページのコピーが行われる。
これは、uva2kva(addr)(ユーザモードでのaddrをカーネルモードでのaddrに変換したもの)から、addr(このアドレスはユーザモード表現)、にコピーしている。
ユーザモードでのaddrは既に上記でテーブルを作成済みなので、例外を起こすことなくコピーすることが可能という訳だ。
uintptr_t sstatus = set_csr(sstatus, SSTATUS_SUM); memcpy((void*)addr, uva2kva(addr), PGSIZE); write_csr(sstatus, sstatus);