CS452 - Real-Time Programming - Fall 2008
Lecture 10 - Inter-task Communication
Questions & Comments
Message Passing
Let's think about objects
- Send a message to an object, asking it to run some code for us, and
return an response
- For the moment don't think about who actually runs the code
We normally think about this in a single threaded way, like a procedure
call
- Call a method and block
- Method is just sitting, waiting for the call and immediately starts to
execute
- in the address space of the caller
- with access to the address space of the object
- When it's finished executing it returns a result.
- The caller is no longer blocked and continues executing.
- The method goes back to sitting, waiting
In an environment with multiple threads of control, which is true of your
system
- Several threads might call the same method
- or different methods within the same object
at the same time.
- Then the object needs to be `thread-safe'
- aware that there may be simultaneous execution
- use monitors, or whatever
- The object worries about the details
- but this makes the object very difficult to create, OR
- if the object isn't thread-safe makes user code very difficult to
write
To make the last easier we make the communnication more explicit than it
is in a method call
- To call the method a task calls
Send with an argument that
indicates which code is to be executed and its environment
- To indicate its readiness to execute the object calls
Receive, which returns which code is to be executed and its
environment
- To indicate that the code has completed its execution the object calls
Reply with an argument that indicates the result of the
execution
Send returns within the task, with the result of the
execution
Synchronization details
Send blocks the caller
- Either it has to block sometime
- Or it needs to poll
- Or it needs to split into multiple threads
Most of the time it needs the result before it can proceed further
Receive blocks the callee
- Which makes it easy for the object to control how many executions
it is performing
- And obviates the need for multiple threads
Reply blocks nobody
- The transaction is complete
What practical things can be going on in the sender?
- Getting some information that's needed
- Making a request for a service
- Output some characters
- Create a new task
Waiting for something
- Input to be available
- a particular time
- a resource to be free
Producer/Consumer
Two ways to do this
- Producer
Sends, consumer Receives
Send means "Here is some data"
Receive means "I'm ready to receive data"
Reply means "I got the data"
After Reply buffers holding data can be overwritten at
will
- Producer
Receives and Replys
Send means "I'm ready for data"
Receive means "I'm ready to transmit data"
Reply means "Here's the data"
After Reply buffers can be overwritten at will.
What is the difference?
- If relationship is one-to-one
- If relationship is many-to-one
- the one doing many MUST
Receive.
- If relationship is many-to-many
- both MUST
Receive
- Placing a
Send among Receives is a bad
idea. Why?
- How do you solve the problem: a worker
Sends to
both.
int Send( Tid tid, char *arg, int arg-length, char *reply-buffer, int
reply-buffer-size )
These are pretty self explanatory, except
- The return value is the number of characters actually returned
- including the \0 if the contents of the reply buffer is a
string
- If something goes wrong, the return value is negative, coded to
indicate what went wrong
What can go wrong
- Illegal
tid
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
- Parsing
argument and reply-buffer is
potentially costly and error-prone
- A type system might be nice
- But then you would feel compelled to implement run-time type
checking
- This form of message passing requires user and kernel code to cooperate
to avoid malignancies
- which is just barely okay when one implementer does both
- but is otherwise monumentally insecure
int Receive( Tid *tid, char *arg-buffer, int arg-buffer-length )
These are pretty self explanatory, except
- How is the task id copied form kernel to receiver?
- That is, where does the pointer point to?
- What if the buffer wasn't big enough?
- If several tasks have
Sended, which one gets
Received first?
int Reply( Tid tid, char *reply, int reply-length )
These are pretty self explanatory.
Implementation
Code should be constant time
Send before Receive
- Kernel needs to find the
Sender
- Need a FIFO
Send queue in the Receiver's
TD
Receive before Send
Tid of Receiver in the arguments
- Test state to know which case
How is the message copying done?
Return to: