CS452 - Real-Time Programming - Spring 2015

Lecture 7 - Create

Public Service Annoucements

  1. Due date for kernel 1: 25 May, 2015
  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 swi and movs pc, lr do.

Static/Global Variables.

Global variables

TD tdarray[64];
void main( ) {
  ....
}
int Create( int priority, ( *code( ) ) ) {
  ....
}
tdarray is equally accessible to main and Create. With suitable use of extern it can be made available to code in other files as well. Using your linker script you can have it put anywhere in memory you like.

This kind of global variable is universally recognized as bad programming practice.

Static variables You are used to lines of code like

printf( "Hello, world.\n" );
How does printf get its argument? Near the end -- usually -- of the function containing printf are 14 bytes containing the ASCII representation of the string. A pointer to it is put into the code by the link editor.

Variables stored like this are called static in C. They preserve their values across function calls. There are three reasons why you might prefer not to make them writable.

  1. You must make the part of memory containing the instructions writable, which leaves you open to a whole lot of widely known exploits.
  2. If you reuse code in different tasks you may find static variables changing value unexpectedly and unpredictably.
  3. If you do something like this
     static TD* tdarray; 
    then read and write the array, strange and amazing things might happen. (Use lint or equivalent.)


Scheduling

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

Implementing lists without memory allocation

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

      struct element { struct element *next;
		       struct whatever *content;
      }
      struct list { struct element *head;
		    struct element *tail;
      }
      void insert( list *l, content *c ) {
	struct element *e;
	e = malloc( element );
	e->content = c;
	e->next = null;
	l->tail->next = e;
	l->tail = e;
      }
    
We don't like this because it requires allocating and freeing memory.

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.

Implementation decisions

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

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

Described in lecture 6.

What the code you write does

  1. Moves the arguments from gcc's locations to whatever convention you choose for passing arguments to 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, which is READY
  8. install in its ready queue

Must also initialize the stack

The following implementation decisions are up to you.


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. At the very least you need

  1. an array of empty ready queues
  2. a pointer 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 hardware interrupts turned off for now. But when hardware interrupts are turned on in kernel 3 interrupts in user tasks must be turned on, though they stay off in the kernel.

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, and will never become active or ready, but continues to own all its resources.


Return to: