CS452 - Real-Time Programming - Spring 2011

Lecture 7 - Context Switch, Create

Pubilic Service Announcements

  1. Groups have been created

    getent group | grep 'cs452_[0-9]' | sort


Context Switch on the ARM

Software Interrupt

   swi  n                 ; n identifies which system call you are calling

; In kernel
kernel entry:
; Save user state in user space
; Restore kernel state from kernel space
; Return request to do kernel work

Kernel Work for Create

Allocate Resources

Task ID

  1. 32-bit word to match architecture
  2. Possibly broken into TD index and generation.

Task Descriptor

  1. During initialization create an array of task descriptors
  2. Without task destruction

    With task destruction

Notice that because all task descriptors are the same same size allocation and re-allocation are constant time.

Memory

  1. During initialization break the spare memory into equal sized chunks
  2. Without task destruction

    With task destruction

You could create a more intimate relationship between TD and memory.

Initialize Memory

The principle is, `Make the memory (the stack) exactly ready to be processed by your kernel exit code.'

In practice,

  1. After running the kernel entry code the stack is exactly ready for the kernel exit code to be run.
  2. Make the stack look as though the kernel entry code has just been run.
  3. After runing the kernel exit code you must have

Fill in the Task Descriptor

For some state there is a choice between stack and task descriptor. The minimum is

  1. run state: READY to start with
  2. priority
  3. parent Tid
  4. SP
  5. Pointers for queues.

There could be more

Enter the Task into its Ready Queue

To do this we need to know how scheduling works.


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?'

Three possibilities

  1. Tasks run to completion, which means until they make a request for kernel services
  2. Event-driven pre-emption, which means when hardware makes a request for service
  3. Time-slicing

We do 1 & 2, but not 3, because our tasks co-operate. Time-slicing is needed when tasks are adversarial.

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.

Inexpensive (=constant time)ways to schedule

Least expensive first

  1. active task decides = co-routines
  2. round robin
  3. priorities
    1. fixed at compile time
    2. fixed when task is created
    3. re-fixed every time task is scheduled
      • Do you have a good algorithm?

The number of priorities should be small, but not too small.

Tasks at the same priority should have the same precedence.

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 fornt pointer (for extraction)and a back 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

Set up the Return Value

Because the task that made the request may not be the next one to run, the kernel needs to save the request's return value until the next time the task is scheduled.


Before the Software Interrupt

When kernel work is complete it's time to leave the kernel, which requires activating the active task

  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 setting r0
  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.

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.


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( ) remains ready to execute.

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.

How Should Execution Terminate?

Nicely.

When there ar no tasks left on the ready queues, it goes back to RedBoot.


Return to: