CS452 - Real-Time Programming - Winter 2013
Lecture 6 - Context Switch
Pubilc Service Annoucements
- Due date of kernel 1
- Partners
- Rebellion software, Jinga software, fishbowl
- Locked train room
Baby Steps
Conside the execution of the following program
void sub( ) {
return;
}
void main( ) {
sub( );
}
What does it compile to? Something like this, which is assumed to start
(and finish) in supervisor mode.
main:
mov ip, sp
stm sp!, {fp, ip, lr}
bl <sub>
ldm sp, {fp, sp, pc}
sub:
mov ip, sp
stm sp!, {pf, ip, lr}
mov r0, #0x55555555
mov r2, #0x218000
str [r2], r0
ldm sp, {fp, sp, pc}
Which stack is used to save these registers? What do you do to get your
own private stack?
Now let's change the third instruction to
swi n
What happens? Why?
We need to set low memory to make swi work as we wish. Before you run the
program look at the load map, find the hex address of sub when the program is
loaded.
What initialization code would you put in the assembly language code to
achieve the same effect?
But we don't really want to look at load maps, or even worse, parse them.
Try changing the C version to
void main( ) {
void (*syscall)();
syscall = sub;
*(0x28) = syscall;
...
}
What does this compile to? Figure out how to get it right.
Now main is playing the role of a user program,
sub is playing the role of the kernel. Let's make sub more like
a kernel.
- Fully store and restore the state of main. (The kernel doesn't know
what registers the user program may have been using.)
- Restore and store the state of the kernel.
- Initialize main to be in user mode and put it back into user mode when
you reactivate it.
- ....
When you are fully confident that you know exactly what is going on figure
out how the parts of your code map onto the structure of
getNextRequest( ), handle( req ), and
initialize( ). You can now restructure sub into a
kernel that offers one service: enter and exit the kernel.
Execution now starts in the kernel: sub changes its name to
main.
After the Software Interrupt
In the kernel
The order matters
kernel entry:
- State on entry
- supervisor mode
- interrupts off
- spsr_svc = cpsr_usr
- arguments in r0-r3
- caller context in registers r4-r12
- caller local variables indexed off fp
- kernel stack pointer (sp_svc) in r13
- address of instruction following swi in r14, i.e., lr_svc = return
address = pc_usr
- Thus,
swi n is accessed as [lr,
#-4]
- 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.
- Then you can 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. Why?
- 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 when you're finished
handling the request and before scheduling.
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 necessarily best!.
Self-test question. Why is it ridiculous to put saving
state into a function?
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
- From TD, or the user stack
- get sp_usr
- set spsr_svc = cpsr_usr
- You should understand how this takes us back to user mode.
- set lr_svc = pc for return to user mode
- Save kernel state on kernel stack
- Combined with 6, above this should be a NOP
- Set return value by overwriting r0 on user stack
- Switch to system mode
- Load registers from user stack
- Combined wi 3 above this should be a NOP
- Return to supervisor mode
- Let it go
movs pc, lr
The instruction after this one is normally the kernel entry.
int Create( void (*code)(), int priority )
Now we are ready to create a task. What are the arguments?
- The name of the function that contains the task's code.
- This is easy, just the initial program counter.
- The priority of the task, which means how important it is.
- This is easy also, but only in the context of scheduling
Scheduling
You choose the number of priority queues.
- Within each priority queue scheduling is round robin.
- That is,
- When a task becomes ready it is put on the end of the queue.
- The head of the highest priority non-empty queue is the next
activated task.
- Scheduling occurs frequently. Therefore, each part of scheduling
- finding the highest priority non-empty queue,
- changing the highest priority non-empty queue,
- extracting from the head of a priority queue,
- inserting into that tail of a priority queue, and
- anything else?
must be both
Return to: