Week 7.a CS6640 02/17 2026 https://naizhengtan.github.io/26spring/ Virtual memory implementation □ 1. Today's virtual memory □ 2. RV32 page table intro □ 3. (manually) walking page table --- 1. Today's virtual memory - What we have today: [virtual memory -> paging -> page table -> RV32] introducing the alternatives: * VA but not paging: segmentation * paging but not page table: hashing-based translation * page table but not RISC-V: x86 and ARM - how is this translation implemented? --software(OS)-hardware(MMU) co-design --in modern systems, hardware does it. this hardware is configured by the OS. --this hardware is called the MMU, for memory management unit, and is part of the CPU --why doesn't OS just translate itself? similar to asking why we don't execute programs by running them on an emulation of a processor (too slow) - things to remember in what follows: --OS is going to be setting up data structures that the hardware sees --these data structures are *per-process* PTs 2. RV32 page table intro [show overview fig] * a toy example: VA: 0x803fffec * split it into L1/L2 index and offset: 0x803fffec => - l1: 1000 0000 00 => 0x200 => 2*16*16=512 - l2: 0x3ff => 1023 - offset: 0xfec detail info: (1) satp [handout] (2) PTE [handout] Q: what if a PTE has W but not R? [undefined behavior] (3) VA [handout] 3. walking page table * a real example take a look at helloworld.c int main(int args, char **argv) { uint data = 0xdeadbeef; printf("%p\n", &data); asm("ecall"); } * run it; it returns 0x803fffec // syscall failed (ecall) // because we didn't set up syscall struct Q: what do you think gdb> p/x *0x803fffec [A: 0] Q: Why? We're in M-mode. Only see physical memory. * simulate CPU: manual page walk Goal: see which physical address holds data "0xdeadbeef". Method: we will use gdb to simulate page walk. Steps: (1) split the VA to l1/l2 indexes and offest (2) get the root of page table (3) calc the L1 page (4) calc the L2 page (5) calc the physical address * now, let's do it: (1) split the va to l1/l2 indexes and offest 0x803fffec => l1: 0x200 (512) l2: 0x3ff offset: 0xfec (2) get the root of page table gdb> p/x $satp // 0x8008040b (3) find the L1 page: Q: how to interpret 0x8008040b? [the most significant bit is MODE] Q: what is the L1 page? gdb> p/x 0x8040b << 12 // 0x8040b000 Q: is this physial or virtual? [physical] Q: what is the L1 PTE address? gdb> p/x (0x8040b << 12) + 0x200*4 // 0x200 is the L1 index in VA // 0x8040b800 Q: what is the L1 PTE content? gdb> p/x *((0x8040b << 12) + 0x200*4) // get 0x20103401 (L1 PTE) Q: what does "0x1" in the end mean? [it is the valid bit] (4) find the L2 page: Q: what's the L2 page? gdb> p/x (0x20103401 & ~(0x3ff)) >> 10 << 12 // 0x8040d000 Q: what's the L2 PTE address? gdb> p/x 0x8040d000 + 0x3ff*4 // 0x1 is the L2 index in VA Q: what's the L2 PTE content? gdb> p/x *(0x8040d000 + 0x3ff*4) // 0x201060df (5) find the physical address Q: what is "0xdf" in L2 PTE? [check out PTE fig] Q: what is the data page? gdb> p/x (0x201060df & ~(0x3ff)) << 2 // 0x80418000 Q: what is the PA? gdb> p/x ((0x201060df & ~(0x3ff)) << 2) + 0xfec // 0x80418fec gdb> p/x *0x80418fec // you will see 0xdeadbeef (the data) when inferencing the address