Skip to content

sk-io/x86-multitasking-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

x86-multitasking-example

Minimal 32bit x86 preemptive multitasking example with basic kernel/userspace separation.

As simple as possible. No paging/memory protection. Uses multiboot.

To build:

make

To build and run with qemu:

make run

How it works

Preemptive multitasking works by quickly switching the execution context between different tasks (or threads).

An execution context on x86 consists of registers like the instruction pointer (EIP), stack pointer (ESP), the general purpose registers (EAX, ECX, etc..), segment registers, and so on.

To perform a task switch between two contexts, we save the registers of the current context and then load in the registers of the next one.

You could copy these registers to variables but its faster to push/pop them in a set order to an appropriate stack. This is what most kernels do.

We're going to use the interrupt generated by the PIT to exchange these registers without the individual tasks ever noticing.

So, which stack should we use for this? Well, each task is going to need its own userspace stack for executing functions and such. However if we want separation between kernel and userspace, we probably shouldn't use this.

Instead, lets allocate a stack in kernel space for each task to use when switching tasks.

Another thing to note is that we do not have to push/pop all the registers to the stack. Some are automatically pushed by the interrupt(EIP, CS, EFLAGS, and optionally ESP and SS if transitioning from user to kernel mode), some are pushed by the cdecl convention(EAX, ECX, EDX).

So, the basic steps are as follows:

  1. Set up protected mode, initial stack, GDT, IDT, PIT and TSS
  2. When a PIT interrupt occurs:
    1. Decide on which task should run next
    2. Update ESP0 of the TSS. This determines which stack to use when going from user to kernel mode. (I.e. when interrupted)
    3. Push registers that we want to save
    4. Change stack pointer to the next tasks kernel stack
    5. Pop registers that we want to load (in reverse order to step 3)
    6. Return from the interrupt into the next task

You may notice a problem with this though. What happens if we attempt to switch to a newly created task? It'll work fine until step 5 where it tries to pop registers from an empty newly created kernel stack!

To fix this, we populate the kernel stack of the newly created task with data in the order that step 5 expects.

About

Minimal 32bit x86 Preemptive Multitasking Example

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published