CS452 - Real-Time Programming - Winter 2013

Lecture 7 - Create, Scheduling

Pubilc Service Annoucements

  1. Due date for assignment 1
  2. Partners
  3. Locked doors?

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. Change the active task's TD appropriately
  8. Somewhere above you may have picked up the arguments. If you didn't, do it now.
  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?


Handling the Request

What needs to be done

  1. Check for errors
  2. Manipulating TDs, including
  3. Sometimes, copying bytes from one address space to another.

Saving the return value

The task that made the request may not be the next one to run.


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

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.


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

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.

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.

How Should Execution Terminate?

Nicely.

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


Return to: