Contexts, invariants, and process exit
- What is a Chickadee context?
- What kinds of context are there?
- What invariants must hold?
Context
- A context is a logical thread of program control
- Specifically, registers and a stack
Interruptibility
- Interruptible contexts
- Might give up control voluntarily (voluntary context switch)
- Might experience a hardware interrupt and give up control involuntarily (involuntary context switch)
- Non-interruptible contexts
- Can only give up control voluntarily
Privilege
- Privileged contexts
- Run with full CPU privilege
- Unprivileged contexts
- Run without full CPU privilege
Permanence
- Permanent contexts
- Always available
- Dynamic contexts
- Can be created and destroyed
Suspension
- Suspendible contexts
- Can give up control & pick up right where they left off
- Non-suspendible contexts
- Start from scratch (i.e., fresh stack) every time they run
WeensyOS contexts
- Process context
- Interruptible, unprivileged, dynamic, suspendible
- Kernel context
- Non-interruptible, privileged, permanent, non-suspendible
Chickadee contexts
-
CPU context
-
Kernel task context
-
User context
-
Interruptible? Privileged? Permanent? Suspendible?
Chickadee contexts
- CPU context
- Non-interruptible, privileged, permanent, non-suspendible
- One per CPU
- Kernel task context
- Mostly non-interruptible, privileged, mostly dynamic, suspendible
- One per kernel task
- Examples of dynamic and permanent kernel tasks?
- User context
- Interruptible, unprivileged, dynamic, suspendible
- One per process
- Each corresponds to a kernel task
Current context
-
What context is currently running?
-
CPU? Kernel task? User?
-
What hardware support is necessary?
Current context lookup
- Current CPU context
this_cpu()
- Requires interrupts disabled
- Current kernel task context
current()
- Current user context
sys_getpid()
%gs
and current context
- In kernel mode, the
GSBASE
register is the high-canonical address of this CPU’scpustate
- So different CPUs have different
GSBASE
s
// this_cpu
// Return a pointer to the current CPU. Requires disabled interrupts.
inline cpustate* this_cpu() {
assert(is_cli());
cpustate* result;
asm volatile ("movq %%gs:(0), %0" : "=r" (result));
return result;
}
// current
// Return a pointer to the current `struct proc`.
inline proc* current() {
proc* result;
asm volatile ("movq %%gs:(8), %0" : "=r" (result));
return result;
}
struct __attribute__((aligned(4096))) cpustate {
// These three members must come first: they are referenced in assembly
cpustate* self_;
proc* current_ = nullptr;
uint64_t syscall_scratch_;
...
- Why does
this_cpu()
require interrupts be disabled?
Context switching
- A CPU switches from one context to another
- May require saving and restoring state
- Goal: Save and restore the minimal viable state
- State manipulation can be expensive; in common cases, saving and restoring all state is unnecessary
- Hardware tries to provide minimal primitives
- Operating systems generally save & restore far more than minimal state, for safety and programmability reasons
How are context switches performed?
- Involuntary context switches
- Exceptions: traps, faults, interrupts
- CPU hardware switches to a new stack at a preconfigured address
- (Unless the CPU was already on that stack)
- Stores old
%rip
,%cs
,%rflags
,%rsp
,%ss
on that stack
- Voluntary context switches
Privileged voluntary context switches 1
- Kernel task → CPU
proc::yield()
- Prepares a
yieldstate
in assembly for resumption- Only callee-saved registers and flags!
- Stored on the kernel task stack
- Pointer to
yieldstate
stored at fixed location inproc
- Also serves as stack pointer at resumption time
- Loads CPU context in assembly (
proc::yield_noreturn
)
Privileged voluntary context switches 2
- CPU → kernel task
set_pagetable
+proc::resume
- Does not prepare resumption state for CPU context, because CPU context is not suspendible
- Changes stack pointer to
yieldstate
- Pops registers and returns to caller of
proc::yield
Privileged voluntary context switches 3
- CPU → CPU
- Impossible
- Kernel task → kernel task
- Does not happen
- Always bounces through CPU context for scheduling
Unprivileged voluntary context switches 1
- User → kernel task
syscall
instruction- CPU hardware sets
%rcx := next %rip
,%r11 := %rflags
, and loads%cs
and%ss
with kernel-configured values- Really trying to change minimal registers!
- Then sets
%rip := IA32_LSTAR
(a privileged machine register), to: syscall_entry
, which saves other registers- What about the stack pointer?
Unprivileged voluntary context switches 2
- Kernel task → user
- Returning to
exception_entry
orsyscall
- Returning to
- CPU → user
set_pagetable
+proc::resume
- Applies when kernel task has
regs_
, notyields_
- User → user
- Impossible
- User → CPU
- Impossible
cpustate::schedule
- The only function that runs in CPU context
- Selects a task to run and runs it
- Manages a run queue, which is a doubly-linked list of kernel tasks
- Chooses a runnable kernel task and runs it
- That kernel task might correspond to a user context, so resuming the kernel task might switch to a user context
Requirements and invariants
cpustate::runq_lock_
protects the run queue- Must acquire that lock to enqueue or dequeue tasks
cpustate::current_
is part of the run queue, but cannot be modified outsidecpustate::schedule
current_
must identify the currently running kernel task- One CPU can’t change what another CPU is doing!
- The memory containing a runnable context cannot be free
proc
cpustate
- The page table in force while a context is running cannot be free
proc