CS452 - Real-Time Programming - Fall 2011

Lecture 8 - Create

Public Service Annoucements

  1. Reference handin for a0.

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.

Comment on Memory Allocation

At the beginning of the course we said, `No memory allocation, ' and now we are doing it, right in the middle of a kernel primitive, which is supposed to be real-time. What's going on?

Really there are two qualitatively different cases of memory allocation:

  1. the malloc/new type of resource allocation:

allocation of big same-size chunks of memory,

obvious trade-off between chunk size and free-list size.

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

Implementation of Ready Queues


Making a Stub to Wrap 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.


Inter-task Communication

Overview

Message passing combines synchronization and communication in one set of primitives.

int Send( Tid tid, char *message, int mslen, char *reply, int rplen )

int Receive( Tid *tid, char *message, int mslen )

int Reply( Tid tid, char *reply, int rplen )


Sequence of States

Sender

  1. Active -> Receive_Blocked
  2. Receive_Blocked -> Reply_Blocked
  3. Reply_Blocked -> Ready

Receiver

  1. Active -> Send_Blocked
  2. Send_Blocked -> Ready
  3. Ready -> Active
  4. ...
  5. Active -> Ready

There are two cases

Send before Receive

Send
           ...
           Receive
           ...
           Reply
...        ...

Message copying occurs inside Receive and Reply.

Receive needs to have a list of current senders, the ReceiveQ

Receive before Send

            Receive
...
Send
            ...
            Reply
...         ...

Message copying occurs inside Send and Reply.


Practical Details


int Send( Tid tid, char *message, int mslen, char *reply, int rplen )

These are pretty self explanatory, except

  1. The return value is the number of characters actually placed in the reply-buffer
  2. If something goes wrong, the return value is negative, coded to indicate what went wrong

    What can go wrong

    1. Illegal tid
    2. tid not an existing task

    It's up to Send to check that the reply-buffer was big enough by looking at its return value

    It's not an error if the task to which we Send never Receives

  3. Parsing argument and reply-buffer is potentially costly and error-prone

Implementing Send

What's in user space is just stubs.

What the kernel must do

  1. Check arguments
  2. Change state of sender to RECEIVE_BLOCKED
  3. Put sender on the end of the receiver's sendQ
  4. If receiver is SEND_BLOCKED, do #5 in Receive, immediately below.

int Receive( Tid *tid, char *message, int msglen )

These are pretty self explanatory, except

  1. How is the task id copied form kernel to receiver?
  2. What if the buffer wasn't big enough?
  3. If several tasks have done Send, which one gets Received first?
  4. return value is number of bytes in message, including terminal character (\000) if the message is really a string..
  5. If something goes wrong, the return value is negative, coded to indicate what went wrong

    What can go wrong?

    1. Only part of the message was copied

    It's up to Receive to check that the message-buffer was big enough by looking at its return value

Implementing Receive

What the kernel must do

  1. Check arguments
  2. Change receiver's state to SEND_BLOCKED
  3. Check the sendQ
  4. If SENDQ_EMPTY
    1. Exit from kernel after scheduling
  5. sendQ is not empty
    1. extract head of the send queue, called the sender below
    2. copy message from sender to receiver, after checking buffer sizes
    3. change sender's state to REPLY_BLOCKED
    4. change receiver's state to READY
    5. put sender's tid into receiver's argument
    6. put receiver on its readyQ
    7. set up receiver's return value

int Reply( Tid tid, char *reply, int rplen )

These are pretty self explanatory, except

  1. The Replyer need not be the Receiver, but must be in contact with the Receiver
  2. When all goes well Reply leaves two tasks READY when it completes

Implementing Reply

  1. Check arguments
  2. Copy message from replier to sender, checking buffer sizes
  3. Put sender on readyQ
  4. Set up sender's return value
  5. Change sender's state to READY
  6. Put replier on readyQ
  7. Set up replier's return value
  8. Change replier's state to READY.

Return to: