Context Switching: Balance and Control

We’ve just spent a bunch of time talking about how the OS and hardware (CPU/cache/RAM) work together to virtualize memory, the gist being: we split the address space of our process into pages, and page frames, then use page frames and page tables, along with the TLB, to quickly access sections of process memory.

So we can virtualize memory now! But there’s another big virtualization topic we’ve left untouched, which is the virtualization of the CPU. We’ve talked about how different processes are context switched in/out of the CPU, and the implications that has for memory… but how does the OS decide when to perform a context switch, and which processes should be swapped in/out? This is what the CPU scheduler is all about!

There are two things that our solution needs to take into account.

First, we must swap processes in/out with the right balance of time. If the OS doesn’t context switch often enough, we aren’t effectively utilizing the CPU. If the OS context switches TOO often, then all of the overhead of saving the current state of our process (to be switched out) and loading in a new process (to be switched in) will start to become cumbersome.

Second, the OS must be the sole controller of when to initiate context switches. A process shouldn’t be able to intentionally hog the CPU for itself, leaving other processes out in the rain. A process can have a heavy workload, sure, and that COULD cause the OS to lavish it with CPU time, slowing down the system overall. But a process shouldn’t be able to lock up the CPU completely for itself, keeping everyone else from ever getting a turn. This means the OS must have sole control over when to perform context switches.

Processor Modes

When the instructions of a process are being executed on the CPU… how does that work? Seems like an odd and straightforward question, but one that’s worth answering!

The most straightforward way for a process to run on the CPU is… for the process to just RUN on the CPU. Meaning, the processes’ instructions are loaded into the CPU, one at a time, and executed. Neat! So assigning variables, iterating loops, evaluating if statements, calling functions… all of that stuff just HAPPENS by executing the corresponding instructions on the CPU.

But what happens if a process wants access to system resources? What about requesting more heap memory, or writing to a file? We’ve said that the OS manages those resources, so what happens when a process needs access to them?

Before answering this question, there’s one thing we should put out in the open, so we’re all on the same page. Consider for a moment a single-core processor. If that CPU is executing the instructions of a process (MS Word, Minecraft, Firefox, etc.), that means the CPU is NOT executing any other instructions… meaning the OS/kernel is NOT running! The normal/user process is the only thing currently running on the CPU!

So if a process requests a system resource, how does the OS grant access to that resource if it isn’t running when a process ASKS for it? And another interesting question arises from this fact: if the kernel isn’t running , how does the OS prevent processes from doing something dumb or malicious?

To facilitate the ability for the OS to regain control, we need to introduce processor modes. All operating systems are different, but there are generally two processor modes: user mode and kernel mode (sometimes called privileged mode). These modes exist on the processor itself, in a special register. This register is used to track which mode the processor is operating in.

So what’s the difference between the two modes? In kernel mode, instructions that are being executed on the CPU can do whatever they want. System resources (like files or different sections of RAM) can be read from/written to, and restricted instructions (to change processor state, run kernel code, etc) can be used. In user mode, code is restricted in what it can do. The process can run normal instructions (loops, variable creation, etc.) but anything that accesses system resources can’t happen.

Wait so… what happens if a process is operating in user mode, and it issues a request to open a file? If the process isn’t ALLOWED to do that, then what actually happens under the hood? This is where system calls come in!

Control

Let’s address our second issue, that of control. How does the OS/kernel maintain control over the running processes’ use of the CPU, so context switches can be done effectively? Or, said another way: if a regular process is using the CPU, how/when does the kernel butt back in to start running its own code? Pretend that this is a single core CPU: if a process has its instructions being executed by the CPU at a moment in time, the kernel/OS is NOT currently being run at all! So… how does the kernel regain control?

We again need help from the hardware side to allow the OS to do its job. The solution is for the CPU to use something called an interrupt timer. Every so often a special timer on the CPU will trigger, halting the currently executing process. The CPU then executes a pre-configured section of kernel code, called an interrupt handler, giving control back to the kernel.

For the CPU and OS to perform and interrupt correctly the CPU must know WHERE this special bit of code (the kernel code for handling the interrupt) lives. Whenever the OS/kernel is booting up, it informs the CPU of where this code lives in memory.