mittos64/doc/10_Threading.md
2018-03-29 19:36:33 +02:00

2.5 KiB

Threading

In this chapter we'll implement context switching and a simple scheduler.

Context switching

Switching from one thread to another is actually really easy. All you need to do is replace everything in every processor register at the same time, and you're good to go. Ok... that doesn't sound so simple, but we get some help.

In the conventions used by our gcc cross compiler (known as System V ABI), when calling a function only a few registers are guaranteed to be preserved. Those are rbx, rsp, rbp, r12, r13, r14 and r15. The rest can not be assumed to retain the same value.

So, if we make our context switch out to look like a function call, we only need to replace those seven registers. This can easily be done with a small asm routine:

src/kernel/proc/swtch.S

.intel_syntax noprefix

.global switch_stack
switch_stack:
  push rbp
  mov rbp, rsp

  push r15
  push r14
  push r13
  push r12
  push rbx
  push rbp

  mov [rdi], rsp
  mov rsp, [rsi]

  pop rbp
  pop rbx
  pop r12
  pop r13
  pop r14
  pop r15

  leaveq
  ret

This pushes all registers, writes the stack pointer to the address passed as the first argument, reads a new stack pointer from the address in the second argument, and pops all registers. Since the return address is on the stack, the ret instruction will return to the new thread.

A note on credit

Not everything I present here is my own original ideas. In fact, most of it probably isn't. I've been itterating my kernel design from the ground up a dozen times or more through the last ten years, and where I picked up methods and ideas have gotten lost along the way.

This method of switching threads, though, I know I got from XV6, where it may or may not have originated.

I'm sorry I can't always give proper and detailed credit to the giants on whose shoulders I stand, but for a list of my most significant sources of inspiration through the years, see Chapter 0.

Threads

Ok, so now switching between two threads of execution only requires:

switch_stack(&old_stack_ptr, &new_stack_ptr);

So the next step would be to have a structured and reliable way of keeping track of stacks and stack pointers. The stack pointer is easy enough, and we might as well store some more thread related stuff at the same place while we're at it.

src/kernel/include/thread.h

struct thread
{
  uintptr_t stack_ptr;
  uint64_t tid;
  uint64_t state;
};

Queueing