// Initialize and load the per-CPU TSS and IDT void trap_init_percpu(void) { // The example code here sets up the Task State Segment (TSS) and // the TSS descriptor for CPU 0. But it is incorrect if we are // running on other CPUs because each CPU has its own kernel stack. // Fix the code so that it works for all CPUs. // // Hints: // - The macro "thiscpu" always refers to the current CPU's // struct CpuInfo; // - The ID of the current CPU is given by cpunum() or // thiscpu->cpu_id; // - Use "thiscpu->cpu_ts" as the TSS for the current CPU, // rather than the global "ts" variable; // - Use gdt[(GD_TSS0 >> 3) + i] for CPU i's TSS descriptor; // - You mapped the per-CPU kernel stacks in mem_init_mp() // // ltr sets a 'busy' flag in the TSS selector, so if you // accidentally load the same TSS on more than one CPU, you'll // get a triple fault. If you set up an individual CPU's TSS // wrong, you may not get a fault until you try to return from // user space on that CPU. // // LAB 4: Your code here: // Setup a TSS so that we get the right stack // when we trap to the kernel. thiscpu->cpu_ts.ts_esp0 = KSTACKTOP-cpunum()*(KSTKSIZE+KSTKGAP); thiscpu->cpu_ts.ts_ss0 = GD_KD; thiscpu->cpu_ts.ts_eflags = 0; thiscpu->cpu_ts.ts_iomb = 0; // Initialize the TSS slot of the gdt. gdt[(GD_TSS0 >> 3)+cpunum()] = SEG16(STS_T32A, (uint32_t)(&(thiscpu->cpu_ts)), sizeof(struct Taskstate) - 1, 0); gdt[(GD_TSS0 >> 3)+cpunum()].sd_s = 0; ltr(GD_TSS0+cpunum()*sizeof(struct Segdesc)); /* thiscpu->cpu_ts.ts_esp0 = KSTACKTOP-cpunum()*(KSTKSIZE+KSTKGAP); thiscpu->cpu_ts.ts_ss0 = GD_KD; gdt[(GD_TSS0 >> 3) + cpunum()]= SEG16(STS_T32A, (uint32_t)thiscpu, sizeof(struct Taskstate) - 1, 0); gdt[(GD_TSS0 >> 3) + cpunum()].sd_s = 0; // Load the TSS selector (like other segment selectors, the // bottom three bits are special; we leave them 0) ltr(GD_TSS0+cpunum()*sizeof(struct Segdesc));*/ // Load the IDT lidt(&idt_pd); }
// Bootstrap processor starts running C code here. // Allocate a real stack and switch to it, first // doing some setup required for memory allocator to work. int main(void) { kinit1(end, P2V(4*1024*1024)); // phys page allocator kvmalloc(); // kernel page table mpinit(); // detect other processors lapicinit(); // interrupt controller seginit(); // segment descriptors cprintf("\ncpu%d: starting xv6\n\n", cpunum()); picinit(); // another interrupt controller ioapicinit(); // another interrupt controller consoleinit(); // console hardware uartinit(); // serial port pinit(); // process table tvinit(); // trap vectors binit(); // buffer cache fileinit(); // file table ideinit(); // disk if(!ismp) timerinit(); // uniprocessor timer startothers(); // start other processors kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers() userinit(); // first user process mpmain(); // finish this processor's setup init_semaphores_on_boot(); }
void print_trapframe(struct Trapframe *tf) { cprintf("TRAP frame at %p from CPU %d\n", tf, cpunum()); print_regs(&tf->tf_regs); cprintf(" es 0x----%04x\n", tf->tf_es); cprintf(" ds 0x----%04x\n", tf->tf_ds); cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); // If this trap was a page fault that just happened // (so %cr2 is meaningful), print the faulting linear address. if (tf == last_tf && tf->tf_trapno == T_PGFLT) cprintf(" cr2 0x%08x\n", rcr2()); cprintf(" err 0x%08x", tf->tf_err); // For page faults, print decoded fault error code: // U/K=fault occurred in user/kernel mode // W/R=a write/read caused the fault // PR=a protection violation caused the fault (NP=page not present). if (tf->tf_trapno == T_PGFLT) cprintf(" [%s, %s, %s]\n", tf->tf_err & 4 ? "user" : "kernel", tf->tf_err & 2 ? "write" : "read", tf->tf_err & 1 ? "protection" : "not-present"); else cprintf("\n"); cprintf(" eip 0x%08x\n", tf->tf_eip); cprintf(" cs 0x----%04x\n", tf->tf_cs); cprintf(" flag 0x%08x\n", tf->tf_eflags); if ((tf->tf_cs & 3) != 0) { cprintf(" esp 0x%08x\n", tf->tf_esp); cprintf(" ss 0x----%04x\n", tf->tf_ss); } }
static void bootothers(void) { extern uchar _binary_bootother_start[], _binary_bootother_size[]; uchar *code; struct cpu *c; char *stack; // Write bootstrap code to unused memory at 0x7000. code = (uchar*)0x7000; memmove(code, _binary_bootother_start, (uint)_binary_bootother_size); for(c = cpus; c < cpus+ncpu; c++){ if(c == cpus+cpunum()) // We've started already. continue; // Fill in %esp, %eip and start code on cpu. stack = kalloc(KSTACKSIZE); *(void**)(code-4) = stack + KSTACKSIZE; *(void**)(code-8) = mpmain; lapicstartap(c->id, (uint)code); // Wait for cpu to get through bootstrap. while(c->booted == 0) ; } }
// Setup code for APs void mp_main(void) { // We are in high EIP now, safe to switch to kern_pgdir lcr3(PADDR(kern_pgdir)); cprintf("SMP: CPU %d starting\n", cpunum()); lapic_init(); env_init_percpu(); trap_init_percpu(); xchg(&thiscpu->cpu_status, CPU_STARTED); // tell boot_aps() we're up #ifdef USE_TICKET_SPIN_LOCK spinlock_test(); #endif // Now that we have finished some basic setup, call sched_yield() // to start running processes on this CPU. But make sure that // only one CPU can enter the scheduler at a time! // // Your code here: lock_kernel(); sched_yield(); // Remove this after you finish Exercise 4 //for (;;); }
// Bootstrap processor gets here after setting up the hardware. // Additional processors start here. static void mpmain(void) { if(cpunum() != mpbcpu()) lapicinit(cpunum()); ksegment(); cprintf("cpu%d: mpmain\n", cpu->id); idtinit(); cpu->dir = kernel_dir; enable_page(cpu->dir); cprintf("-- mpmain -- cpu%d enable paging dir: %x \n", cpu->id, cpu->dir); xchg(&cpu->booted, 1); cprintf("cpu%d: scheduling\n", cpu->id); scheduler(); }
// Set up CPU's kernel segment descriptors. // Run once at boot time on each CPU. void seginit(void) { struct cpu *c; // Map virtual addresses to linear addresses using identity map. // Cannot share a CODE descriptor for both kernel and user // because it would have to have DPL_USR, but the CPU forbids // an interrupt from CPL=0 to DPL=3. c = &cpus[cpunum()]; c->gdt[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0xffffffff, 0); c->gdt[SEG_KDATA] = SEG(STA_W, 0, 0xffffffff, 0); c->gdt[SEG_UCODE] = SEG(STA_X|STA_R, 0, 0xffffffff, DPL_USER); c->gdt[SEG_UDATA] = SEG(STA_W, 0, 0xffffffff, DPL_USER); // Map cpu, and curproc c->gdt[SEG_KCPU] = SEG(STA_W, &c->cpu, 8, 0); lgdt(c->gdt, sizeof(c->gdt)); loadgs(SEG_KCPU << 3); // Initialize cpu-local storage. cpu = c; proc = 0; }
// Start the non-boot (AP) processors. static void startothers(void) { extern uchar _binary_entryother_start[], _binary_entryother_size[]; uchar *code; struct cpu *c; char *stack; // Write entry code to unused memory at 0x7000. // The linker has placed the image of entryother.S in // _binary_entryother_start. code = p2v(0x7000); memmove(code, _binary_entryother_start, (uint)_binary_entryother_size); for(c = cpus; c < cpus+ncpu; c++){ if(c == cpus+cpunum()) // We've started already. continue; // Tell entryother.S what stack to use, where to enter, and what // pgdir to use. We cannot use kpgdir yet, because the AP processor // is running in low memory, so we use entrypgdir for the APs too. stack = kalloc(); *(void**)(code-4) = stack + KSTACKSIZE; *(void**)(code-8) = mpenter; *(int**)(code-12) = (void *) v2p(entrypgdir); lapicstartap(c->id, v2p(code)); // wait for cpu to finish mpmain() while(c->started == 0) ; } }
// Start the non-boot (AP) processors. static void boot_aps(void) { extern unsigned char mpentry_start[], mpentry_end[]; void *code; struct CpuInfo *c; // Write entry code to unused memory at MPENTRY_PADDR code = KADDR(MPENTRY_PADDR); memmove(code, mpentry_start, mpentry_end - mpentry_start); // Boot each AP one at a time for (c = cpus; c < cpus + ncpu; c++) { if (c == cpus + cpunum()) // We've started already. continue; // Tell mpentry.S what stack to use mpentry_kstack = percpu_kstacks[c - cpus] + KSTKSIZE; // Start the CPU at mpentry_start lapic_startap(c->cpu_id, PADDR(code)); // Wait for the CPU to finish some basic setup in mp_main() while(c->cpu_status != CPU_STARTED) ; } }
// Other CPUs jump here from entryother.S. static void mpenter(void) { switchkvm(); seginit(); lapicinit(cpunum()); mpmain(); }
// Common CPU setup code. static void mpmain(void) { cprintf("cpu%d: starting\n", cpunum()); idtinit(); // load idt register xchg(&cpu->started, 1); // tell startothers() we're up scheduler(); // start running processes }
// Choose a user environment to run and run it. void sched_yield(void) { struct Env *idle; int i; // Implement simple round-robin scheduling. // // Search through 'envs' for an ENV_RUNNABLE environment in // circular fashion starting just after the env this CPU was // last running. Switch to the first such environment found. // // If no envs are runnable, but the environment previously // running on this CPU is still ENV_RUNNING, it's okay to // choose that environment. // // Never choose an environment that's currently running on // another CPU (env_status == ENV_RUNNING) and never choose an // idle environment (env_type == ENV_TYPE_IDLE). If there are // no runnable environments, simply drop through to the code // below to switch to this CPU's idle environment. // LAB 4: Your code here. // For debugging and testing purposes, if there are no // runnable environments other than the idle environments, // drop into the kernel monitor. for (i = 0; i < NENV; i++) { if (envs[i].env_type != ENV_TYPE_IDLE && (envs[i].env_status == ENV_RUNNABLE || envs[i].env_status == ENV_RUNNING)) break; } if (i == NENV) { cprintf("No more runnable environments!\n"); while (1) monitor(NULL); } // Run this CPU's idle environment when nothing else is runnable. idle = &envs[cpunum()]; if (!(idle->env_status == ENV_RUNNABLE || idle->env_status == ENV_RUNNING)) panic("CPU %d: No idle environment!", cpunum()); env_run(idle); }
void spinlock_test() { int i; volatile int interval = 0; /* BSP give APs some time to reach this point */ if (cpunum() == 0) { while (interval++ < 10000) asm volatile("pause"); } for (i=0; i<100; i++) { lock_kernel(); if (test_ctr % 10000 != 0) panic("ticket spinlock test fail: I saw a middle value\n"); interval = 0; while (interval++ < 10000) test_ctr++; unlock_kernel(); } lock_kernel(); cprintf("spinlock_test() succeeded on CPU %d!\n", cpunum()); unlock_kernel(); }
// Set up CPU's kernel segment descriptors. // Run once at boot time on each CPU. void ksegment(void) { struct cpu *c; c = &cpus[cpunum()]; c->gdt[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0x100000 + 64*1024-1, 0); c->gdt[SEG_KDATA] = SEG(STA_W, 0, 0xffffffff, 0); c->gdt[SEG_KCPU] = SEG(STA_W, &c->cpu, 8, 0); lgdt(c->gdt, sizeof(c->gdt)); loadgs(SEG_KCPU << 3); // Initialize cpu-local storage. cpu = c; proc = 0; }
// Release the lock. void spin_unlock(struct spinlock *lk) { #ifdef DEBUG_SPINLOCK if (!holding(lk)) { int i; uint32_t pcs[10]; // Nab the acquiring EIP chain before it gets released memmove(pcs, lk->pcs, sizeof pcs); #ifdef bug_017 if(lk->cpu == 0) { cprintf("Total lock_cnt:%d\n",lock_cnt); panic("here\n"); } #endif cprintf("CPU %d cannot release %s: held by CPU %d\nAcquired at:", cpunum(), lk->name, lk->cpu->cpu_id); for (i = 0; i < 10 && pcs[i]; i++) { struct Eipdebuginfo info; if (debuginfo_eip(pcs[i], &info) >= 0) cprintf(" %08x %s:%d: %.*s+%x\n", pcs[i], info.eip_file, info.eip_line, info.eip_fn_namelen, info.eip_fn_name, pcs[i] - info.eip_fn_addr); else cprintf(" %08x\n", pcs[i]); } panic("spin_unlock"); } lk->pcs[0] = 0; lk->cpu = 0; #endif // The xchg serializes, so that reads before release are // not reordered after it. The 1996 PentiumPro manual (Volume 3, // 7.2) says reads can be carried out speculatively and in // any order, which implies we need to serialize here. // But the 2007 Intel 64 Architecture Memory Ordering White // Paper says that Intel 64 and IA-32 will not move a load // after a store. So lock->locked = 0 would work here. // The xchg being asm volatile ensures gcc emits it after // the above assignments (and after the critical section). xchg(&lk->locked, 0); }
// Acquire the lock. // Loops (spins) until the lock is acquired. // Holding a lock for a long time may cause // other CPUs to waste time spinning to acquire it. void spin_lock(struct spinlock *lk) { #ifdef DEBUG_SPINLOCK if (holding(lk)) panic("CPU %d cannot acquire %s: already holding", cpunum(), lk->name); #endif // The xchg is atomic. // It also serializes, so that reads after acquire are not // reordered before it. while (xchg(&lk->locked, 1) != 0) asm volatile ("pause"); // Record info about lock acquisition for debugging. #ifdef DEBUG_SPINLOCK lk->cpu = thiscpu; get_caller_pcs(lk->pcs); #endif }
// Setup code for APs void mp_main(void) { // We are in high EIP now, safe to switch to kern_pgdir lcr3(PADDR(kern_pgdir)); cprintf("SMP: CPU %d starting\n", cpunum()); lapic_init(); env_init_percpu(); trap_init_percpu(); xchg(&thiscpu->cpu_status, CPU_STARTED); // tell boot_aps() we're up // Now that we have finished some basic setup, call sched_yield() // to start running processes on this CPU. But make sure that // only one CPU can enter the scheduler at a time! // // Your code here: lock_kernel(); //Acquire the lock sched_yield(); //Call the sched_yield() function to schedule and run different environments, Exercise 6 // Remove this after you finish Exercise 4 //for (;;); }
// Start the non-boot (AP) processors. static void startothers(void) { extern uchar _binary_entryother_start[], _binary_entryother_size[]; uchar *code; struct cpu *c; char *stack; // Write entry code to unused memory at 0x7000. // The linker has placed the image of entryother.S in // _binary_entryother_start. code = p2v(0x7000); memmove(code, _binary_entryother_start, (uint)_binary_entryother_size); for(c = cpus; c < cpus+ncpu; c++){ if(c == cpus+cpunum()) // We've started already. continue; // Tell entryother.S what stack to use, where to enter, and what // pgdir to use. We cannot use kpgdir yet, because the AP processor // is running in low memory, so we use entrypgdir for the APs too. // kalloc can return addresses above 4Mbyte (the machine may have // much more physical memory than 4Mbyte), which aren't mapped by // entrypgdir, so we must allocate a stack using enter_alloc(); // this introduces the constraint that xv6 cannot use kalloc until // after these last enter_alloc invocations. stack = enter_alloc(); *(void**)(code-4) = stack + KSTACKSIZE; *(void**)(code-8) = mpenter; *(int**)(code-12) = (void *) v2p(entrypgdir); lapicstartap(c->id, v2p(code)); // wait for cpu to finish mpmain() while(c->started == 0) ; } }
/* * Panic is called on unresolvable fatal errors. * It prints "panic: mesg", and then enters the kernel monitor. */ void _panic(const char *file, int line, const char *fmt,...) { va_list ap; if (panicstr) goto dead; panicstr = fmt; // Be extra sure that the machine is in as reasonable state __asm __volatile("cli; cld"); va_start(ap, fmt); cprintf("kernel panic on CPU %d at %s:%d: ", cpunum(), file, line); vcprintf(fmt, ap); cprintf("\n"); va_end(ap); dead: /* break into the kernel monitor */ while (1) monitor(NULL); }
//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); #ifndef __ORIGINAL_SCHED__ p = pdequeue(); if(p){ #else for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->state != RUNNABLE) continue; #endif // For debug, checking the scheduled process priority cprintf("cpu %d get process %s, process prio = %d\n",cpunum(),p->name,p->priority); // 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); switchkvm(); // 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; if(!holding(&ptable.lock)) panic("sched ptable.lock"); if(cpu->ncli != 1) panic("sched locks"); if(proc->state == RUNNING) panic("sched running"); if(readeflags()&FL_IF) panic("sched interruptible"); intena = cpu->intena; swtch(&proc->context, cpu->scheduler); cpu->intena = intena; }
void trap(struct Trapframe *tf) { // The environment may have set DF and some versions // of GCC rely on DF being clear asm volatile("cld" ::: "cc"); // Halt the CPU if some other CPU has called panic() extern char *panicstr; if (panicstr) asm volatile("hlt"); // Re-acqurie the big kernel lock if we were halted in // sched_yield() //if(tf->tf_eip >= KERNBASE && tf->tf_trapno >= IRQ_OFFSET) // lock_kernel(); if (xchg(&thiscpu->cpu_status, CPU_STARTED) == CPU_HALTED) lock_kernel(); // Check that interrupts are disabled. If this assertion // fails, DO NOT be tempted to fix it by inserting a "cli" in // the interrupt path. assert(!(read_eflags() & FL_IF)); if ((tf->tf_cs & 3) == 3) { // Trapped from user mode. // Acquire the big kernel lock before doing any // serious kernel work. // LAB 4: Your code here. lock_kernel(); assert(curenv); // Garbage collect if current enviroment is a zombie if (curenv->env_status == ENV_DYING) { env_free(curenv); curenv = NULL; sched_yield(); } // Copy trap frame (which is currently on the stack) // into 'curenv->env_tf', so that running the environment // will restart at the trap point. curenv->env_tf = *tf; // The trapframe on the stack should be ignored from here on. tf = &curenv->env_tf; } // Record that tf is the last real trapframe so // print_trapframe can print some additional information. last_tf = tf; // Dispatch based on what type of trap occurred trap_dispatch(tf); // If we made it to this point, then no other environment was // scheduled, so we should return to the current environment // if doing so makes sense. thiscpu->cpu_ts.ts_esp0 = KSTACKTOP-cpunum()*(KSTKSIZE+KSTKGAP); thiscpu->cpu_ts.ts_ss0 = GD_KD; thiscpu->cpu_ts.ts_eflags = 0; if (curenv && curenv->env_status == ENV_RUNNING) env_run(curenv); else sched_yield(); }
// Return the current time. static int sys_time_msec(void) { // LAB 6: Your code here. return time_msec(cpunum()); }