7.2 Code: Context switching
Figure 7.1 outlines the steps involved in switching from one user process to another: a trap (system call or interrupt) from user space to the old process’s kernel thread, a context switch to the current CPU’s scheduler thread, a context switch to a new process’s kernel thread, and a trap return to the user-level process. Xv6 has separate threads (saved registers and stacks) in which to execute the scheduler because it is not safe for the scheduler to execute on any process’s kernel stack: some other CPU might wake the process up and run it, and it would be a disaster to use the same stack on two different CPUs. There is a separate scheduler thread for each CPU to cope with situations in which more than one CPU is running a process that wants to give up the CPU. In this section we’ll examine the mechanics of switching between a kernel thread and a scheduler thread.
Switching from one thread to another involves saving the old thread’s CPU registers, and restoring the previously-saved registers of the new thread; the fact that the stack pointer and program counter are saved and restored means that the CPU will switch stacks and switch what code it is executing.
The function
swtch
saves and restores registers for a kernel thread switch.
swtch
doesn’t directly know about threads; it just saves and
restores sets of RISC-V registers, called
contexts.
When it is time for a process to give up the CPU,
the process’s kernel thread calls
swtch
to save its own context and restore the scheduler’s context.
Each context is contained in a
struct
context
(kernel/proc.h:2),
itself contained in a process’s
struct proc
or a CPU’s
struct cpu
.
swtch
takes two arguments:
struct context
*old
and
struct
context
*new
.
It saves the current registers in
old
,
loads registers from
new
,
and returns.
Let’s follow a process through
swtch
into the scheduler.
We saw in Chapter 4
that one possibility at the end of an interrupt is that
usertrap
calls
yield
.
yield
in turn calls
sched
,
which calls
swtch
to save the current context in
p->context
and switch to the scheduler context previously saved in
cpu->context
(kernel/proc.c:506).
swtch
(kernel/swtch.S:3)
saves only callee-saved registers;
the C compiler generates code in the caller to save
caller-saved registers on the stack.
swtch
knows the offset of each
register’s field in
struct context
.
It does not save the program counter.
Instead,
swtch
saves the
ra
register,
which holds the return address from which
swtch
was called.
Now
swtch
restores registers from the new context,
which holds register values saved by a previous
swtch
.
When
swtch
returns, it returns to the instructions pointed to
by the restored
ra
register, that is,
the instruction from which the new thread previously
called swtch
.
In addition, it returns on the new thread’s stack,
since that’s where the restored sp
points.
In our example,
sched
called
swtch
to switch to
cpu->context
,
the per-CPU scheduler context.
That context was saved at the point in the past when
scheduler
called
swtch
(kernel/proc.c:466)
to switch to the process that’s now giving up the CPU.
When the
swtch
we have been tracing returns,
it returns not to
sched
but to
scheduler
,
with the stack pointer in the current CPU’s
scheduler stack.