Context Switch from XV6

Brian Pan
5 min readMar 7, 2018

--

Flow of the Context Switch

  1. Invoke the interrupt (ex: Timer Interrupt (trap.c 109) )
  2. Invoke the scheduler ( yield (proc.c 390) )
  3. Start the context switch(swtch (proc.c 387) )

Before Context Switch Interrupt happens

Vector.S(Generate from perl script)

vector 32:
pushl $0 //error code
pushl $32 //vector number of timer interrupt
jmp alltraps
status after pushing vector to kernel stack

trapasm.S

Ref: some registers references

Segment register saves physical address of the segment offset

 # 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

# 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

Then, in trap.c 111, calling yield() to invoke scheduler.

Also, after invoking scheduler, the system starts context switch said before in swtch(struct context **old, struct context *new)

stack status before context switch ( Recall: calling convention will generate EIP(alltraps, trap, yield) as return address before calling )

Context Switch starts !

Context switch writes in assembly and links by linker !

// defs.h 129
void swtch(struct context** old, struct context* new);

swtch.S

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

Remind: Calling convention on stack

  • local variables | EBP(frame pointer) | return address | parameters

Context data structure(proc.h 28)

// reverse order from pushl registers
struct context {
uint edi;
uint esi;
uint ebx;
uint ebp;
uint eip;
}

Remind: Context is always on the top of some stack

Status after moving ESP to new context

CPU initialization

main.c

// 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
}

What does xchg(volatile uint *addr, uint newval) do?

xchg did a value exchange with the value store in addr, this uses for lock implementation.

Sample from MIT course:

xchg %eax, addr

  1. free other CPU’s activities to access address of addr
  2. tmp := *addr
  3. *addr := %eax
  4. %eax := tmp
  5. Unfreeze other CPU’s activities toward address of addr

Back to our topic, focus on function scheduler():

What it does is after initializing all requirements we need for XV6, it launches one proc from current context(cpu->scheduler)

proc.c

// 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;
}
}

Then, we know the context switch to first process to run.

However, how does XV6 prepare context for process?

How context is created?

Well, when first user process created in userinit() or doing the process fork(), both of them invoked allocproc to find an UNUSED proc in ptable

static struct proc* allocproc(void) (proc.c 75)

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

Discuss about switch from current process to scheduler when time interrupt happens !

void sched(void) (proc.c 371)

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

Let stack pointer points to the context we want to switch. Here is the address of the cpu->schedule

movl %edx, %esp
ESP points to new Context

Now, we should know the whole context switch flow !

Context Switch Flow

  1. Interrupt the current process. Then, switch to cpu->scheduler
  2. Scheduler find a RUNNABLE proc in ptable and context switch from scheduler to another RUNNABLE process

Process -> Scheduler -> Process

The scheduler is the bridge between two processes

The next is how to return back to the user space?

After Context Switch

Destination: Exit back to User-Level

stack status before returning back to user level

Strategy: Calling Convention ! Use EIP to get the return address

switch.S -> sched(proc.c) -> yield(proc.c) -> trap(trap.c) -> trapret(trapasm.S, but set up in allocproc() ) -> iret (trapasm.S)

stack after trap() returns

Remind: Why should XV6 push ESP onto the stack ?

Ans: As a parameter for void trap(struct trapframe *tf)

Summary

In this post, summarized the flow of the Context Switch in a small system XV6.

It is strait-forward to understand it. Think about every process has its own private space for storing data including registers, stacks. Then, the Context Switch is the mechanism to prepare the resources for CPUs when we want to finish the job from different processes.

Also, the scheduler is the bridge to help processes to do so.

References

  1. brilliant OS course by Anton Burtsev in UC Irvine
  2. MIT 6.828 Operating System Engineering

--

--