From 03f374319f682cc79cad5e5a4ec81d85ba36eef9 Mon Sep 17 00:00:00 2001 From: s-p-1 Date: Wed, 22 Apr 2026 23:23:05 +0530 Subject: [PATCH 1/3] p32: Add kernel page table setup and fix perl script line endings --- defs.h | 3 ++ main.c | 5 ++- mmu.h | 29 +++++++++++++++ vm.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ x86.h | 6 +++ 5 files changed, 156 insertions(+), 2 deletions(-) diff --git a/defs.h b/defs.h index 0c4bda7..9a857c4 100644 --- a/defs.h +++ b/defs.h @@ -160,6 +160,9 @@ void uartputc(int); // vm.c void seginit(void); void switchuvm(struct proc*); +pde_t* setupkvm(void); +void kvmalloc(void); +void switchkvm(void); // number of elements in fixed-size array #define NELEM(x) (sizeof(x)/sizeof((x)[0])) diff --git a/main.c b/main.c index 599e3f5..750b36b 100644 --- a/main.c +++ b/main.c @@ -17,12 +17,13 @@ int main(void) { kinit(end, P2V(PHYSTOP)); // phys page allocator + consoleinit(); // console hardware (moved up so early panics are visible) + uartinit(); // serial port (moved up so early panics are visible) + kvmalloc(); // kernel page table mpinit(); // detect other processors lapicinit(); // interrupt controller picinit(); // disable pic ioapicinit(); // another interrupt controller - consoleinit(); // console hardware - uartinit(); // serial port ideinit(); // disk tvinit(); // trap vectors binit(); // buffer cache diff --git a/mmu.h b/mmu.h index c829a22..01f4b63 100644 --- a/mmu.h +++ b/mmu.h @@ -143,3 +143,32 @@ struct gatedesc { } #endif + +// Page directory and page table constants. +#define NPDENTRIES 1024 // # directory entries per page directory +#define NPTENTRIES 1024 // # PTEs per page table + +#define PTXSHIFT 12 // offset of PTX in a linear address +#define PDXSHIFT 22 // offset of PDX in a linear address + +// page directory index +#define PDX(va) (((uint)(va) >> PDXSHIFT) & 0x3FF) + +// page table index +#define PTX(va) (((uint)(va) >> PTXSHIFT) & 0x3FF) + +#define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1)) + +// Page table/directory entry flags. +#define PTE_P 0x001 // Present +#define PTE_W 0x002 // Writeable +#define PTE_U 0x004 // User +#define PTE_PS 0x080 // Page Size + +// Address in page table or page directory entry +#define PTE_ADDR(pte) ((uint)(pte) & ~0xFFF) +#define PTE_FLAGS(pte) ((uint)(pte) & 0xFFF) + +#ifndef __ASSEMBLER__ +typedef uint pte_t; +#endif diff --git a/vm.c b/vm.c index caf87ae..53be2de 100644 --- a/vm.c +++ b/vm.c @@ -1,3 +1,4 @@ +#include "param.h" #include "types.h" #include "memlayout.h" #include "asm.h" @@ -5,6 +6,18 @@ #include "proc.h" #include "defs.h" #include "x86.h" +#include "elf.h" + +// x86 hardware page tables are 4KB-granular. The rest of this branch still +// uses the 1MB segmentation PGSIZE (see mmu.h) for process layout and kalloc, +// so override PGSIZE locally here for the page-table math. kalloc still +// returns 1MB chunks; we only use the first 4KB of each as a page +// directory / page table, which is wasteful but structurally correct. +#undef PGSIZE +#define PGSIZE 4096 + +extern char data[]; // defined by kernel.ld +pde_t *kpgdir; // for use in scheduler() // Set up CPU's kernel segment descriptors. // Run once on entry on each CPU. @@ -46,4 +59,106 @@ switchuvm(struct proc *p) mycpu()->ts.iomb = (ushort) 0xFFFF; ltr(SEG_TSS << 3); popcli(); +} + +// Return the address of the PTE in page table pgdir +// that corresponds to virtual address va. If alloc!=0, +// create any required page table pages. +static pte_t * +walkpgdir(pde_t *pgdir, const void *va, int alloc) +{ + pde_t *pde; + pte_t *pgtab; + + pde = &pgdir[PDX(va)]; + if(*pde & PTE_P){ + pgtab = (pte_t*)P2V(PTE_ADDR(*pde)); + } else { + if(!alloc || (pgtab = (pte_t*)kalloc()) == 0) + return 0; + // Make sure all those PTE_P bits are zero. + memset(pgtab, 0, PGSIZE); + // The permissions here are overly generous, but they can + // be further restricted by the permissions in the page table + // entries, if necessary. + *pde = V2P(pgtab) | PTE_P | PTE_W | PTE_U; + } + return &pgtab[PTX(va)]; +} + +// Create PTEs for virtual addresses starting at va that refer to +// physical addresses starting at pa. va and size might not +// be page-aligned. +static int +mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm) +{ + char *a, *last; + pte_t *pte; + + a = (char*)PGROUNDDOWN((uint)va); + last = (char*)PGROUNDDOWN(((uint)va) + size - 1); + for(;;){ + if((pte = walkpgdir(pgdir, a, 1)) == 0) + return -1; + if(*pte & PTE_P) + panic("remap"); + *pte = pa | perm | PTE_P; + if(a == last) + break; + a += PGSIZE; + pa += PGSIZE; + } + return 0; +} + +// This table defines the kernel's mappings, which are present in +// every process's page table. +static struct kmap { + void *virt; + uint phys_start; + uint phys_end; + int perm; +} kmap[] = { + { (void*)KERNBASE, 0, EXTMEM, PTE_W}, // I/O space + { (void*)KERNLINK, V2P(KERNLINK), V2P(data), 0}, // kern text+rodata + { (void*)data, V2P(data), PHYSTOP, PTE_W}, // kern data+memory + { (void*)DEVSPACE, DEVSPACE, 0, PTE_W}, // more devices +}; + +// Set up kernel part of a page table. +pde_t* +setupkvm(void) +{ + pde_t *pgdir; + struct kmap *k; + + if((pgdir = (pde_t*)kalloc()) == 0) + return 0; + memset(pgdir, 0, PGSIZE); + if (P2V(PHYSTOP) > (void*)DEVSPACE) + panic("PHYSTOP too high"); + for(k = kmap; k < &kmap[NELEM(kmap)]; k++) + if(mappages(pgdir, k->virt, k->phys_end - k->phys_start, + (uint)k->phys_start, k->perm) < 0) { + kfree((char*)pgdir); + return 0; + } + return pgdir; +} + +// Switch h/w page table register to the kernel-only page table, +// for when no process is running. +void +switchkvm(void) +{ + lcr3(V2P(kpgdir)); // switch to the kernel page table +} + +// Allocate one page table for the machine for the kernel address +// space for scheduler processes. +void +kvmalloc(void) +{ + kpgdir = setupkvm(); + switchkvm(); } \ No newline at end of file diff --git a/x86.h b/x86.h index ac85f1e..d1e4517 100644 --- a/x86.h +++ b/x86.h @@ -126,6 +126,12 @@ rcr2(void) return val; } +static inline void +lcr3(uint val) +{ + asm volatile("movl %0,%%cr3" : : "r" (val)); +} + static inline void noop(void) { From 2de5ce229ed2c0942ee7db56db3b6a035cff55f9 Mon Sep 17 00:00:00 2001 From: s-p-1 Date: Wed, 22 Apr 2026 23:42:22 +0530 Subject: [PATCH 2/3] p33: Add user-space virtual memory lifecycle utilities --- defs.h | 4 +++ mmu.h | 3 ++ proc.h | 1 + vm.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 105 insertions(+), 1 deletion(-) diff --git a/defs.h b/defs.h index 9a857c4..a18cadb 100644 --- a/defs.h +++ b/defs.h @@ -163,6 +163,10 @@ void switchuvm(struct proc*); pde_t* setupkvm(void); void kvmalloc(void); void switchkvm(void); +void inituvm(pde_t*, char*, uint); +int allocuvm(pde_t*, uint, uint); +int deallocuvm(pde_t*, uint, uint); +void freevm(pde_t*); // number of elements in fixed-size array #define NELEM(x) (sizeof(x)/sizeof((x)[0])) diff --git a/mmu.h b/mmu.h index 01f4b63..7e72acb 100644 --- a/mmu.h +++ b/mmu.h @@ -157,6 +157,9 @@ struct gatedesc { // page table index #define PTX(va) (((uint)(va) >> PTXSHIFT) & 0x3FF) +// construct virtual address from indexes and offset +#define PGADDR(d, t, o) ((uint)((d) << PDXSHIFT | (t) << PTXSHIFT | (o))) + #define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1)) // Page table/directory entry flags. diff --git a/proc.h b/proc.h index 39c70d2..54b1386 100644 --- a/proc.h +++ b/proc.h @@ -36,6 +36,7 @@ enum procstate { UNUSED, EMBRYO, RUNNABLE, RUNNING, SLEEPING, ZOMBIE }; // Per-process state struct proc { uint sz; // Size of process memory (bytes) + pde_t *pgdir; // Page table (unused on this branch; added for parity with xv6-public) char *offset; // Where code is copied char *kstack; // Bottom of kernel stack for this process enum procstate state; // Process state diff --git a/vm.c b/vm.c index 53be2de..9ae7fec 100644 --- a/vm.c +++ b/vm.c @@ -140,7 +140,7 @@ setupkvm(void) for(k = kmap; k < &kmap[NELEM(kmap)]; k++) if(mappages(pgdir, k->virt, k->phys_end - k->phys_start, (uint)k->phys_start, k->perm) < 0) { - kfree((char*)pgdir); + freevm(pgdir); return 0; } return pgdir; @@ -161,4 +161,100 @@ kvmalloc(void) { kpgdir = setupkvm(); switchkvm(); +} + +// Load the initcode into address 0 of pgdir. +// sz must be less than a page. +void +inituvm(pde_t *pgdir, char *init, uint sz) +{ + char *mem; + + if(sz >= PGSIZE) + panic("inituvm: more than a page"); + mem = kalloc(); + memset(mem, 0, PGSIZE); + mappages(pgdir, 0, PGSIZE, V2P(mem), PTE_W|PTE_U); + memmove(mem, init, sz); +} + +// Allocate page tables and physical memory to grow process from oldsz to +// newsz, which need not be page aligned. Returns new size or 0 on error. +int +allocuvm(pde_t *pgdir, uint oldsz, uint newsz) +{ + char *mem; + uint a; + + if(newsz >= KERNBASE) + return 0; + if(newsz < oldsz) + return oldsz; + + a = PGROUNDUP(oldsz); + for(; a < newsz; a += PGSIZE){ + mem = kalloc(); + if(mem == 0){ + cprintf("allocuvm out of memory\n"); + deallocuvm(pgdir, newsz, oldsz); + return 0; + } + memset(mem, 0, PGSIZE); + if(mappages(pgdir, (char*)a, PGSIZE, V2P(mem), PTE_W|PTE_U) < 0){ + cprintf("allocuvm out of memory (2)\n"); + deallocuvm(pgdir, newsz, oldsz); + kfree(mem); + return 0; + } + } + return newsz; +} + +// Deallocate user pages to bring the process size from oldsz to +// newsz. oldsz and newsz need not be page-aligned, nor does newsz +// need to be less than oldsz. oldsz can be larger than the actual +// process size. Returns the new process size. +int +deallocuvm(pde_t *pgdir, uint oldsz, uint newsz) +{ + pte_t *pte; + uint a, pa; + + if(newsz >= oldsz) + return oldsz; + + a = PGROUNDUP(newsz); + for(; a < oldsz; a += PGSIZE){ + pte = walkpgdir(pgdir, (char*)a, 0); + if(!pte) + a = PGADDR(PDX(a) + 1, 0, 0) - PGSIZE; + else if((*pte & PTE_P) != 0){ + pa = PTE_ADDR(*pte); + if(pa == 0) + panic("kfree"); + char *v = P2V(pa); + kfree(v); + *pte = 0; + } + } + return newsz; +} + +// Free a page table and all the physical memory pages +// in the user part. +void +freevm(pde_t *pgdir) +{ + uint i; + + if(pgdir == 0) + panic("freevm: no pgdir"); + deallocuvm(pgdir, KERNBASE, 0); + for(i = 0; i < NPDENTRIES; i++){ + if(pgdir[i] & PTE_P){ + char * v = P2V(PTE_ADDR(pgdir[i])); + kfree(v); + } + } + kfree((char*)pgdir); } \ No newline at end of file From 3fca84fab5cb5db3c1673d19d2edd96650c73a99 Mon Sep 17 00:00:00 2001 From: s-p-1 Date: Thu, 23 Apr 2026 00:11:43 +0530 Subject: [PATCH 3/3] p34: Add page table cloning and ELF loading utilities --- defs.h | 3 +++ proc.c | 2 +- vm.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/defs.h b/defs.h index a18cadb..b2be952 100644 --- a/defs.h +++ b/defs.h @@ -167,6 +167,9 @@ void inituvm(pde_t*, char*, uint); int allocuvm(pde_t*, uint, uint); int deallocuvm(pde_t*, uint, uint); void freevm(pde_t*); +pde_t* copyuvm(pde_t*, uint); +int loaduvm(pde_t*, char*, struct inode*, uint, uint); +void clearpteu(pde_t*, char*); // number of elements in fixed-size array #define NELEM(x) (sizeof(x)/sizeof((x)[0])) diff --git a/proc.c b/proc.c index aa468ee..1b6537e 100644 --- a/proc.c +++ b/proc.c @@ -107,7 +107,7 @@ pinit(void) extern char _binary_initcode_start[], _binary_initcode_size[]; p = allocproc(); - + initproc = p; memmove(p->offset, _binary_initcode_start, (int)_binary_initcode_size); diff --git a/vm.c b/vm.c index 9ae7fec..e2ead16 100644 --- a/vm.c +++ b/vm.c @@ -186,7 +186,10 @@ allocuvm(pde_t *pgdir, uint oldsz, uint newsz) char *mem; uint a; - if(newsz >= KERNBASE) + // TEMPORARY: KERNBASE is 0 on this branch while the kernel is still + // mapped at 0 in the segmentation model. Use PHYSTOP as the ceiling so + // the shadow page directories can actually grow. + if(newsz >= PHYSTOP) return 0; if(newsz < oldsz) return oldsz; @@ -257,4 +260,75 @@ freevm(pde_t *pgdir) } } kfree((char*)pgdir); +} + +// Given a parent process's page table, create a copy +// of it for a child. +pde_t* +copyuvm(pde_t *pgdir, uint sz) +{ + pde_t *d; + pte_t *pte; + uint pa, i, flags; + char *mem; + + if((d = setupkvm()) == 0) + return 0; + for(i = 0; i < sz; i += PGSIZE){ + if((pte = walkpgdir(pgdir, (void *) i, 0)) == 0) + panic("copyuvm: pte should exist"); + if(!(*pte & PTE_P)) + panic("copyuvm: page not present"); + pa = PTE_ADDR(*pte); + flags = PTE_FLAGS(*pte); + if((mem = kalloc()) == 0) + goto bad; + memmove(mem, (char*)P2V(pa), PGSIZE); + if(mappages(d, (void*)i, PGSIZE, V2P(mem), flags) < 0) { + kfree(mem); + goto bad; + } + } + return d; + +bad: + freevm(d); + return 0; +} + +// Load a program segment into pgdir. addr must be page-aligned +// and the pages from addr to addr+sz must already be mapped. +int +loaduvm(pde_t *pgdir, char *addr, struct inode *ip, uint offset, uint sz) +{ + uint i, pa, n; + pte_t *pte; + + if((uint) addr % PGSIZE != 0) + panic("loaduvm: addr must be page aligned"); + for(i = 0; i < sz; i += PGSIZE){ + if((pte = walkpgdir(pgdir, addr+i, 0)) == 0) + panic("loaduvm: address should exist"); + pa = PTE_ADDR(*pte); + if(sz - i < PGSIZE) + n = sz - i; + else + n = PGSIZE; + if(readi(ip, P2V(pa), offset+i, n) != n) + return -1; + } + return 0; +} + +// Clear PTE_U on a page. Used to create an inaccessible +// page beneath the user stack. +void +clearpteu(pde_t *pgdir, char *uva) +{ + pte_t *pte; + + pte = walkpgdir(pgdir, uva, 0); + if(pte == 0) + panic("clearpteu"); + *pte &= ~PTE_U; } \ No newline at end of file