Posts: 21
Joined: Sat Dec 10, 2011 11:54 pm

Implementing tasks

Mon Nov 12, 2012 12:02 am

I'm trying to do a basic implementation of tasks in supervisor mode (no MMU concerns yet). I have the goal of implementing something similar to channels as they exist in the go language. I have a task structure that holds a stack pointer. When I shift from task to task I just push state onto the stack pointer, and pop off the new stack pointer. I have a global pointer called current_task which points the currently active task.

So I'm wondering what to do when one gets an interrupt. Instead of returning to the executing task, I want to jump to my yield function. My yield function will then select the next task to run and switch to it.

Is there a way to return from the interrupt to a different function? I guess I should do something like this in the irq return

sub lr,lr,#4 // fixup the address in lr
subs pc,=yield // call yield function

I'm guessing here that the lr will keep the value that it had in irq mode after the return to supervisor mode, so it will be correct in the yield function

push {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}

Allowing the yield function to choose a next task to execute and jump to it

bx =next_task
mov sp,r0 // stack pointer of next task is returned
pop {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
bx lr

Is this likely to work? Any pointers to simple task schedulers out there that already have this stuff working?

Posts: 1456
Joined: Sun Sep 11, 2011 2:32 pm

Re: Implementing tasks

Mon Nov 12, 2012 7:10 am

You're pretty much there.

My context switcher is here : ... switcher.s
Task setup here : ... ore/task.c

The interrupt handling code is probably still bugged, but the context switch stuff definitely works. Bear in mind that my code is for stuff that's running in user mode.

Posts: 1456
Joined: Sun Sep 11, 2011 2:32 pm

Re: Implementing tasks

Mon Nov 12, 2012 10:19 am

I should probably add a bit more information, although the code itself is pretty well commented, there's stuff that might not be completely obvious.

When we hit an interrupt handler, the link register is set to the current execution location in the interrupted code, and the spsr is set to the interrupted code's cpsr. We need to save these, or we get totally fracked. So, the first two instructions are:

Code: Select all

srsdb sp!, #SYS_MODE
srs stores the spsr and current link register (these are the only bits we are guarnteed to be interested in whilst in the current mode - if we're coming in from an interrupt, we need to save these, if we're coming in from a yield of some description we're probably already in the right mode so this is doing a bit of extra work) to the system mode stack, and cps switches us to system mode. I'm using system mode because it uses the same register set as my (user mode) code, but i digress.

Now we know where we are, we can decide if we need to context switch, and save (if necessary) registers on the stack. We then swap out the stack pointer, again if necessary, restore registers, and then return using the rfe instruction. This will restore not only the PC from the saved LR, but also the cpsr, thus restoring us to the operating mode we were in before we dropped into the task switcher.

If you can guarantee your stack will always be 4-byte aligned, you can cut out a bit of code, and come closer to a properly realtime switcher.


Return to “Bare metal, Assembly language”