logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

Process-scheduling-in-KnightOS.md (6386B)


  1. ---
  2. date: 2014-09-02
  3. # set tw=80
  4. layout: post
  5. title: Process scheduling and multitasking in KnightOS
  6. tags: [KnightOS, kernel hacking]
  7. ---
  8. I'm going to do some blogging about technical decisions made with
  9. [KnightOS](http://knightos.org). It's an open-source project I've been working
  10. on for the past four years to build an open-source Unix-like kernel for TI
  11. calculators (in assembly). It's been a cool platform on top of which I can
  12. research low level systems concepts and I thought I'd share some of my findings
  13. with the world.
  14. So, first of all, what is scheduling? For those who are completely out of the
  15. loop, I'll explain what exactly it is and why it's neccessary. Computers run on
  16. a CPU, which executes a series of instructions in order. Each core is not
  17. capable of running several instructions concurrently. However, you can run
  18. hundreds of processes at once on your computer (and you probably are doing so
  19. as you read this article). There are a number of ways of accomplishing, but the
  20. one that suits the most situations is *preemtive multitasking*. This is what
  21. KnightOS uses. You see, a CPU can only execute one instruction after another,
  22. but you can "raise an interrupt". This will halt execution and move to some
  23. other bit of code for a moment. This can be used to handle various events (for
  24. example, the GameBoy raises an interrupt when a button is pressed). One of
  25. these events is often a timer, which raises an interrupt at a fixed interval.
  26. This is the mechanism by which preemptive multitasking is accomplished.
  27. Let's say for a moment that you have two programs loaded into memory and
  28. running, at addresses 0x1000 and 0x2000. Your kernel has an interrupt handler
  29. at 0x100. So if program A is running and an interrupt fires, the following
  30. happens:
  31. 1. 0x1000 is pushed to the stack as the return address
  32. 2. The program counter is set to 0x100 and the interrupt runs
  33. 3. The interrupt concludes and returns, which pops 0x1000 from the stack and
  34. into the program counter.
  35. Once the interrput handler runs, however, the kernel has a chance to be sneaky:
  36. 1. 0x1000 is pushed to the stack as the return address
  37. 2. The program counter is set to 0x100 and the interrupt runs
  38. 3. The interrupt removes 0x1000 from the stack and puts 0x2000 there instead
  39. 3. The interrupt concludes and returns, which pops 0x2000 from the stack and
  40. into the program counter.
  41. Now the interrupt has switched the CPU from program A to program B. And the
  42. next time an interrupt occurs, the kernel can switch from program B to program
  43. A. This event is called a "context switch". This is the basis of preemptive
  44. multitasking. On top of this, however, there are lots of ideas around which
  45. processes should get CPU time and when. Some systems have more complex
  46. schedulers, but KnightOS runs on limited hardware and I wanted the context
  47. switch to be short and sweet so that the running processes get as much of the
  48. CPU as possible. I'll explain the simple KnightOS scheduling algorithm here.
  49. First, its goals:
  50. * Short and simple context switches
  51. * Ability to suspend processes when not in foreground
  52. * Ability to run background processes
  53. What KnightOS uses is a simple round robin with the ability to suspend threads.
  54. That is, we have a list of processes and then some flags, among which is
  55. whether or not the processes is currently suspended. So say we have this list
  56. of processes in memory:
  57. * 1: PC=0x2000, not suspended
  58. * 2: PC=0x2000, not suspended
  59. * 3: PC=0x2000, suspended
  60. * 4: PC=0x2000, not suspended
  61. As process 1 is running and an interrupt fires, the kernel looks at this table
  62. and picks the next non-suspended process to run - process 2. On the next
  63. interrupt, it does it again, skipping process 3 and giving time to process 4.
  64. To actually implement this, we have to think about the stack. KnightOS runs on
  65. z80 processors, which have a single stack and a shared memory space. The CPU
  66. uses the PC register to keep track of which address the current instruction is
  67. at. That is, say you compile this code:
  68. ```
  69. ld a, 10
  70. inc a
  71. ld (hl), a
  72. ```
  73. This compiles to the machine code 3E 0A 3C 77. Say we load this program at
  74. 0x8000 - then 0x8000 will point to `ld a, 10`. When the CPU finishes executing
  75. this instruction, it advances PC to 0x8002 (since `ld a, 10` is a two-byte
  76. instruction). The next instruction it executes will be `inc a`, and then PC
  77. advances to 0x8003.
  78. The stack is used for a lot of things. It can be used to save values, and it is
  79. used to call subroutines. It is also used for interrupts. It's like the same
  80. stacks you use in higher level applications, but it's at a very low level. When
  81. an interrupt fires, the current value of PC is pushed to the stack. Then PC is
  82. set to the interrupt routine, and then when that's done the top of the stack is
  83. removed and placed into PC (effectively returning control to the original
  84. location). However, since the stack is used for much more than that, we have
  85. additional things to consider.
  86. In KnightOS, when a new process starts, it's allocated a stack in memory and
  87. the CPU's stack pointer (SP) is set to its address. When an interrupt happens,
  88. we need to change the stack to point at some other process so it has time to
  89. run (since that's where its PC is). However, we need to make sure that the
  90. first processes stack is left intact. Since we allocate a new stack for the
  91. next process, we can simply change SP to that processes stack. This will leave
  92. behind the value of PC that was pushed during the interrupt for the previous
  93. process, and lo and behlod a similar value of PC is waiting on top of the other
  94. processes stack.
  95. So that's it! We do a simple round robin, skipping suspended processes and
  96. following the procedure outlined above to switch between them. This is how
  97. KnightOS shares one CPU with several "concurrent" processes. Operating systems
  98. like Linux use more complicated schedulers with more interesting theory if
  99. you'd like some additional reading. And of course, since KnightOS is open
  100. source, you may enjoy reading all of our code for handling this stuff (in
  101. assembly):
  102. [Context switching](https://github.com/KnightOS/kernel/blob/master/src/00/interrupt.asm)
  103. [Stack allocation during process creation](https://github.com/KnightOS/kernel/blob/master/src/00/thread.asm#L72)
  104. We're hanging out on #knightos on Freenode if you want to chat about cool
  105. low-level stuff like scheduling and memory management.