diff options
author | rsc <rsc> | 2006-07-16 01:15:28 +0000 |
---|---|---|
committer | rsc <rsc> | 2006-07-16 01:15:28 +0000 |
commit | 65bd8e139a8368e987455a10ec59dd7b079b3af1 (patch) | |
tree | 8ce996135fadab4abde8acf5a6ed4eb69d463c60 /proc.c | |
parent | 40a2a08319511fd157d2d77eefbda52423cc81ec (diff) | |
download | xv6-labs-65bd8e139a8368e987455a10ec59dd7b079b3af1.tar.gz xv6-labs-65bd8e139a8368e987455a10ec59dd7b079b3af1.tar.bz2 xv6-labs-65bd8e139a8368e987455a10ec59dd7b079b3af1.zip |
New scheduler.
Removed cli and sti stack in favor of tracking
number of locks held on each CPU and explicit
conditionals in spinlock.c.
Diffstat (limited to 'proc.c')
-rw-r--r-- | proc.c | 321 |
1 files changed, 168 insertions, 153 deletions
@@ -12,6 +12,7 @@ struct spinlock proc_table_lock; struct proc proc[NPROC]; struct proc *curproc[NCPU]; int next_pid = 1; +extern void forkret(void); /* * set up a process's task state and segment descriptors @@ -96,12 +97,14 @@ newproc() *(np->tf) = *(op->tf); np->tf->tf_regs.reg_eax = 0; // so fork() returns 0 in child - // set up new jmpbuf to start executing at trapret with esp pointing at tf + // Set up new jmpbuf to start executing forkret (see trapasm.S) + // with esp pointing at tf. Forkret will call forkret1 (below) to release + // the proc_table_lock and then jump into the usual trap return code. memset(&np->jmpbuf, 0, sizeof np->jmpbuf); - np->jmpbuf.jb_eip = (unsigned) trapret; + np->jmpbuf.jb_eip = (unsigned) forkret; np->jmpbuf.jb_esp = (unsigned) np->tf - 4; // -4 for the %eip that isn't actually there - // copy file descriptors + // Copy file descriptors for(fd = 0; fd < NOFILE; fd++){ np->fds[fd] = op->fds[fd]; if(np->fds[fd]) @@ -112,127 +115,152 @@ newproc() } void +forkret1(void) +{ + release(&proc_table_lock); +} + +// Per-CPU process scheduler. +// Each CPU calls scheduler() after setting itself up. +// Scheduler never returns. It loops, doing: +// - choose a process to run +// - longjmp to start running that process +// - eventually that process transfers control back +// via longjmp back to the top of scheduler. +void scheduler(void) { - struct proc *op, *np; + struct proc *p; int i; cprintf("start scheduler on cpu %d jmpbuf %p\n", cpu(), &cpus[cpu()].jmpbuf); cpus[cpu()].lastproc = &proc[0]; - setjmp(&cpus[cpu()].jmpbuf); - - op = curproc[cpu()]; - - if(op == 0 || op->mtx != &proc_table_lock) - acquire1(&proc_table_lock, op); - - if(op){ - if(op->newstate <= 0 || op->newstate > ZOMBIE) - panic("scheduler"); - op->state = op->newstate; - op->newstate = -1; - if(op->mtx){ - struct spinlock *mtx = op->mtx; - op->mtx = 0; - if(mtx != &proc_table_lock) - release1(mtx, op); - } - } - - // find a runnable process and switch to it - curproc[cpu()] = 0; - np = cpus[cpu()].lastproc + 1; - while(1){ + for(;;){ + // Loop over process table looking for process to run. + acquire(&proc_table_lock); for(i = 0; i < NPROC; i++){ - if(np >= &proc[NPROC]) - np = &proc[0]; - if(np->state == RUNNABLE) - break; - np++; - } - - if(i < NPROC){ - np->state = RUNNING; - release1(&proc_table_lock, op); - break; + p = &proc[i]; + if(p->state != RUNNABLE) + continue; + + // Run this process. + // XXX move this into swtch or trapret or something. + // It can run on the other stack. + // h/w sets busy bit in TSS descriptor sometimes, and faults + // if it's set in LTR. so clear tss descriptor busy bit. + p->gdt[SEG_TSS].sd_type = STS_T32A; + + // XXX should probably have an lgdt() function in x86.h + // to confine all the inline assembly. + // XXX probably ought to lgdt on trap return too, in case + // a system call has moved a program or changed its size. + asm volatile("lgdt %0" : : "g" (p->gdt_pd.pd_lim)); + ltr(SEG_TSS << 3); + + // Switch to chosen process. It is the process's job + // to release proc_table_lock and then reacquire it + // before jumping back to us. + if(0) cprintf("cpu%d: run %d\n", cpu(), p-proc); + curproc[cpu()] = p; + p->state = RUNNING; + if(setjmp(&cpus[cpu()].jmpbuf) == 0) + longjmp(&p->jmpbuf); + + // Process is done running for now. + // It should have changed its p->state before coming back. + curproc[cpu()] = 0; + if(p->state == RUNNING) + panic("swtch to scheduler with state=RUNNING"); + + // XXX if not holding proc_table_lock panic. } + release(&proc_table_lock); - release1(&proc_table_lock, op); - op = 0; - acquire(&proc_table_lock); - np = &proc[0]; + if(cpus[cpu()].nlock != 0) + panic("holding locks in scheduler"); + + // With proc_table_lock released, there are no + // locks held on this cpu, so interrupts are enabled. + // Hardware interrupts can happen here. + // Also, releasing the lock here lets the other CPUs + // look for runnable processes too. } +} - cpus[cpu()].lastproc = np; - curproc[cpu()] = np; - - // h/w sets busy bit in TSS descriptor sometimes, and faults - // if it's set in LTR. so clear tss descriptor busy bit. - np->gdt[SEG_TSS].sd_type = STS_T32A; - - // XXX should probably have an lgdt() function in x86.h - // to confine all the inline assembly. - // XXX probably ought to lgdt on trap return too, in case - // a system call has moved a program or changed its size. - asm volatile("lgdt %0" : : "g" (np->gdt_pd.pd_lim)); - ltr(SEG_TSS << 3); - - if(0) cprintf("cpu%d: run %d esp=%p callerpc=%p\n", cpu(), np-proc); - longjmp(&np->jmpbuf); +// Enter scheduler. Must already hold proc_table_lock +// and have changed curproc[cpu()]->state. +void +sched(void) +{ + if(setjmp(&curproc[cpu()]->jmpbuf) == 0) + longjmp(&cpus[cpu()].jmpbuf); } -// give up the cpu by switching to the scheduler, -// which runs on the per-cpu stack. +// Give up the CPU for one scheduling round. void -swtch(int newstate) +yield() { - struct proc *p = curproc[cpu()]; + struct proc *p; - if(p == 0) - panic("swtch no proc"); - if(p->mtx == 0 && p->locks != 0) - panic("swtch w/ locks"); - if(p->mtx && p->locks != 1) - panic("swtch w/ locks 1"); - if(p->mtx && p->mtx->locked == 0) - panic("switch w/ lock but not held"); - if(p->locks && (read_eflags() & FL_IF)) - panic("swtch w/ lock but FL_IF"); - - p->newstate = newstate; // basically an argument to scheduler() - if(setjmp(&p->jmpbuf) == 0) - longjmp(&cpus[cpu()].jmpbuf); + if((p=curproc[cpu()]) == 0 || curproc[cpu()]->state != RUNNING) + panic("yield"); + acquire(&proc_table_lock); + p->state = RUNNABLE; + sched(); + release(&proc_table_lock); } +// Atomically release lock and sleep on chan. +// Reacquires lock when reawakened. void -sleep(void *chan, struct spinlock *mtx) +sleep(void *chan, struct spinlock *lk) { struct proc *p = curproc[cpu()]; if(p == 0) panic("sleep"); + // Must acquire proc_table_lock in order to + // change p->state and then call sched. + // Once we hold proc_table_lock, we can be + // guaranteed that we won't miss any wakeup + // (wakeup runs with proc_table_lock locked), + // so it's okay to release lk. + if(lk != &proc_table_lock){ + acquire(&proc_table_lock); + release(lk); + } + + // Go to sleep. p->chan = chan; - p->mtx = mtx; // scheduler will release it + p->state = SLEEPING; + sched(); - swtch(WAITING); - - if(mtx) - acquire(mtx); + // Tidy up. p->chan = 0; + + // Reacquire original lock. + if(lk != &proc_table_lock){ + release(&proc_table_lock); + acquire(lk); + } } +// Wake up all processes sleeping on chan. +// Proc_table_lock must be held. void wakeup1(void *chan) { struct proc *p; for(p = proc; p < &proc[NPROC]; p++) - if(p->state == WAITING && p->chan == chan) + if(p->state == SLEEPING && p->chan == chan) p->state = RUNNABLE; } +// Wake up all processes sleeping on chan. +// Proc_table_lock is acquired and released. void wakeup(void *chan) { @@ -241,15 +269,32 @@ wakeup(void *chan) release(&proc_table_lock); } -// give up the CPU but stay marked as RUNNABLE -void -yield() +// Kill the process with the given pid. +// Process won't actually exit until it returns +// to user space (see trap in trap.c). +int +proc_kill(int pid) { - if(curproc[cpu()] == 0 || curproc[cpu()]->state != RUNNING) - panic("yield"); - swtch(RUNNABLE); + struct proc *p; + + acquire(&proc_table_lock); + for(p = proc; p < &proc[NPROC]; p++){ + if(p->pid == pid){ + p->killed = 1; + // Wake process from sleep if necessary. + if(p->state == SLEEPING) + p->state = RUNNABLE; + release(&proc_table_lock); + return 0; + } + } + release(&proc_table_lock); + return -1; } +// Exit the current process. Does not return. +// Exited processes remain in the zombie state +// until their parent calls wait() to find out they exited. void proc_exit() { @@ -257,6 +302,7 @@ proc_exit() struct proc *cp = curproc[cpu()]; int fd; + // Close all open files. for(fd = 0; fd < NOFILE; fd++){ if(cp->fds[fd]){ fd_close(cp->fds[fd]); @@ -266,91 +312,60 @@ proc_exit() acquire(&proc_table_lock); - // wake up parent + // Wake up our parent. for(p = proc; p < &proc[NPROC]; p++) if(p->pid == cp->ppid) wakeup1(p); - // abandon children + // Reparent our children to process 1. for(p = proc; p < &proc[NPROC]; p++) if(p->ppid == cp->pid) - p->pid = 1; + p->ppid = 1; - cp->mtx = &proc_table_lock; - swtch(ZOMBIE); - panic("a zombie revived"); + // Jump into the scheduler, never to return. + cp->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 proc_wait(void) { struct proc *p; struct proc *cp = curproc[cpu()]; - int any, pid; + int i, havekids, pid; acquire(&proc_table_lock); - - while(1){ - any = 0; - for(p = proc; p < &proc[NPROC]; p++){ - if(p->state == ZOMBIE && p->ppid == cp->pid){ - kfree(p->mem, p->sz); - kfree(p->kstack, KSTACKSIZE); - pid = p->pid; - p->state = UNUSED; - release(&proc_table_lock); - return pid; + for(;;){ + // Scan through table looking zombie children. + havekids = 0; + for(i = 0; i < NPROC; i++){ + p = &proc[i]; + if(p->ppid == cp->pid){ + if(p->state == ZOMBIE){ + // Found one. + kfree(p->mem, p->sz); + kfree(p->kstack, KSTACKSIZE); + pid = p->pid; + p->state = UNUSED; + p->pid = 0; + release(&proc_table_lock); + return pid; + } + havekids = 1; } - if(p->state != UNUSED && p->ppid == cp->pid) - any = 1; } - if(any == 0){ + + // No point waiting if we don't have any children. + if(!havekids){ release(&proc_table_lock); return -1; } + + // Wait for children to exit. (See wakeup1 call in proc_exit.) sleep(cp, &proc_table_lock); } } -int -proc_kill(int pid) -{ - struct proc *p; - - acquire(&proc_table_lock); - for(p = proc; p < &proc[NPROC]; p++){ - if(p->pid == pid && p->state != UNUSED){ - p->killed = 1; - if(p->state == WAITING) - p->state = RUNNABLE; - release(&proc_table_lock); - return 0; - } - } - release(&proc_table_lock); - return -1; -} - -// disable interrupts -void -cli(void) -{ - if(cpus[cpu()].clis == 0) - __asm __volatile("cli"); - cpus[cpu()].clis += 1; - if((read_eflags() & FL_IF) != 0) - panic("cli but enabled"); -} - -// enable interrupts -void -sti(void) -{ - if((read_eflags() & FL_IF) != 0) - panic("sti but enabled"); - if(cpus[cpu()].clis < 1) - panic("sti"); - cpus[cpu()].clis -= 1; - if(cpus[cpu()].clis < 1) - __asm __volatile("sti"); -} |