2016-08-21

2016-08-21 Multitasking

One of the last big hurdles to conquer before my kernel can have "v1.0" stamped on the packaging is that of multitasking.  There's a lot of theory to cover off and a specifics for the x86 architecture to grasp before it can be done, so ... to the Vroomster!

Context Switching


The biggest part of multitasking is being able to switch context.  This is when the processor decides to stop what it is doing, record the state of where it got to with the current task, fetching where it got to with a different task, then carrying on.
In order to do this, we need to store and restore the Processor Registers, the Stack, and the Memory Space,  These aren't all necessarily required, of course, restoring the registers can mean switching the stack or the memory space, and switching the memory space probably means you've replaced the stack.

So we need to make sure we understand which of these things we need to store and restore for specific circumstances, and how we get to them.

When we're switching context, we're likely to need to do this for two reasons; we are surrendering the context voluntarily (maybe because we're waiting for some IO operation before we continue), or an interrupt has happened (either a device requires attention, or the PIT has indicated that our time slice is up).

Registers


This is the biggest part of the context switch that we need to worry about, so let's make sure we don't miss anything.  We're a 32-bit operating system so the registers we need to worry about are:

  • General Purpose registers: EAX, EBX, ECX, EDX
  • Index Registers: ESI, EDI
  • Instruction Pointer: EIP
  • Flags: EFLAGS
  • Stack Registers: EBP, ESP
  • Floating Point / MMX registers: ST(0) to ST(7)
  • Segment Registers: CS, DS, ES, FS, GS, SS
  • Descriptor Table Registers: GDTR, IDTR, LDTR
  • Task Register: TR
  • Control Registers: CR0 to CR4, MSW
  • SME Registers: MXCSR, XMM0 to XMM7
  • Debug Registers (what are these?): DR0 to DR3


Next up, when might we need to persist these registers?  Well, there are two instances where persisting registers may be needed; when an interrupt occurs, or when the process surrenders the processor.

We'll look at the interrupts first because the interrupts persist some of the registers to the stack anyway, so it's quite likely that if we're going to have a function to surrender the processor, it's going to want to do exactly the same thing as the interrupt handler.  If we can achieve this, then we don't need a special function to reinstate a process that surrendered context differently to one that was pre-empted.

Looks like when an interrupt fires, the CPU persists the EFLAGS register, and the instruction pointer (EIP) as the return address.

I wonder if we can implement the "process surrendering control" as an interrupt itself?  Possibly, but as most process surrendering will be waiting for an IPC message, the process is going to be in the kernel anyway when it surrenders.  So most processes will be making a request of the kernel, the kernel will be suspending the process.

There's also the question of privilege level rings, which in the x86 world are from ring 0 (highest privilege kernel stuff) to ring 3 (lowest privilege userland).  When interrupts fire, the kernel has to change stacks and do all sorts of black magic (apparently) to maintain security.

I think maybe more research is needed on the basics of the processor architecture, particularly around interrupt handling between ring levels.  Maybe I'll put a pin in this and come back to it later.