CS452 - Real-Time Programming - Spring 2011

Lecture 6 - Context Switch, Create

Pubilic Service Announcements

  1. E-mail forwarding

Doing It on the ARM

Features

  1. 16 32-bit registers
  2. Processor modes.
  3. Program status register, CPSR and SPSR
  4. Exceptions
    1. You are concerned right now with Reset and Software Interrupt.
    2. 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.

    3. 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.
    4. Note endianness of RedBoot output when examining these locations.
  5. Three data types

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?

  1. r14_svc <- address of the following instruction. This is where the kernel will return to.
  2. SPSR_svc <- CPSR. This saves the mode, condition codes, etc.
  3. CPSR[0:4] <- 0b10011. Supervisor mode.
  4. CPSR[5] <- 0. ARM (not Thumb) state.
  5. CPSR[7] <- 1. Normal interrupts disabled.
  6. 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:

  1. What is above kernel entry?
  2. If you put swi in a wrapper or stub what happens before and after it?
  3. If the request had arguments, how would you get them into the kernel?

    Hint. How does gcc pass arguments into a function?

  4. It might be important that there are two link registers. Which two link registers? Why?
  5. In practice it isn't important. Why not?

Suggestions:

  1. Try this first on paper drawing the stack, registers, etc after each instruction
  2. 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:
  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
  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 best!.


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. Save kernel state on kernel stack
  2. From TD
  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.


Making the Stub that Wraps swi

For each kernel primitive there must be a function available in usr code: the kernel's API.

What gcc does for you

Before calling Create

  1. gcc saves the scratch registers to memory.
  2. gcc puts the arguments into the scratch registers, and possibly on the stack.

While calling Create

  1. bl to the entry point of Create

While executing Create

  1. gcc saves the registers that it thinks will be altered during execution of the function.
  2. your code gets executed
  3. gcc restores the registers it saved, and only those registers.

Exiting from Create

  1. mov pc, lr, or equivalent, is executed, returning the execution to the instruction following bl

After calling Create

  1. gcc stores register r0, the return value, in the variable, to which the result of Create is assigned.

What the code you write does

  1. Moves the arguments from gcc's locations to whatever convention you choose for your kernel
  2. Does swi n, where n is the code for Create.
  3. 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

  1. Get and initialize resources needed by the task
  2. Make the task look as if it had just entered the kernel

Things you need to do

Get an unused TD and memory for its stack

Mostly filling in fields in the TD.

  1. task id
  2. stack pointer
  3. SPSR
  4. link register
  5. parent tid
  6. return value
  7. state
  8. 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.

  1. Passing arguments
  2. Jumping into the kernel
  3. Getting the return value from the kernel and returning it.

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?

  1. This requires a bit of assembly language.
  2. In assembly language all arguments & return values are words.
  3. What happens when Create is called?

What happens when Create returns?

When the caller is next activated,

user code has to get it and put it in the compiler's special place, r0 for gcc.


Return to: