Preemptive Multitasking (ARM)
Jump to navigation
Jump to search
This page documents how Embedded Xinu implements preemptive multitasking on ARM-architecture platforms that it has been ported to, such as the Raspberry Pi.
Thread context
The format and size of the thread context used for ARM ports of Embedded Xinu rests on two factors: the registers available on the ARM architecture, and the standard ARM calling convention[1]:
- ARM processors have 16 "general-purpose" registers numbered r0-r15, as well as the Current Program Status Register (cpsr). However, despite being considered "general-purpose" registers, r13-r15 actually have special purposes; namely, r13 is used as the stack pointer (sp), r14 is used as the link register (lr), and r15 is used as the program counter (pc).
- The standard ARM calling convention specifies which registers are callee-save (r4-r11, r13-r14) and which are caller-save (r0-r3, r11), as well as how arguments are passed to procedures (up to four arguments in r0-r3; additional arguments spill onto stack).
The thread context we chose is 15 words long and is the following:
TODO
We reproduce the ctxsw()
code below, since it only contains a few instructions and has a detailed explanation.
/*------------------------------------------------------------------------ * ctxsw - Switch from one thread context to another. *------------------------------------------------------------------------ * * This is the ARM version. How it works: we have to save r4-r11 and lr, since * r4-r11 are callee-save and lr needs to be loaded into the pc when this * context is switched to again. Registers r0-r3 are caller-save so they do * *not* need not be saved, but they are pushed anyway to leave space since they * are part of the context constructed by create() to pass thread arguments. * * When restoring a context, we pop both the lr and pc. These are both set to * appropriate values in create(). But when saving a context below, we only * have an appropriate value for pc--- namely, the lr, a.k.a. the address * ctxsw() will return to. The lr at that instruction is unknown. However, * this is irrelevant because the lr is caller-save, and we can simply push a * garbage value from r13 instead. * * We almost don't need to do anything about the CPSR here, since: * * - We do all our context switches to/from the same mode (namely, SYS mode). * - The ARM ABI does not expect comparison flags in the CPSR to be preserved * across function calls. * - resched() takes care of saving/restoring whether interrupts are enabled * or not when resuming a thread that has been switched out. * - Xinu never makes changes to the CPSR not already covered above, such as * switching to executing Thumb instructions. * * However, interrupts are disabled when ctxsw() is called from resched(), but * we want interrupts to be enabled when starting a *new* thread, which * resched() does not take care of. We solve this by including the control bits * of the current program status register in the context and adding a line of * code to create() that sets the control bits of new threads such that * interrupts are enabled. *------------------------------------------------------------------------*/ ctxsw: mrs r12, cpsr push {r0-r14} str sp, [r0] ldr sp, [r1] pop {r0-r12} msr cpsr_c, r12 pop {lr, pc}
Preemption
The means of generating a timer interrupt for preemption is not standard to the ARM architecture; instead, software must make use of a board-specific or chip-specific device such as the BCM2835 System Timer.