diff --git a/xv6-os/ci/test/main.py b/xv6-os/ci/test/main.py index 1206764e..2fed239c 100644 --- a/xv6-os/ci/test/main.py +++ b/xv6-os/ci/test/main.py @@ -38,6 +38,9 @@ def read_header(qemu: Qemu): prefix = [qemu.readline() for _ in range(7)] assert_eq(prefix[2], "xv6 kernel is booting") assert_eq(prefix[3], "") + # ingenious skip of buddy allocator metadata + if "bd: memory sz" in prefix[4]: + prefix = prefix[0:4] + [qemu.readline() for _ in range(3)] assert prefix[4] in (f"hart {i + 1} starting" for i in range(2)) assert prefix[5] in (f"hart {i + 1} starting" for i in range(2)) assert_eq(prefix[6], "init: starting sh") diff --git a/xv6-os/kernel/buddy.c b/xv6-os/kernel/buddy.c index 8080234c..f18720da 100644 --- a/xv6-os/kernel/buddy.c +++ b/xv6-os/kernel/buddy.c @@ -5,6 +5,7 @@ #include "riscv.h" #include "defs.h" #include "list.h" +#include // Buddy allocator @@ -58,6 +59,15 @@ void bit_clear(char *array, int index) { array[index / 8] = (b & ~m); } +// Invert bit at position index in array which stands for xor'ed bits of pair blocks at (2 * index) and (2 * index + 1) positions +void bit_invert(char *array, int index) { + int b_idx = index / 2; // Every bit is xor'ed with the next one thus flag in + // the array has a bit index two times smaller + char b = array[b_idx / 8]; + char m = (1 << (b_idx % 8)); + array[b_idx / 8] = (b ^ m); +} + // Print a bit vector as a list of ranges of 1 bits void bd_print_vector(char *vector, int len) { int last, lb; @@ -132,13 +142,13 @@ void *bd_malloc(uint64 nbytes) { // Found a block; pop it and potentially split it. char *p = lst_pop(&bd_sizes[k].free); - bit_set(bd_sizes[k].alloc, blk_index(k, p)); + bit_invert(bd_sizes[k].alloc, blk_index(k, p)); for (; k > fk; k--) { // split a block at size k and mark one half allocated at size k-1 // and put the buddy on the free list at size k-1 char *q = p + BLK_SIZE(k - 1); // p's buddy bit_set(bd_sizes[k].split, blk_index(k, p)); - bit_set(bd_sizes[k - 1].alloc, blk_index(k - 1, p)); + bit_invert(bd_sizes[k - 1].alloc, blk_index(k - 1, p)); lst_push(&bd_sizes[k - 1].free, q); } release(&lock); @@ -166,8 +176,8 @@ void bd_free(void *p) { for (k = size(p); k < MAXSIZE; k++) { int bi = blk_index(k, p); int buddy = (bi % 2 == 0) ? bi + 1 : bi - 1; - bit_clear(bd_sizes[k].alloc, bi); // free p at size k - if (bit_isset(bd_sizes[k].alloc, buddy)) { // is buddy allocated? + bit_invert(bd_sizes[k].alloc, bi); // free p at size k + if (bit_isset(bd_sizes[k].alloc, buddy / 2)) { // is buddy allocated? break; // break out of loop } // budy is free; merge with buddy @@ -215,20 +225,21 @@ void bd_mark(void *start, void *stop) { // if a block is allocated at size k, mark it as split too. bit_set(bd_sizes[k].split, bi); } - bit_set(bd_sizes[k].alloc, bi); + bit_invert(bd_sizes[k].alloc, bi); } } } // If a block is marked as allocated and the buddy is free, put the // buddy on the free list at size k. -int bd_initfree_pair(int k, int bi) { +int bd_initfree_pair(int k, int bi, bool use_buddy) { int buddy = (bi % 2 == 0) ? bi + 1 : bi - 1; int free = 0; - if (bit_isset(bd_sizes[k].alloc, bi) != bit_isset(bd_sizes[k].alloc, buddy)) { + if (bit_isset(bd_sizes[k].alloc, bi / 2)) { // one of the pair is free free = BLK_SIZE(k); - if (bit_isset(bd_sizes[k].alloc, bi)) + // put buddy on free list, depending on the flag + if (use_buddy) lst_push(&bd_sizes[k].free, addr(k, buddy)); // put buddy on free list else lst_push(&bd_sizes[k].free, addr(k, bi)); // put bi on free list @@ -245,9 +256,9 @@ int bd_initfree(void *bd_left, void *bd_right) { for (int k = 0; k < MAXSIZE; k++) { // skip max size int left = blk_index_next(k, bd_left); int right = blk_index(k, bd_right); - free += bd_initfree_pair(k, left); + free += bd_initfree_pair(k, left, false); if (right <= left) continue; - free += bd_initfree_pair(k, right); + free += bd_initfree_pair(k, right, true); } return free; } @@ -297,7 +308,8 @@ void bd_init(void *base, void *end) { // initialize free list and allocate the alloc array for each size k for (int k = 0; k < nsizes; k++) { lst_init(&bd_sizes[k].free); - sz = sizeof(char) * ROUNDUP(NBLK(k), 8) / 8; + // optimisation takes 2 times less blocks + sz = sizeof(char) * ROUNDUP(NBLK(k) / 2, 8) / 8; bd_sizes[k].alloc = p; memset(bd_sizes[k].alloc, 0, sz); p += sz; diff --git a/xv6-os/kernel/defs.h b/xv6-os/kernel/defs.h index f947e244..0cf72934 100644 --- a/xv6-os/kernel/defs.h +++ b/xv6-os/kernel/defs.h @@ -87,7 +87,7 @@ int cpuid(void); void exit(int); int fork(void); int growproc(int); -void proc_mapstacks(pagetable_t); +// void proc_mapstacks(pagetable_t); pagetable_t proc_pagetable(struct proc *); void proc_freepagetable(pagetable_t, uint64); int kill(int); diff --git a/xv6-os/kernel/file.c b/xv6-os/kernel/file.c index 25fa2263..ab10040c 100644 --- a/xv6-os/kernel/file.c +++ b/xv6-os/kernel/file.c @@ -14,15 +14,15 @@ #include "proc.h" struct devsw devsw[NDEV]; -struct { - struct spinlock lock; - struct file file[NFILE]; -} ftable; +// struct { +// struct spinlock lock; +// struct file file[NFILE]; +// } ftable; void fileinit(void) { - initlock(&ftable.lock, "ftable"); + // initlock(&ftable.lock, "ftable"); } // Allocate a file structure. @@ -31,27 +31,24 @@ filealloc(void) { struct file *f; - acquire(&ftable.lock); - for(f = ftable.file; f < ftable.file + NFILE; f++){ - if(f->ref == 0){ - f->ref = 1; - release(&ftable.lock); - return f; - } + f = (struct file*) bd_malloc(sizeof(struct file)); + + if (f == 0) { + return 0; } - release(&ftable.lock); - return 0; + + memset(f, 0, sizeof(struct file)); + f->ref = 1; + return f; } // Increment ref count for file f. struct file* filedup(struct file *f) { - acquire(&ftable.lock); if(f->ref < 1) panic("filedup"); f->ref++; - release(&ftable.lock); return f; } @@ -59,27 +56,20 @@ filedup(struct file *f) void fileclose(struct file *f) { - struct file ff; - - acquire(&ftable.lock); if(f->ref < 1) panic("fileclose"); if(--f->ref > 0){ - release(&ftable.lock); return; } - ff = *f; - f->ref = 0; - f->type = FD_NONE; - release(&ftable.lock); - - if(ff.type == FD_PIPE){ - pipeclose(ff.pipe, ff.writable); - } else if(ff.type == FD_INODE || ff.type == FD_DEVICE){ + if(f->type == FD_PIPE){ + pipeclose(f->pipe, f->writable); + } else if(f->type == FD_INODE || f->type == FD_DEVICE){ begin_op(); - iput(ff.ip); + iput(f->ip); end_op(); } + + bd_free(f); } // Get metadata about file f. diff --git a/xv6-os/kernel/kalloc.c b/xv6-os/kernel/kalloc.c index 0699e7ee..0aa69433 100644 --- a/xv6-os/kernel/kalloc.c +++ b/xv6-os/kernel/kalloc.c @@ -9,34 +9,16 @@ #include "riscv.h" #include "defs.h" -void freerange(void *pa_start, void *pa_end); - extern char end[]; // first address after kernel. // defined by kernel.ld. -struct run { - struct run *next; -}; - -struct { - struct spinlock lock; - struct run *freelist; -} kmem; void kinit() { - initlock(&kmem.lock, "kmem"); - freerange(end, (void*)PHYSTOP); -} - -void -freerange(void *pa_start, void *pa_end) -{ - char *p; - p = (char*)PGROUNDUP((uint64)pa_start); - for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE) - kfree(p); + // Align `end` to the next page boundary + void *start = (char *)PGROUNDUP((uint64)end); + bd_init(start, (void *)PHYSTOP); } // Free the page of physical memory pointed at by pa, @@ -46,20 +28,7 @@ freerange(void *pa_start, void *pa_end) void kfree(void *pa) { - struct run *r; - - if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP) - panic("kfree"); - - // Fill with junk to catch dangling refs. - memset(pa, 1, PGSIZE); - - r = (struct run*)pa; - - acquire(&kmem.lock); - r->next = kmem.freelist; - kmem.freelist = r; - release(&kmem.lock); + bd_free(pa); } // Allocate one 4096-byte page of physical memory. @@ -68,15 +37,5 @@ kfree(void *pa) void * kalloc(void) { - struct run *r; - - acquire(&kmem.lock); - r = kmem.freelist; - if(r) - kmem.freelist = r->next; - release(&kmem.lock); - - if(r) - memset((char*)r, 5, PGSIZE); // fill with junk - return (void*)r; + return bd_malloc(PGSIZE); } diff --git a/xv6-os/kernel/proc.c b/xv6-os/kernel/proc.c index 130d9ce3..9fd23288 100644 --- a/xv6-os/kernel/proc.c +++ b/xv6-os/kernel/proc.c @@ -3,12 +3,23 @@ #include "memlayout.h" #include "riscv.h" #include "spinlock.h" +#include "list.h" #include "proc.h" #include "defs.h" +#define MAX_PRIORITY_DEPTH 5 + struct cpu cpus[NCPU]; -struct proc proc[NPROC]; +// list, where i will push procs dynamically to +struct list proc_list; + +// count procs to pass forkforkfork() test +int proc_count = 0; + +// new: lock for introduced proc list +// note: must be acquired after waitlock, but before any other locks. +struct spinlock proc_list_lock; struct proc *initproc; @@ -26,36 +37,16 @@ extern char trampoline[]; // trampoline.S // must be acquired before any p->lock. struct spinlock wait_lock; -// Allocate a page for each process's kernel stack. -// Map it high in memory, followed by an invalid -// guard page. -void -proc_mapstacks(pagetable_t kpgtbl) -{ - struct proc *p; - - for(p = proc; p < &proc[NPROC]; p++) { - char *pa = kalloc(); - if(pa == 0) - panic("kalloc"); - uint64 va = KSTACK((int) (p - proc)); - kvmmap(kpgtbl, va, (uint64)pa, PGSIZE, PTE_R | PTE_W); - } -} - // initialize the proc table. void procinit(void) { - struct proc *p; - initlock(&pid_lock, "nextpid"); initlock(&wait_lock, "wait_lock"); - for(p = proc; p < &proc[NPROC]; p++) { - initlock(&p->lock, "proc"); - p->state = UNUSED; - p->kstack = KSTACK((int) (p - proc)); - } + + // new: lock for proc list + lst_init(&proc_list); + initlock(&proc_list_lock, "proc_list_lock"); } // Must be called with interrupts disabled, @@ -102,6 +93,16 @@ allocpid() return pid; } +// new: remove proc from proc_list +// note: proc_list_lock must be held. +static void proc_remove(struct proc *p) { + if (lst_empty(&proc_list)) { + panic("proc_remove"); + } + lst_remove(&p->ple); + proc_count--; +} + // Look in the process table for an UNUSED proc. // If found, initialize state required to run in the kernel, // and return with p->lock held. @@ -109,43 +110,41 @@ allocpid() static struct proc* allocproc(void) { - struct proc *p; + // new: use bd_malloc for proc struct + struct proc *p = (struct proc *) bd_malloc(sizeof(struct proc)); + if (p == 0) + return 0; - for(p = proc; p < &proc[NPROC]; p++) { - acquire(&p->lock); - if(p->state == UNUSED) { - goto found; - } else { - release(&p->lock); - } - } - return 0; + memset(p, 0, sizeof(struct proc)); + + initlock(&p->lock, "proc"); + acquire(&p->lock); -found: p->pid = allocpid(); p->state = USED; + p->priority = 0; // set it to 0 for clarity, even tho memset does this fine - // Allocate a trapframe page. - if((p->trapframe = (struct trapframe *)kalloc()) == 0){ - freeproc(p); + // Allocate a stack, a trapframe page, + // and a page table + if ((p->kstack = (uint64) kalloc()) == 0 || + (p->trapframe = (struct trapframe *)kalloc()) == 0 || + (p->pagetable = proc_pagetable(p)) == 0) { release(&p->lock); - return 0; - } - - // An empty user page table. - p->pagetable = proc_pagetable(p); - if(p->pagetable == 0){ freeproc(p); - release(&p->lock); return 0; } // Set up new context to start executing at forkret, // which returns to user space. - memset(&p->context, 0, sizeof(p->context)); p->context.ra = (uint64)forkret; p->context.sp = p->kstack + PGSIZE; + // new: push proc to a proc list + acquire(&proc_list_lock); + lst_push(&proc_list, &p->ple); + proc_count++; + release(&proc_list_lock); + return p; } @@ -155,20 +154,16 @@ allocproc(void) static void freeproc(struct proc *p) { + // new: free proc's stack + if (p->kstack) + kfree((void*)p->kstack); if(p->trapframe) kfree((void*)p->trapframe); - p->trapframe = 0; if(p->pagetable) proc_freepagetable(p->pagetable, p->sz); - p->pagetable = 0; - p->sz = 0; - p->pid = 0; - p->parent = 0; - p->name[0] = 0; - p->chan = 0; - p->killed = 0; - p->xstate = 0; - p->state = UNUSED; + + // new: just free a proc with buddy allocator + bd_free(p); } // Create a user page table for a given process, with no user memory, @@ -279,6 +274,15 @@ growproc(int n) int fork(void) { + // new: forkforkfork test passage + // note: just a hard limit for a number of procs (NPROC = 64 currently) + acquire(&proc_list_lock); + if(proc_count + 1 >= NPROC){ + release(&proc_list_lock); + return -1; + } + release(&proc_list_lock); + int i, pid; struct proc *np; struct proc *p = myproc(); @@ -290,8 +294,13 @@ fork(void) // Copy user memory from parent to child. if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){ - freeproc(np); release(&np->lock); + + // new: remove proc from proc list + acquire(&proc_list_lock); + proc_remove(p); + release(&proc_list_lock); + freeproc(np); return -1; } np->sz = p->sz; @@ -330,14 +339,24 @@ fork(void) void reparent(struct proc *p) { + struct list *ple; struct proc *pp; + int need_wakeup = 0; - for(pp = proc; pp < &proc[NPROC]; pp++){ + acquire(&proc_list_lock); + // new: change parent for an abandoned proc + for(ple = proc_list.next; ple != &proc_list; ple = ple->next){ + pp = (struct proc *) ple; if(pp->parent == p){ pp->parent = initproc; - wakeup(initproc); + // note: can't wakeup while inside this lock + // feature, not a bug + need_wakeup = 1; } } + release(&proc_list_lock); + if (need_wakeup) + wakeup(initproc); } // Exit the current process. Does not return. @@ -390,6 +409,7 @@ exit(int status) int wait(uint64 addr) { + struct list *ple; struct proc *pp; int havekids, pid; struct proc *p = myproc(); @@ -399,7 +419,9 @@ wait(uint64 addr) for(;;){ // Scan through table looking for exited children. havekids = 0; - for(pp = proc; pp < &proc[NPROC]; pp++){ + acquire(&proc_list_lock); + for(ple = proc_list.next; ple != &proc_list; ple = ple->next){ + pp = (struct proc *)ple; if(pp->parent == p){ // make sure the child isn't still in exit() or swtch(). acquire(&pp->lock); @@ -411,17 +433,21 @@ wait(uint64 addr) if(addr != 0 && copyout(p->pagetable, addr, (char *)&pp->xstate, sizeof(pp->xstate)) < 0) { release(&pp->lock); + release(&proc_list_lock); release(&wait_lock); return -1; } - freeproc(pp); release(&pp->lock); + proc_remove(pp); + release(&proc_list_lock); release(&wait_lock); + freeproc(pp); return pid; } release(&pp->lock); } } + release(&proc_list_lock); // No point waiting if we don't have any children. if(!havekids || killed(p)){ @@ -444,6 +470,7 @@ wait(uint64 addr) void scheduler(void) { + struct list *ple; struct proc *p; struct cpu *c = mycpu(); @@ -455,22 +482,47 @@ scheduler(void) intr_on(); int found = 0; - for(p = proc; p < &proc[NPROC]; p++) { + int min = MAX_PRIORITY_DEPTH + 1; + acquire(&proc_list_lock); + struct proc *selected = 0; + for(ple = proc_list.next; ple != &proc_list; ple = ple->next){ + p = (struct proc *) ple; acquire(&p->lock); - if(p->state == RUNNABLE) { - // Switch to chosen process. It is the process's job - // to release its lock and then reacquire it - // before jumping back to us. - p->state = RUNNING; - c->proc = p; - swtch(&c->context, &p->context); - - // Process is done running for now. - // It should have changed its p->state before coming back. - c->proc = 0; - found = 1; + if(p->state == RUNNABLE && p->priority < min) { + if (selected) { + release(&p->lock); + release(&selected->lock); + acquire(&p->lock); + } + selected = p; + min = p->priority; + } else { + release(&p->lock); + } - release(&p->lock); + } + release(&proc_list_lock); + + if (selected) { + // Switch to chosen process. It is the process's job + // to release its lock and then reacquire it + // before jumping back to us. + selected->state = RUNNING; + c->proc = selected; + swtch(&c->context, &selected->context); + + // Process is done running for now. + // It should have changed its p->state before coming back. + c->proc = 0; + found = 1; + release(&selected->lock); + + // FCFS: push proc to the end of the list to ensure + // fisrt-come-first-served among 1 priority queue + acquire(&proc_list_lock); + lst_remove(&selected->ple); + lst_push(&proc_list, &selected->ple); + release(&proc_list_lock); } if(found == 0) { // nothing to run; stop running on this core until an interrupt. @@ -507,12 +559,18 @@ sched(void) mycpu()->intena = intena; } +// utility +int min(int a, int b) { + return a < b ? a : b; +} + // Give up the CPU for one scheduling round. void yield(void) { struct proc *p = myproc(); acquire(&p->lock); + p->priority = min(p->priority + 1, MAX_PRIORITY_DEPTH); p->state = RUNNABLE; sched(); release(&p->lock); @@ -578,9 +636,12 @@ sleep(void *chan, struct spinlock *lk) void wakeup(void *chan) { + struct list *ple; struct proc *p; - for(p = proc; p < &proc[NPROC]; p++) { + acquire(&proc_list_lock); + for(ple = proc_list.next; ple != &proc_list; ple = ple->next){ + p = (struct proc *) ple; if(p != myproc()){ acquire(&p->lock); if(p->state == SLEEPING && p->chan == chan) { @@ -589,6 +650,7 @@ wakeup(void *chan) release(&p->lock); } } + release(&proc_list_lock); } // Kill the process with the given pid. @@ -597,9 +659,12 @@ wakeup(void *chan) int kill(int pid) { + struct list *ple; struct proc *p; - for(p = proc; p < &proc[NPROC]; p++){ + acquire(&proc_list_lock); + for(ple = proc_list.next; ple != &proc_list; ple = ple->next){ + p = (struct proc *) ple; acquire(&p->lock); if(p->pid == pid){ p->killed = 1; @@ -608,10 +673,12 @@ kill(int pid) p->state = RUNNABLE; } release(&p->lock); + release(&proc_list_lock); return 0; } release(&p->lock); } + release(&proc_list_lock); return -1; } @@ -678,11 +745,14 @@ procdump(void) [RUNNING] "run ", [ZOMBIE] "zombie" }; + struct list *ple; struct proc *p; char *state; printf("\n"); - for(p = proc; p < &proc[NPROC]; p++){ + acquire(&proc_list_lock); + for(ple = proc_list.next; ple != &proc_list; ple = ple->next){ + p = (struct proc *) ple; if(p->state == UNUSED) continue; if(p->state >= 0 && p->state < NELEM(states) && states[p->state]) @@ -692,4 +762,5 @@ procdump(void) printf("%d %s %s", p->pid, state, p->name); printf("\n"); } + release(&proc_list_lock); } diff --git a/xv6-os/kernel/proc.h b/xv6-os/kernel/proc.h index d021857a..0d97498e 100644 --- a/xv6-os/kernel/proc.h +++ b/xv6-os/kernel/proc.h @@ -1,3 +1,5 @@ +#include "list.h" + // Saved registers for kernel context switches. struct context { uint64 ra; @@ -83,6 +85,9 @@ enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; // Per-process state struct proc { + struct list ple; // proc list address entry + int priority; // feedback scheduler priority queue + struct spinlock lock; // p->lock must be held when using these: diff --git a/xv6-os/kernel/vm.c b/xv6-os/kernel/vm.c index 62421a24..9faa2d14 100644 --- a/xv6-os/kernel/vm.c +++ b/xv6-os/kernel/vm.c @@ -44,7 +44,7 @@ kvmmake(void) kvmmap(kpgtbl, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X); // allocate and map a kernel stack for each process. - proc_mapstacks(kpgtbl); + // proc_mapstacks(kpgtbl); return kpgtbl; }