CS452 - Real-Time Programming - Winter 2015

Lecture 6 - Context Switch Details

Public Service Annoucements

  1. Due date for kernel 1: 25 May, 2015

SWI

Software Interrupt, SWI.

To make the software interrupt work you must set low memory correctly. You know what to put in 0x08:

Before the Software Interrupt

When the previous request has been handled completely, it's time to leave the kernel.

  1. You are in svc mode, executing kernel instructions
  2. Schedule to discover which task runs next.
  3. Enter activate( active )
  4. active is a pointer to a task descriptor (TD*). From it, or the from the user stack get sp_usr
  5. set spsr_svc = cpsr_usr
  6. Store kernel state on kernel stack
  7. get the address of the next instruction of active to be run, its pc
  8. set lr_svc = pc
  9. 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.
  10. Switch to system mode
  11. Load registers from user stack
  12. Return to supervisor mode
  13. Set return value by overwriting r0
  14. Let it go
    movs   pc, lr

Somewhere after movs is the kernel entry.

After the kernel entry is the inverse of this sequence.


Scheduling

There are two important issues for scheduling

  1. When do we reschedule?
  2. 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

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

  1. Find the highest priority non-empty ready queue.
  2. 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.

  3. 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

  1. How many priorities
  2. Which task should have which priority
  3. What to do when there is no ready task

The queues of typical running system

  1. Highest priority:
  2. Medium priority
  3. Low priority
  4. Lowest priority

Making the Stub that Wraps swi

For each kernel primitive there must be a function available in user 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 could 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

Fill in the fields of 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


Initializing the Kernel

Set up the Hardware

  1. busy-wait io
  2. low memory
  3. Turn off interrupts in the ICU

Prepare the Kernel Data Structures

Where is the kernel's stack pointer, right now? What does the stack look like?

The kernel data structures

  1. an array of empty ready queues
  2. a poimter to the TD of the active task
  3. an array of TDs
  4. a free list of pointers to free TDs

Prepare the Memory to be Used by Tasks

  1. 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

A question, to which there is a correct answer, or more specifically, a correct (answer, reason) pair.

Tid MyParentTid( )

Self-explanatory

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: