Memory Layout and Page Table Management
Hardware Device Mapping
Hardware devices like UART0 (0x10000000L), VIRTIO0, CLINT, and PLIC have fixed physical addresses and interrupt numbers defined by hardware design. The kernel uses these addresses to communicate with devices, initialize registers, and handle interrupts.
Kernel Loading and Execution
Kernel code loads at 0x80000000 in RISC-V architecture to avoid conflicts with low-address hardware devices. KERNBASE and PHYSTOP define the physical memory range for kernel operations.
Memory Isolation and Protection
TRAMPOLINE and TRAPFRAME pages handle kernel-user space transitions. The KSTACK macro calculates kernel stack addresses with guard pages between stacks:
#define KSTACK(p) (TRAMPOLINE - ((p)+1)*2*PGSIZE)
This layout ensures security by preventing user programs from accessing kernel memory.
Virtual Memory Management
Key functions in vm.c manage user process memory:
uvmcreate(): Creates new user page tablesuvmalloc(): Allocates user process memoryuvmdealloc(): Releases user process memoryuvmcopy(): Copies user page tables and physical memory
Physical Memory Allocation
kalloc() and kfree() manage phyiscal memory without modifying page tables:
// Allocation returns physical page pointer
void* page = kalloc();
// Must manually map to virtual address
// Release requires prior unmapping
kfree(page);
Implementing vmprint() for Page Table Visualization
Function Declaration
Add to kernel/defs.h:
void vmprint(pagetable_t pagetable);
Recursive Page Table Printing
Modified freewalk function in kernel/vm.c:
void vmprint(pagetable_t pagetable) {
for(int i = 0; i < 512; i++) {
pte_t pte = pagetable[i];
if(pte & PTE_V) {
printf("%d: pte %p pa %p\n", i, pte, PTE2PA(pte));
if((pte & (PTE_R|PTE_W|PTE_X)) == 0) {
uint64 child = PTE2PA(pte);
vmprint((pagetable_t)child);
}
}
}
}
Integration in Exec
Add to kernel/exec.c before return argc:
if(p->pid == 1) vmprint(p->pagetable);
Process-Specific Kernel Page Tables
Data Structure Modification
Add to struct proc in proc.h:
pagetable_t kernelpgtbl;
Process Initialization Changes
Modify procinit() to remove pre-allocation:
void procinit(void) {
struct proc *p;
initlock(&pid_lock, "nextpid");
for(p = proc; p < &proc[NPROC]; p++) {
initlock(&p->lock, "proc");
}
kvminithart();
}
Enhanced Mapping Function
Updated mappages() handles custom kernel page tables:
int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) {
uint64 a = PGROUNDDOWN(va);
uint64 last = PGROUNDDOWN(va + size - 1);
for(;;) {
pte_t *pte = walk(pagetable, a, 1);
if(!pte) return -1;
if(*pte & PTE_V) panic("remap");
*pte = PA2PTE(pa) | perm | PTE_V;
if(a == last) break;
a += PGSIZE;
pa += PGSIZE;
}
return 0;
}
Process Context Switching
Modify scheduler() in proc.c:
// Switch to process kernel page table
w_satp(MAKE_SATP(p->kernelpgtbl));
sfence_vma();
// Execute process
swtch(&c->context, &p->context);
// Return to global kernel page table
kvminithart();
Kernel Page Table Cleanup
Recursive freeing function in vm.c:
void kvm_free_kernelpgtbl(pagetable_t pagetable) {
for(int i = 0; i < 512; i++) {
pte_t pte = pagetable[i];
if(pte & PTE_V && (pte & (PTE_R|PTE_W|PTE_X)) == 0) {
kvm_free_kernelpgtbl((pagetable_t)PTE2PA(pte));
pagetable[i] = 0;
}
}
kfree((void*)pagetable);
}
Optimizing copyin/copyinstr Operations
Mapping Utility Functions
Implement in kernel/vm.c:
int kvmcopymappings(pagetable_t src, pagetable_t dst, uint64 start, uint64 sz);
uint64 kvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz);
Modified Kernel Page Table Mapping
Remove CLINT mapping from process kernel page tables:
void kvm_map_pagetable(pagetable_t pgtbl) {
kvmmap(pgtbl, UART0, UART0, PGSIZE, PTE_R | PTE_W);
kvmmap(pgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);
kvmmap(pgtbl, PLIC, PLIC, 0x400000, PTE_R | PTE_W);
}
Memory Boundary Check
Add to kernel/exec.c:
int exec(char *path, char **argv) {
// Load program into memory
for(i = 0, off = elf.phoff; i < elf.phnum; i++, off += sizeof(ph)) {
if(sz1 >= PLIC) goto bad;
}
}
Synchronizing Mappings Across Operations
Fork operation synchronization:
int fork(void) {
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0 ||
kvmcopymappings(np->pagetable, np->kernelpgtbl, 0, p->sz) < 0) {
freeproc(np);
return -1;
}
}
Exec operation synchronization:
int exec(char *path, char **argv) {
uvmunmap(p->kernelpgtbl, 0, PGROUNDUP(oldsz)/PGSIZE, 0);
kvmcopymappings(pagetable, p->kernelpgtbl, 0, sz);
}
Memory growth synchronization:
int growproc(int n) {
if(n > 0) {
if(kvmcopymappings(p->pagetable, p->kernelpgtbl, p->sz, n) != 0) {
uvmdealloc(p->pagetable, newsz, p->sz);
return -1;
}
} else if(n < 0) {
sz = kvmdealloc(p->kernelpgtbl, p->sz, p->sz + n);
}
}
Function Replacement
Replace copyin/copyinstr implementations in vm.c:
int copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len) {
return copyin_new(pagetable, dst, srcva, len);
}
int copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max) {
return copyinstr_new(pagetable, dst, srcva, max);
}
RISC-V Address Translation Mechanism
Virtual addresses use three-level page tables with 9-bit indices for each level and 12-bit page offset. The translation process:
- Address decomposition: 27 bits for page table indices, 12 bits for page offset
- Page table traversal:
- Level 1 index finds level 2 page table
- Level 2 index finds level 3 page table
- Level 3 index finds physical page base
- Physical address generation: Physical base + page offset
- Hardware assistance: MMU performs translation automatically, triggering page faults for invalid mappings