CS452 - Real-Time Programming - Winter 2013

Lecture 6 - Context Switch

Pubilc Service Annoucements

  1. Due date of kernel 1
  2. Partners
  3. Rebellion software, Jinga software, fishbowl
  4. 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.

  1. Fully store and restore the state of main. (The kernel doesn't know what registers the user program may have been using.)
  2. Restore and store the state of the kernel.
  3. Initialize main to be in user mode and put it back into user mode when you reactivate it.
  4. ....

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:
  1. State on entry
  2. Change to system state
  3. Save the user state
  4. Return to supervisor mode
  5. Get the request into a scratch register
    ldr r3, [lr, #-4]
  6. Retrieve the kernel state, which should not include the scratch registers. Why?
  7. Put what you need to in the active task's TD
  8. Some where above you must have picked up the arguments
  9. Return from getNextRequest( active ) and get to work

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

  1. Schedule the next task to run
  2. Call GetNextRequest( active )

Inside GetNextRequest

  1. From TD, or the user stack
  2. Save kernel state on kernel stack
  3. Set return value by overwriting r0 on user stack
  4. Switch to system mode
  5. Load registers from user stack
  6. Return to supervisor mode
  7. 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?

  1. The name of the function that contains the task's code.
  2. The priority of the task, which means how important it is.

Scheduling

You choose the number of priority queues.


Return to: