CS452 - Real-Time Programming - Spring 2011
Lecture 6 - Context Switch, Create
Pubilic Service Announcements
- E-mail forwarding
Doing It on the ARM
Features
- 16 32-bit registers
- Processor modes.
- Program status register, CPSR and SPSR
- Exceptions
- You are concerned right now with Reset and Software Interrupt.
- The first instruction executed by the CPU after reset is the one at
location
0x00000000. Usually it is
ldr pc, [pc, #0x18] ; 0xe590f018 is the binary encoding
which you will normally find in addresses 0x00 to
0x1c. Just executing an instruction, rather than having
an address that is specially processed saves transistors, which is
good.
- RedBoot puts entry points of RedBoot into addresses
0x20 to 0x3c.This makes it possible to jump
to any location in the 32 bit address space.
- Note endianness of RedBoot output when examining these
locations.
- Three data types
- word: 32 bits, word-aligned
- half-word: 16 bits, half-word-aligned
- byte: 8 bits
Context Switch
Step-by-step
Function Call (gcc calling conventions)
; In calling code
; store values of r0-r3
; load arguments into r0-r3
bl <entry point> ; this treats the pc and lr specially
; lr <- pc, pc <- <entry point>
; r0 has the return value
; r1-r3 have useless junk
; In called code
entry point:
mov ip, sp
stmdb sp!, {fp, ip, lr} ; and usually others,
; determined by the registers the function uses
...
ldmia sp, {fp, sp, pc} ; and whatever others
; exact inverse of bl, mov and stmdb
Note the role of the index pointer (ip), link register (lr) and stack
pointer (sp).
Software Interrupt
The software interrupt instruction ( SWI{cond} <immed_24> ). What
happens when it is executed?
- r14_svc <- address of the following instruction. This is where the
kernel will return to.
- SPSR_svc <- CPSR. This saves the mode, condition codes, etc.
- CPSR[0:4] <- 0b10011. Supervisor mode.
- CPSR[5] <- 0. ARM (not Thumb) state.
- CPSR[7] <- 1. Normal interrupts disabled.
- PC <- 0x08
The CPU ignores the 24-bit immediate value, which can be used by the
programmer as an argument identifying the system call, for example.
; In calling code
; Store r0-r3
; Put arguments into r0-r3
; 0x08 holds the kernel entry point
swi n ; n identifies which system call you are calling
; retrieve return value from r0
; r1-r3 have even more useless junk
; In kernel
kernel entry:
; Change to system mode
; Save user state on user stack
; Return to supervisor mode
ldr r4, [lr, #-4] ; gets the request type
; at this point you can get the arguments
; Where are they?
; Retrieve kernel state from kernel stack
; Do kernel work
Questions:
- What is above kernel entry?
- If you put swi in a wrapper or stub what happens before and after
it?
- If the request had arguments, how would you get them into the kernel?
Hint. How does gcc pass arguments into a function?
- It might be important that there are two link registers. Which two link
registers? Why?
- In practice it isn't important. Why not?
Suggestions:
- Try this first on paper drawing the stack, registers, etc after each
instruction
- Try coding in baby steps, which is usually a good idea in assembly
language.
Try reading this.
After the Software Interrupt
In the kernel
The order matters
kernel entry:
- State on entry
- supervisor mode
- interrupts off
- spsr = cpsr_usr
- arguments in r0-r3
- caller context in registers r4-r12
- caller local variables indexed off fp
- kernel stack pointer in r13
- address of instruction following swi in r14, i.e., lr_srv = return
address
- kernel entry in r15
- Change to system state
- Save the user state
- on its stack
- This might include scratch registers (arguments), which you may or
may not need later.
- Put
sp_usr in a scratch register, say r2
- Return to supervisor mode
- Get the request into a scratch register
ldr r3, [lr, #-4]
- Retrieve the kernel state, which should not include the scratch
registers
- You now have the kernel frame pointer
- You can use it to put stuff in kernel memory
- Put what you need to in the active task's TD
active is indexed off the kernel's frame pointer
active is a pointer to the TD of the requester
- Some where above you must have picked up the arguments
- must be done after 5. Why?
- must be done before 9. Why?
- Return from
getNextRequest( active ) and get to work
- Don't forget to store the return value in the TD when you're
finished
There is more than one way to do almost everything in this list, and I
have chosen this way of describing what is to be done because it's simplest
to describe, not because it's best!.
Before the Software Interrupt
After a while it's time to leave the kernel
- Schedule the next task to run
- i.e. get the value of
active
- Call
GetNextRequest( active )
Inside GetNextRequest
- Save kernel state on kernel stack
- From TD
- set spsr_svc = cpsr_usr
- set lr_svc = pc for execuation in user mode
- get sp_usr
- Set return value by overwriting r0 on user stack
- Switch to system mode
- Load registers from user stack
- Return to supervisor mode
- Let it go
movs pc, lr
The instruction after this one is normally the kernel entry.
Making the Stub that Wraps swi
For each kernel primitive there must be a function available in usr code:
the kernel's API.
- e.g.
int Create( int priority, void ( *code ) ( ) );
What gcc does for you
Before calling Create
- gcc saves the scratch registers to memory.
- gcc puts the arguments into the scratch registers, and possibly on the
stack.
While calling Create
bl to the entry point of Create
While executing Create
- gcc saves the registers that it thinks will be altered
during execution of the function.
- gcc thinks wrong, because only the assembler knows that swi is in
the instruction stream
- your code gets executed
- gcc restores the registers it saved, and only those registers.
Exiting from Create
- mov pc, lr, or equivalent, is executed, returning the execution to the
instruction following bl
After calling Create
- gcc stores register r0, the return value, in the variable, to which the
result of Create is assigned.
What the code you write does
- Moves the arguments from gcc's locations to whatever convention you
choose for your kernel
- Does swi n, where n is the code for Create.
- Moves the return value from your kernel's conventional location to
r0.
Creating a Task
In creating a task you have to do two things
- Get and initialize resources needed by the task
- Make the task look as if it had just entered the kernel
- it's ready to execute when it's scheduled
Things you need to do
Get an unused TD and memory for its stack
- memory could be associated with TD during initialization
- actually a form of constant time memory allocation
- unless you implement Destroy
Mostly filling in fields in the TD.
- task id
- stack pointer
- SPSR
- link register
- parent tid
- return value
- dummy
- different return value for the active task, which goes in its
TD
- state
- install in the ready queues
Must also initialize the stack
The Create Function
You also need a int Create( int priority, void (*code) ( ) )
function to call from user tasks.
Although it's no more than a wrapper there are a few problems to solve.
- Passing arguments
- On entry the arguments are somewhere, usually r0 & r1
- You have to put them where the kernel can find them.
- gcc's function extry code immediately puts them on the stack.
- In assembly you can find them using the frame pointer.
- Jumping into the kernel
- Getting the return value from the kernel and returning it.
- You find it where the kernel put it
- gcc's function exit code expects it to be indexed off the frame
pointer
- from where it does into r0
What follows just seems to say the same thing again. But we might as well
leave it here because some student might find it useful.
How do we implement the API for user code?
- This requires a bit of assembly language.
- In assembly language all arguments & return values are words.
- usually pointers
- stored in registers
- What happens when there are too many arguments?
- What happens when
Create is called?
- Two arguments
- stored in
r0 and r1
- which is which?
- Immediately placed on the stack, referred to by
fp
- Create puts them in the special place you choose
swi switches context into the kernel.
What happens when Create returns?
- When the kernel is finished manipulating
TDs and
PQs
- it knows the tid to be returned
- it places the tid in the caller's return value in its
TD
When the caller is next activated,
- as part of activation the return value is put in your special place,
usually
r0
user code has to get it and put it in the compiler's special place, r0 for
gcc.
Return to: