struct thread { // other stuff, name, priority, etc // base of stack void *stack; // saved stack base, so it can be freed when the thread is destroyed void *esp; }; initial thread setup: void setup_thread(struct thread *t, void *entry_point) { uint *stack = malloc(STACKSIZE); uint *stacktop = stack + STACKSIZE/sizeof(uint); int i; stacktop--; *stacktop = (uint)entry_point; /* set the entry point so context_switch will ret to it */ for (i=0; i < 8; i++) { stacktop--; *stacktop = 0; /* initialize all registers to zero */ } t->esp = stacktop; t->stack = stack; } to reschedule: void reschedule() { thread *new_thread; thread *old_thread = current_thread; int interrupt_state; // disable interrupts on this cpu to keep this code block from being preempted. interrupt_state = disable_ints(); // find new thread new_thread = findnewthread(); if (new_thread == old_thread) { restore_ints(interrupt_state); return; } // swap stacks with the new thread and load its context i386_context_switch(&old_thread->esp, new_thread->esp); // restore the previous interrupt state restore_ints(interrupt_state); } /* void i386_context_switch(void **from_esp, void *esp) */ i386_context_switch: pusha // save all 8 general purpose regs on the stack lea 36(%esp),%eax movl esp,(%eax) // save the current esp -> *from_esp movl 40(%esp),%eax movl %eax,%esp // load esp from -> *esp popa // restore all 8 general purpose regs from the new stack ret // return to caller (usually the bottom part of reschedule() // note that the context switch code only saves the kernel state of the thread. There is // no attempt to try to save the user state directly. This isn't a problem because any // user thread would have already had to enter the kernel either through an irq or a syscall // so the user state is already saved on the kernel stack at the last interrupt frame that got it // into kernel space in the first place. interrupt glue: irq: push all regs set up kernel segments push irq number call handle_irq pop irq pop regs iret C level interrupt code: struct irq { int (*handler)(void *arg); void *args; }; struct irq irqs[NR_IRQS]; void handle_irq(int irq) { int irq_ret = 0; switch (irq) { case 0x20-0x40: // hardware IRQs if (irqs[irq - 0x20].handler) irq_ret = irqs[irq - 0x20].handler(irqs[irq - 0x20].args); // EOI the interrupt controller pic_EOI(); break; case SYSCALL: // XXX do syscall stuff here break; // other vectors (SMP, gpf, page fault, etc) } // handle preemption if (irq_ret != 0) { // if any handler returns != 0, then either the hw interrupt handler driver wants to // force a reschedule because some hw event woke up a thread // or it was a scheduler timer, which would have fired inside the irq handler for // the hardware timer reschedule(); } } timers: // systemwide timers are simply a queue of events sorted by scheduled time to fire. // the events are a function callback with an optional argument. The callbacks look // exactly like an irq so the return code from a timer has the same effect as the // return code from an irq handler. struct timer { timer *next; int (*handler)(void *); void *arg; time_t scheduled_time; }; struct timer *timer_queue; // timer interrupt handler, registered on the PIT (or local apic timer) handler int do_timer_irq(void *unused) { int ret = 0; while (head of timer queue has expired) { timer = pop_head(); // any != 0 return from a timer handler will carry up // to the interrupt glue. ret |= timer->handler(timer->args); } return ret; } thread sleep: int thread_wakeup(void *arg) { struct thread *t = (struct thread *)arg; insert into run queue(t); return 1; // force a reschedule } void thread_sleep(time_t delay) { struct timer; int int_state = disable_ints(); queue_timer(&timer, delay, &thread_wakeup, (void *)current_thread); set thread state to sleep(current_thread); // since the thread wasn't put back in the run queue, a reschedule here will suspend // the current thread until the thread_wakeup timer event is called. reschedule(); restore_ints(int_state); }