ยง6.3Problem #2: Switching Between Processes

Part I OSTEP pp. 55โ€“58 ยท ~8 min read

  • yield
  • timer interrupt

The trap machinery of the previous section has one hole: it only gives the OS control when the process asks (a system call) or slips (an exception). If a process is running, then โ€” by definition โ€” the OS is not. And if the OS is not running, it can do exactly nothing. Almost philosophical, and very real:

The Crux: How To Regain Control Of The CPU

How can the operating system regain control of the CPU so that it can switch between processes?

A cooperative approach: wait for system calls

Some early systems (old Macintosh OS, the Xerox Alto) simply trusted processes: run for a while, then be a good citizen and give the CPU back. Most processes hand over control frequently anyway โ€” every system call does it โ€” and such systems often add an explicit yield() call whose only job is to transfer control to the OS. Misbehavior also helps: divide by zero or touch bad memory, and the resulting trap gives the OS the CPU (and a process to terminate).

The flaw is easy to spot. What if a process โ€” buggy or malicious โ€” lands in an infinite loop and never traps? In a cooperative world the OS has exactly one recourse, the age-old fix for everything:

Tip: Reboot is useful (really)

Donโ€™t scoff. Rebooting moves software back to a known, better-tested state; it reclaims leaked or stale resources; and itโ€™s easy to automate โ€” large cluster services deliberately reboot machines in rotation for exactly these reasons. Still: needing a reboot because one process loops is not an acceptable OS design.

A non-cooperative approach: the OS takes control

The Crux: How To Gain Control Without Cooperation

How can the OS gain control of the CPU even if processes are not being cooperative? What can it do to ensure a rogue process does not take over the machine?

The answer, discovered decades ago, is beautifully simple: a timer interrupt . A timer device raises an interrupt every so many milliseconds; when it fires, the running process is halted by the hardware, and a pre-configured OS handler runs. The OS has the CPU back โ€” free to stop the current process and start another. At boot, the OS registers the timer handler (alongside the syscall handler) and starts the timer โ€” both privileged, naturally. From then on the OS runs user programs with total confidence: control returns within X milliseconds, no cooperation required. (The hardware saves the interrupted programโ€™s state just as it does for a trap โ€” enough that a later return-from-trap resumes it perfectly.)

Saving and restoring context

Control regained (politely via trap, or forcibly via timer), the scheduler makes its decision: keep running the current process, or switch. If it says switch, the OS runs the context switch โ€” conceptually simple: save a few registers of the outgoing process, restore those of the incoming one, so that the eventual return-from-trap lands in the other program. Step through the full protocol:

Figure 6.3: Limited Direct Execution Protocol (Timer Interrupt)
OS(kernel mode)
Hardware
Program(user mode)
Phase 1 โ€” at boot
Phase 2 โ€” running (A is on the CPU)
step 1 / 17 ยท time flows downward

As before โ€” but now the OS registers TWO handlers: the syscall handler and the timer handler.

The subtlest point deserves its own picture: two different register save/restores happen, by two different actors, into two different places โ€”

Process Auser registers(PC, flags, โ€ฆ)k-stack(A)Aโ€™s kernel stacksaved user regs of Apopped by return-from-trapsave #1by HARDWARE, implicitly,at the timer interruptproc_t(A)Aโ€™s process structuresaved kernel regs of Arestored at Aโ€™s next turnsave #2by the OS, explicitly,inside switch()โ€ฆand the mirror image for B:switch() restores Bโ€™s kernel regs from proc_t(B) and switches to k-stack(B) โ€”then return-from-trap pops Bโ€™s user regs from k-stack(B). The system moves from โ€œjust trapped from Aโ€ to โ€œjust trapped from B.โ€

The two saves: hardware implicitly saves user registers onto the kernel stack at interrupt time; the OS explicitly saves kernel registers into the process structure during switch(). Mixing these up is the classic way to misunderstand context switches.

And because a picture of assembly is worth a thousand words of it, here is the real thing โ€” xv6โ€™s context switch (Figure 6.4). By switching stacks mid-routine, swtch is entered in the context of one process and returns in the context of another:

# void swtch(struct context **old, struct context *new);
#
# Save current register context in old
# and then load register context from new.
.globl swtch
swtch:
  # Save old registers
  movl 4(%esp), %eax # put old ptr into eax
  popl 0(%eax)        # save the old IP
  movl %esp, 4(%eax) # and stack
  movl %ebx, 8(%eax) # and other registers
  movl %ecx, 12(%eax)
  movl %edx, 16(%eax)
  movl %esi, 20(%eax)
  movl %edi, 24(%eax)
  movl %ebp, 28(%eax)

  # Load new registers
  movl 4(%esp), %eax # put new ptr into eax
  movl 28(%eax), %ebp # restore other registers
  movl 24(%eax), %edi
  movl 20(%eax), %esi
  movl 16(%eax), %edx
  movl 12(%eax), %ecx
  movl 8(%eax), %ebx
  movl 4(%eax), %esp # stack is switched here
  pushl 0(%eax)       # return addr put in place
  ret                 # finally return into new ctxt

Figure 6.4: The xv6 Context Switch Code โ€” the old and new context structures live in the two processesโ€™ process structures (the struct context you met in chapter 4).

Tip: Dealing with application misbehavior

Malicious or buggy, a process that tries something it shouldnโ€™t gets one standard treatment in modern systems: termination. One strike and youโ€™re out โ€” brutal, perhaps, but what else should the OS do when you access memory illegally or execute an illegal instruction?

Check yourself

1.Under the cooperative approach, how does the OS ever get the CPU back โ€” and what is the fatal flaw?

2.Why does the timer interrupt solve the control problem even for a hostile, never-trapping process?

3.During the Figure 6.3 protocol, registers get saved twice. Who saves what, where?

4.xv6's swtch is entered by Process A's kernel context but RETURNS into Process B's. What makes that possible?

5.Predict: after switch() finishes and the OS executes return-from-trap, what runs next?

5 questions