Mercurial > hg > Members > anatofuz > CbC_xv6
diff src/proc.c @ 0:83c23a36980d
Init
author | Tatsuki IHA <e125716@ie.u-ryukyu.ac.jp> |
---|---|
date | Fri, 26 May 2017 23:11:05 +0900 |
parents | |
children | bf2f70fa8852 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/proc.c Fri May 26 23:11:05 2017 +0900 @@ -0,0 +1,532 @@ +#include "types.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "arm.h" +#include "proc.h" +#include "spinlock.h" + +// +// Process initialization: +// process initialize is somewhat tricky. +// 1. We need to fake the kernel stack of a new process as if the process +// has been interrupt (a trapframe on the stack), this would allow us +// to "return" to the correct user instruction. +// 2. We also need to fake the kernel execution for this new process. When +// swtch switches to this (new) process, it will switch to its stack, +// and reload registers with the saved context. We use forkret as the +// return address (in lr register). (In x86, it will be the return address +// pushed on the stack by the process.) +// +// The design of context switch in xv6 is interesting: after initialization, +// each CPU executes in the scheduler() function. The context switch is not +// between two processes, but instead, between the scheduler. Think of scheduler +// as the idle process. +// +struct { + struct spinlock lock; + struct proc proc[NPROC]; +} ptable; + +static struct proc *initproc; +struct proc *proc; + +int nextpid = 1; +extern void forkret(void); +extern void trapret(void); + +static void wakeup1(void *chan); + +void pinit(void) +{ + initlock(&ptable.lock, "ptable"); +} + +//PAGEBREAK: 32 +// Look in the process table for an UNUSED proc. +// If found, change state to EMBRYO and initialize +// state required to run in the kernel. +// Otherwise return 0. +static struct proc* allocproc(void) +{ + struct proc *p; + char *sp; + + acquire(&ptable.lock); + + for(p = ptable.proc; p < &ptable.proc[NPROC]; p++) { + if(p->state == UNUSED) { + goto found; + } + + } + + release(&ptable.lock); + return 0; + + found: + p->state = EMBRYO; + p->pid = nextpid++; + release(&ptable.lock); + + // Allocate kernel stack. + if((p->kstack = alloc_page ()) == 0){ + p->state = UNUSED; + return 0; + } + + sp = p->kstack + KSTACKSIZE; + + // Leave room for trap frame. + sp -= sizeof (*p->tf); + p->tf = (struct trapframe*)sp; + + // Set up new context to start executing at forkret, + // which returns to trapret. + sp -= 4; + *(uint*)sp = (uint)trapret; + + sp -= 4; + *(uint*)sp = (uint)p->kstack + KSTACKSIZE; + + sp -= sizeof (*p->context); + p->context = (struct context*)sp; + memset(p->context, 0, sizeof(*p->context)); + + // skip the push {fp, lr} instruction in the prologue of forkret. + // This is different from x86, in which the harderware pushes return + // address before executing the callee. In ARM, return address is + // loaded into the lr register, and push to the stack by the callee + // (if and when necessary). We need to skip that instruction and let + // it use our implementation. + p->context->lr = (uint)forkret+4; + + return p; +} + +void error_init () +{ + panic ("failed to craft first process\n"); +} + + +//PAGEBREAK: 32 +// hand-craft the first user process. We link initcode.S into the kernel +// as a binary, the linker will generate __binary_initcode_start/_size +void userinit(void) +{ + struct proc *p; + extern char _binary_initcode_start[], _binary_initcode_size[]; + + p = allocproc(); + initproc = p; + + if((p->pgdir = kpt_alloc()) == NULL) { + panic("userinit: out of memory?"); + } + + inituvm(p->pgdir, _binary_initcode_start, (int)_binary_initcode_size); + + p->sz = PTE_SZ; + + // craft the trapframe as if + memset(p->tf, 0, sizeof(*p->tf)); + + p->tf->r14_svc = (uint)error_init; + p->tf->spsr = spsr_usr (); + p->tf->sp_usr = PTE_SZ; // set the user stack + p->tf->lr_usr = 0; + + // set the user pc. The actual pc loaded into r15_usr is in + // p->tf, the trapframe. + p->tf->pc = 0; // beginning of initcode.S + + safestrcpy(p->name, "initcode", sizeof(p->name)); + p->cwd = namei("/"); + + p->state = RUNNABLE; +} + +// Grow current process's memory by n bytes. +// Return 0 on success, -1 on failure. +int growproc(int n) +{ + uint sz; + + sz = proc->sz; + + if(n > 0){ + if((sz = allocuvm(proc->pgdir, sz, sz + n)) == 0) { + return -1; + } + + } else if(n < 0){ + if((sz = deallocuvm(proc->pgdir, sz, sz + n)) == 0) { + return -1; + } + } + + proc->sz = sz; + switchuvm(proc); + + return 0; +} + +// Create a new process copying p as the parent. +// Sets up stack to return as if from system call. +// Caller must set state of returned proc to RUNNABLE. +int fork(void) +{ + int i, pid; + struct proc *np; + + // Allocate process. + if((np = allocproc()) == 0) { + return -1; + } + + // Copy process state from p. + if((np->pgdir = copyuvm(proc->pgdir, proc->sz)) == 0){ + free_page(np->kstack); + np->kstack = 0; + np->state = UNUSED; + return -1; + } + + np->sz = proc->sz; + np->parent = proc; + *np->tf = *proc->tf; + + // Clear r0 so that fork returns 0 in the child. + np->tf->r0 = 0; + + for(i = 0; i < NOFILE; i++) { + if(proc->ofile[i]) { + np->ofile[i] = filedup(proc->ofile[i]); + } + } + + np->cwd = idup(proc->cwd); + + pid = np->pid; + np->state = RUNNABLE; + safestrcpy(np->name, proc->name, sizeof(proc->name)); + + return pid; +} + +// Exit the current process. Does not return. +// An exited process remains in the zombie state +// until its parent calls wait() to find out it exited. +void exit(void) +{ + struct proc *p; + int fd; + + if(proc == initproc) { + panic("init exiting"); + } + + // Close all open files. + for(fd = 0; fd < NOFILE; fd++){ + if(proc->ofile[fd]){ + fileclose(proc->ofile[fd]); + proc->ofile[fd] = 0; + } + } + + iput(proc->cwd); + proc->cwd = 0; + + acquire(&ptable.lock); + + // Parent might be sleeping in wait(). + wakeup1(proc->parent); + + // Pass abandoned children to init. + for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ + if(p->parent == proc){ + p->parent = initproc; + + if(p->state == ZOMBIE) { + wakeup1(initproc); + } + } + } + + // Jump into the scheduler, never to return. + proc->state = ZOMBIE; + sched(); + + panic("zombie exit"); +} + +// Wait for a child process to exit and return its pid. +// Return -1 if this process has no children. +int wait(void) +{ + struct proc *p; + int havekids, pid; + + acquire(&ptable.lock); + + for(;;){ + // Scan through table looking for zombie children. + havekids = 0; + + for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ + if(p->parent != proc) { + continue; + } + + havekids = 1; + + if(p->state == ZOMBIE){ + // Found one. + pid = p->pid; + free_page(p->kstack); + p->kstack = 0; + freevm(p->pgdir); + p->state = UNUSED; + p->pid = 0; + p->parent = 0; + p->name[0] = 0; + p->killed = 0; + release(&ptable.lock); + + return pid; + } + } + + // No point waiting if we don't have any children. + if(!havekids || proc->killed){ + release(&ptable.lock); + return -1; + } + + // Wait for children to exit. (See wakeup1 call in proc_exit.) + sleep(proc, &ptable.lock); //DOC: wait-sleep + } +} + +//PAGEBREAK: 42 +// Per-CPU process scheduler. +// Each CPU calls scheduler() after setting itself up. +// Scheduler never returns. It loops, doing: +// - choose a process to run +// - swtch to start running that process +// - eventually that process transfers control +// via swtch back to the scheduler. +void scheduler(void) +{ + struct proc *p; + + for(;;){ + // Enable interrupts on this processor. + sti(); + + // Loop over process table looking for process to run. + acquire(&ptable.lock); + + for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ + if(p->state != RUNNABLE) { + continue; + } + + // Switch to chosen process. It is the process's job + // to release ptable.lock and then reacquire it + // before jumping back to us. + proc = p; + switchuvm(p); + + p->state = RUNNING; + + swtch(&cpu->scheduler, proc->context); + // Process is done running for now. + // It should have changed its p->state before coming back. + proc = 0; + } + + release(&ptable.lock); + } +} + +// Enter scheduler. Must hold only ptable.lock +// and have changed proc->state. +void sched(void) +{ + int intena; + + //show_callstk ("sched"); + + if(!holding(&ptable.lock)) { + panic("sched ptable.lock"); + } + + if(cpu->ncli != 1) { + panic("sched locks"); + } + + if(proc->state == RUNNING) { + panic("sched running"); + } + + if(int_enabled ()) { + panic("sched interruptible"); + } + + intena = cpu->intena; + swtch(&proc->context, cpu->scheduler); + cpu->intena = intena; +} + +// Give up the CPU for one scheduling round. +void yield(void) +{ + acquire(&ptable.lock); //DOC: yieldlock + proc->state = RUNNABLE; + sched(); + release(&ptable.lock); +} + +// A fork child's very first scheduling by scheduler() +// will swtch here. "Return" to user space. +void forkret(void) +{ + static int first = 1; + + // Still holding ptable.lock from scheduler. + release(&ptable.lock); + + if (first) { + // Some initialization functions must be run in the context + // of a regular process (e.g., they call sleep), and thus cannot + // be run from main(). + first = 0; + initlog(); + } + + // Return to "caller", actually trapret (see allocproc). +} + +// Atomically release lock and sleep on chan. +// Reacquires lock when awakened. +void sleep(void *chan, struct spinlock *lk) +{ + //show_callstk("sleep"); + + if(proc == 0) { + panic("sleep"); + } + + if(lk == 0) { + panic("sleep without lk"); + } + + // Must acquire ptable.lock in order to change p->state and then call + // sched. Once we hold ptable.lock, we can be guaranteed that we won't + // miss any wakeup (wakeup runs with ptable.lock locked), so it's okay + // to release lk. + if(lk != &ptable.lock){ //DOC: sleeplock0 + acquire(&ptable.lock); //DOC: sleeplock1 + release(lk); + } + + // Go to sleep. + proc->chan = chan; + proc->state = SLEEPING; + sched(); + + // Tidy up. + proc->chan = 0; + + // Reacquire original lock. + if(lk != &ptable.lock){ //DOC: sleeplock2 + release(&ptable.lock); + acquire(lk); + } +} + +//PAGEBREAK! +// Wake up all processes sleeping on chan. The ptable lock must be held. +static void wakeup1(void *chan) +{ + struct proc *p; + + for(p = ptable.proc; p < &ptable.proc[NPROC]; p++) { + if(p->state == SLEEPING && p->chan == chan) { + p->state = RUNNABLE; + } + } +} + +// Wake up all processes sleeping on chan. +void wakeup(void *chan) +{ + acquire(&ptable.lock); + wakeup1(chan); + release(&ptable.lock); +} + +// Kill the process with the given pid. Process won't exit until it returns +// to user space (see trap in trap.c). +int kill(int pid) +{ + struct proc *p; + + acquire(&ptable.lock); + + for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ + if(p->pid == pid){ + p->killed = 1; + + // Wake process from sleep if necessary. + if(p->state == SLEEPING) { + p->state = RUNNABLE; + } + + release(&ptable.lock); + return 0; + } + } + + release(&ptable.lock); + return -1; +} + +//PAGEBREAK: 36 +// Print a process listing to console. For debugging. Runs when user +// types ^P on console. No lock to avoid wedging a stuck machine further. +void procdump(void) +{ + static char *states[] = { + [UNUSED] "unused", + [EMBRYO] "embryo", + [SLEEPING] "sleep ", + [RUNNABLE] "runble", + [RUNNING] "run ", + [ZOMBIE] "zombie" + }; + + struct proc *p; + char *state; + + for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ + if(p->state == UNUSED) { + continue; + } + + if(p->state >= 0 && p->state < NELEM(states) && states[p->state]) { + state = states[p->state]; + } else { + state = "???"; + } + + cprintf("%d %s %d:%s %d\n", p->pid, state, p->pid, p->name, p->parent->pid); + } + + show_callstk("procdump: \n"); +} + +