CS452 - Real-Time Programming - Winter 2018

Lecture 7 - Create

Public Service Annoucements

  1. Due date for kernel 1: Friday, 26 January, 2018
  2. Please remember that some aspects of the system configuration you get depends on the state in which it was left by the previous group. I have seen very hard to find bugs occur at the last minute because a group unknowingly relied on state provided by the group before it.
  3. What does swi and movs pc, lr do?

Context Switch

From the point-of-view of the task

What gcc does

We started by modelling what happens in a context switch as a function call to a kernel function. After all, that's what it looks like to the user code.

    ...
    tid = Create( priorityQ, code );
    ...
  

Software Interrupt SWI

The software interrupt has an inverse instruction which, executed immediately following it, undoes its effect. Its most common form is

    movs	pc, lr
  
which has the following effect.
  1. The pc gets the contents of the lr_<mode>.
  2. The cpsr gets the contents of the spsr_<mode>.
All forms of instructions like this are priveleged. (Privilege is needed for changing the low bits of the CPSR. Why?) The inverse depends on the link register and the stack pointer in the calling program being undisturbed.

Software Interrupt, SWI.

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

The sequence

// in the user task
   swi   n
//inside the kernel
kernel entry:
   movs   pc, lr
is a NOP. The 's' in movs puts the SPSR into CPSR.

Where in the kernel is the kernel entry?


Leaving the kernel.

Between the kernel entry and leaving the kernel

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 from the user stack get sp_usr.
  5. from the user stack get the cpsr of active. and put it into the kernel's spsr_svc.
  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.

Immediately 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

As often as possible, which means every time we are in the kernel, so the question amounts to `When do we enter the kernel?'
The answer right now (during the first part of kernel development) is: whever user code executes SWI.

Who to Schedule

Whatever task will cause all deadlines to be met.

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

Two pieces of code are run every time the kernel runs

We want to make both as efficient as possible, within reason!
  1. Find the highest priority non-empty ready queue. A ready queue can be as simple as a linked list of pointers to task descriptors.
  2. The task found is removed from its queue and becomes the active task. (Until this point active has pointed to the TD of the previously active task.)
  3. When a task is made ready it is put at the end of its ready queue.Thus, all tasks oat the same priority get equal chances of running.

Implementation Comments

The main data structure is usually an 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?

The queues of typical running system

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

Implementation

Decisions

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

General structure

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

Implementing lists without memory allocation

You are probably used to implementing lists (and similar data structures) like this

      void insert( ... ) {
	struct element *e;
	e = malloc( element );
	.
	.
	.
      }
    
We don't like this because it requires freeing memory. Remember that Voyageur code has been executing for almost forty years.

Here's the most common way to do this without allocating memory

      typedef struct task-descr { ...
				  struct task-desc *rdy-next;
				  ...
      } TD;
    
All the allocation is done when the task descriptors are declared.

Of course, because allocating and freeing constant sized pieces of data can be done in constant time, you could allocate a pool of list elements when you initialize, and manage it using a free list.


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

You have the priority and a function point to the first instruction in the arguments to the Create request. You must 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, which is READY
  8. priority, needed to install the task into its ready queue

Must also initialize the stack


Return to: