Context Switch from XV6

Flow of the Context Switch

Before Context Switch Interrupt happens

vector 32:
pushl $0 //error code
pushl $32 //vector number of timer interrupt
jmp alltraps
status after pushing vector to kernel stack
 # trap frame.globl alltrapsalltraps:
pushl %ds #data segment register
pushl %es #extra segment register
pushl %fs #general segment register
pushl %gs #general segment register
pushal #all local registers
# Set up data segments.
movw $(SEG_KDATA<<3), %ax
movw %ax, %ds
movw %ax, %es
# Call trap(tf), where tf=%esp by calling convention
pushl %esp
call trap
stack status before context switch ( Recall: calling convention will generate EIP(alltraps, trap, yield) as return address before calling )

Context Switch starts !

// defs.h 129
void swtch(struct context** old, struct context* new);
swtch:
movl 4(%esp) %eax # address of **old !!little endian
movl 8(%esp) %edx # address of *new
#save all callee registers
pushl %ebp
pushl %ebx
pushl %esi
pushl %edi
# change esp pointer
movl %esp, (%eax) # (%eax) means *old (points to address of old)
movl %edx, %esp
# load new callee-save registers
# do not pop the EIP since this is the return address
popl %edi
popl %esi
popl %ebx
popl %ebp
ret
// reverse order from pushl registers
struct context {
uint edi;
uint esi;
uint ebx;
uint ebp;
uint eip;
}
Status after moving ESP to new context

CPU initialization

// main.c 39
mpmain(); // finish this processor's set up
// Common CPU setup code
static void
mpmain(void)
{
cprintf("cpu%d: starting %d\n", cpuid(), cpuid());
idtinit(); // load idt register
xchg(&(mycpu()->started), 1); // tell startothers() we're up
scheduler(); // start running processes
}
// proc.c 326
void
scheduler(void)
{
...
// inside loop of the ptable
acquire(&ptable.lock);
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
if(p->state != RUNNABLE)
continue;
// switch to chosen process
c->proc = p;
switchuvm(p);
p->state = RUNNING;

// do context switch
// c->scheduler points to current context & restore the context of p
swtch(&(c->scheduler), p->context );
switchkvm();
c->proc = 0;
}
}

How context is created?

static struct proc* 
allocproc(void)
{
// after finding an UNUSED process
found:
p->state = EMBRYO;
p->pid = nextpid++;

release(&ptable.lock);
// Allocate kernel stack.
if((p->kstack = kalloc()) == 0){
p->state = UNUSED;
return 0;
}
// stack pointer
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 -= sizeof *p->context;
p->context = (struct context*)sp;
memset(p->context, 0, sizeof *p->context);
// that's why it is not required to push eip in swtch.S
p->context->eip = (uint) forkret;
return p; }

Dig in more on Context Switch

swtch(&p->context, mycpu()->scheduler);
status before context switch
# move esp value to what %eax points to
movl %esp, (%eax) # (%eax) means *old (points to address of old)
movl %edx, %esp
save p’s context
movl %edx, %esp
ESP points to new Context

Context Switch Flow

After Context Switch

stack status before returning back to user level
stack after trap() returns

Summary

References

Site Reliability Engineer in Greater LA