CS452 - Real-Time Programming - Winter 2015
Lecture 6 - Context Switch Details
Public Service Annoucements
- Due date for kernel 1: 25 May, 2015
SWI
Software Interrupt, SWI.
- Does what bl does, plus
- saves the cpsr in the kernel's spsr
- This must be done atomically. Why?
-
24 bits unused in the swi instruction; you can read them. How?
-
Executes the instruction in 0x08
-
which is mov pc, pc+0x18.
To make the software interrupt work you must set low memory
correctly. You know what to put in 0x08:
-
You need to put the entry point of the kernel into 0x28.
Before the Software Interrupt
When the previous request has been handled completely, it's time
to leave the kernel.
-
You are in svc mode, executing kernel instructions
-
Schedule to discover which task runs next.
- i.e. get the value of
active
-
Enter
activate( active )
-
active
is a pointer to a task descriptor
(TD*
). From it, or the from the user stack get
sp_usr
-
set spsr_svc = cpsr_usr
-
You should understand how this takes us back to user mode.
-
Store kernel state on kernel stack
-
During kernel entry you load the kernel registers from
the kernel stack. Taken together loading and storing
should be a NOP.
-
Storing the kernel state before overwriting its registers is
essential because there are two different link registers.
-
get the address of the next instruction of
active
to be run, its pc
-
set lr_svc = pc
-
At this point
movs pc, lr
would start executing
the code of active at the correct instruction, but the register
values in the CPU are the kernel's.
Switch to system mode
-
Load registers from user stack
- Combined with 3 above this should be a NOP
-
Return to supervisor mode
-
Set return value by overwriting r0
- What about registers r1-3?
-
Let it go
movs pc, lr
Somewhere after movs
is the kernel entry.
- There might be something like an assertion before the
entry.
After the kernel entry is the inverse of this sequence.
Scheduling
There are two important issues for scheduling
- When do we reschedule?
- Who do we activate when we schedule
When to schedule
Every time we are in the kernel, so the issue is `When do we enter the
kernel?'
Who to Schedule
Whoever is needed to meet all the deadlines
- or to optimize something.
Because this is not an easy problem, we don't want to solve it
within the kernel. What the kernel does should be fast (=constant
time) and not resource constrained.
Scheduling algorithm
- Find the highest priority non-empty ready queue.
- Schedule the first task in the queue.
The state of the most recently scheduled (running) task is ACTIVE, not
READY.
The kernel maintains a pointer to the TD of the active task so it
knows which task is making the current request.
- When a task is made ready it is put at the end of its ready queue.
Implementation
Array of ready queues, one for each priority.
Each ready queue is a list with a head pointer (for extraction)and a tail
pointer (for insertion).
Hint. The Art of Computer Programming (Donald Knuth) says that circular
queues are better. Why?
Implementation decisions
- How many priorities
- Which task should have which priority
- What to do when there is no ready task
The queues of typical running system
- Highest priority:
- tasks waiting on interrupts, event-blocked tasks
- almost always blocked
- do minimal processing, then release tasks blocked on them
- Medium priority
- receive-blocked tasks
- almost always blocked
- provide service to application tasks
- Low priority
- send-blocked tasks
- blocked more often than not
- make decisions about what should be done next
- Lowest priority
- one task that runs without blocking
- the idle task
- uses power without doing anything
Making the Stub that Wraps swi
For each kernel primitive there must be a function available in
user 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 could 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
Fill in the fields of the TD.
- task id
- stack pointer
- SPSR
- link register
- parent tid
- return value
- dummy
- different return value for the active task
- state
- install in the ready queues
Must also initialize the stack
Initializing the Kernel
Set up the Hardware
- busy-wait io
- low memory
- Where is the kernel entry?
- Turn off interrupts in the ICU
- This should be unnecessary, but what if the previous kernel turned
them on?
- Later you will initialize the ICU differently.
Prepare the Kernel Data Structures
Where is the kernel's stack pointer, right now? What does the stack look
like?
- Do you want it there? Would you rather have it somewhere else?
- This is your last chance to change it. (If you decide to change it you
might want to keep what you are replacing around. Why?)
The kernel data structures
- an array of empty ready queues
- a poimter to the TD of the active task
- an array of TDs
- a free list of pointers to free TDs
Prepare the Memory to be Used by Tasks
- task memory
Create the First User Task
Can run with interrupts turned off for now (belt and braces) but will need
to be turned on later.
Reminder. The place where the kernel starts executing has the global name
main, which cannot be re-used.
Other Primitives
These primitives exist mostly so that we, which includes you, can ensure
that task creation and scheduling are working when there is not much else
implemented.
Tid MyTid( )
Self-explanatory
- Doesn't block, but does reschedule.
A question, to which there is a correct answer, or more specifically, a
correct (answer, reason) pair.
- Should the Tid be stored in user space?
Tid MyParentTid( )
Self-explanatory
- Doesn't block, but does reschedule.
Where is the parent Tid, and how does the kernel find it?
void Pass( )
Doesn't block: task calling Pass( )
makes a state transition
from ACTIVE
to READY
.
Does reschedule.
When is Pass( )
a NOP
?
void Exit( )
Calling task is removed from all queues, but its resources are not
reclaimed or reused.
That is, the task goes into a zombie state, in which it cannot be active
or ready, but continues to own all its resources.
Try reading this.
Return to: