Context switch

Process: an abstract virtual machine, as if it had its own CPU and memory, not accidentally affected by other processes.

Goals for solution:

Process states:

The per-cpu scheduler thread

scheduler() at proc.c

The context model in xv6

Note that xv6's model is just one example that actually works. Other models used by the real-world OSes can be similar or quite different.

How to understand the term thread here?

An example of context switch in xv6:

From the CPU's perspective (what's really happening):

From the process' perspective:

From the process' kthread's perspective:

From the scheduler thread's perspective:

The seemingly continuous execution flows in this model:

Isolation

What's in swtch()?

Who calls swtch()?

swtch() switches the stack of two kernel threads (or simply put, two kernel execution flows)

Why this context switch looks much simpler than the context switch between kernel and user mode?

As the kernel knows that both context are currently in the swtch() function, only those callee-saved registers really need to be saved. (recall "gentlemen's agreement")

Red zone

swtch() assumes the space below %rsp is freely available so it can save the current context by pushing to the stack.

C compiler may optimize leaf-node functions by directly using as much as 128 bytes below the %rsp. With this optimization, the %rsp doesn't need to be changed by the leaf-node function, thus saving a few instructions.

The original 32-bit xv6 turns off the red zone (which means data below %rsp can be freely clobbered by anyone) feature because the kernel want to directly use the user stack.

The current 64-bit xv6 kernel (the master branch) does not borrow user stack during syscall/interrupt handling. The red zone could be enabled.

A summary of execution flows in xv6-64

Per-CPU scheduler:

User process:

%fs's corresponding base address is set in seginit() -- wrmsr(0xC0000100, ((uint64) local) + (2048));

Documentation here.

per-CPU local storage:

cpu is at %fs:($-16)  -- declared first

proc is at %fs:($-8)

As the reference to the __thread proc is hard-coded in entry.S with offset == (-8), we can try to crash the kernel by simply reordering them.

Extended reading:

Example code for implementing user-space coroutines using the swtch function (co.c + swtch.S)

#include <stdio.h>

#include <stdlib.h>

typedef unsigned long u64;


extern void swtch(void **old, void *new);


void * stackptr_main;

void * stackptr_worker;


void

coworker(void)

{

  printf("worker 1\n");

  swtch(&stackptr_worker, stackptr_main);

  printf("worker 2\n");

  swtch(&stackptr_worker, stackptr_main);

}


int main(void)

{

  u64 * co_stack = malloc(8192);

  u64 * ptr = co_stack + 1000;

  ptr[6] = (u64)coworker; // q1


  printf("main 1\n");

  swtch(&stackptr_main, (void *)ptr);

  printf("main 2\n");

  swtch(&stackptr_main, stackptr_worker);

  printf("main exit\n");

  return 0;

}

To compile:

gcc co.c swtch.S