Implementing Process-Specific Kernel Page Tables and Optimizing Copy Operations in xv6

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 tables
  • uvmalloc(): Allocates user process memory
  • uvmdealloc(): Releases user process memory
  • uvmcopy(): 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:

  1. Address decomposition: 27 bits for page table indices, 12 bits for page offset
  2. 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
  3. Physical address generation: Physical base + page offset
  4. Hardware assistance: MMU performs translation automatically, triggering page faults for invalid mappings

Tags: xv6 RISC-V kernel page tables virtual memory

Posted on Fri, 19 Jun 2026 17:44:33 +0000 by zhahaman2001