CS452 - Real-Time Programming - Fall 2011
Lecture 9 - Send/Receive/Reply
Public Service Annoucements
- Reference handin for a0.
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 )
- blocks until Reply occurs
int Receive( Tid *tid, char *message, int mslen )
- blocks until message is available
- Only one waiting sender is processed per Receive
- Why?
- Hint. There might be tasks that are higher priority than the
Receiver.
int Reply( Tid tid, char *reply, int rplen )
- does not block
- unblocks task that called Send
- Send and Reply become READY at the same time.
How are They Used?
The Producer/Consumer Problem
+--------------+ +--------------+
| | | |
| Producer | ===> | Consumer |
| | | |
+--------------+ +--------------+
Producer Sends
- producer sends and blocks (I have some XXX for you)
- consumer receives (Give me some XXX)
- consumer accepts XXX
- consumer replies (I got the XXX)
- producer & consumer are simultaneously READY
Note. 1 & 2 could be run in the opposite order
Consumer Sends
- Consumer sends and blocks (`I am ready for some XXX.')
- Producer receives (`I have some XXX.')
- Producer replies (`Here is the XXX')
- Consumer accepts XXX
- Producer and consumer are simultaneously READY
Note. 1 & 2 could be run in the opposite order
Multiple Producers
Producers send; consumer receives in the order that producers send.
Note. Critical races can occur, which the application programmer must
resolve.
Multiple Consumers
Consumers send; producer receives in the order that consumers send.
Note. Critical races can occur, which the application programmer must
resolve.
Multiple Consumers AND Multiple Producers
Consumers send and producers send: who receives?
- A third task: you call it a FIFO or buffer; I call it a warehouse
+---------------+ +------------+ +---------------+
| | | | | |
| Producers | ===> | Buffer | ===> | Consumers |
| | | | | |
+---------------+ +------------+ +---------------+
Buffer receives two types of request
- Here is some XXX
- Warehouse blocks sender, saves XXX, or provides XXX
- I want some XXX
- Warehouse blocks sender, provides XXX,, or queues sender
Only a receiver can accept two types of requests at once.
Sequence of States
Sender
- Active -> Receive_Blocked
- Receive_Blocked -> Reply_Blocked
- May happen right away
- When Receive was called first AND
- the Receiver's SendQ is empty
- Otherwise, when Receive is called
- Reply_Blocked -> Ready
Receiver
- Active -> Send_Blocked
- Send_Blocked -> Ready
- May happen right away
- if the sendQ is not empty
- Ready -> Active
- ...
- 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
- Need to keep around request
- For Send_Blocked receivers in the SendQ
- Where is it kept?
- Messages
- Typed as strings
- which require formatting and parsing
- Normally cast from
char* to struct*
- probably should be
void*
- sizeof( ) is useful
- struct expected by receiver/sender must agree with struct
provided by sender/receiver
- Type-checking by compiler not possible
- Dynamic type-checking possible
- First element of struct is its type
- Even polymorphism is possible
- Message types could be handled in a more modern way
- type extension
- would provide a more uniform handling of error returns
- Task states
- DEFUNCT is terminal
- READY -> ACTIVE
- ACTIVE -> SEND_BLOCKED, RECEIVE_BLOCKED, READY, DEFUNCT
- SEND_BLOCKED -> READY
- RECEIVE_BLOCKED -> REPLY_BLOCKED
- REPLY_BLOCKED -> READY
- You can add extra return values beyond those specified
Note. You may find the descriptions below overly elaborate, but they catch
the obvious corner cases.
int Send( Tid tid, char *message, int mslen, char *reply, int rplen )
These are pretty self explanatory, except
- The return value is the number of characters actually placed in the
reply-buffer
- including the terminal character ( \000 ) 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
- Should it be?
- Hint. Finding out if a task "never Receives" is equivalent to what
problem?
- 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
Implementing Send
What's in user space is just stubs.
- checking arguments
- putting arguments in the right place
- Note that there are five arguments. Where does the fifth go?
What the kernel must do
- Check arguments
- Change state of sender to RECEIVE_BLOCKED
- Put sender on the end of the receiver's sendQ
- If receiver is SEND_BLOCKED, do #5 in Implementing
Receive, below.
int Receive( Tid *tid, char *message, int msglen )
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 done
Send, which one gets
Received first?
- return value is number of bytes in message, including terminal
character (\000) if the message is really a string..
- It seems as though the return value should be the tid. Something is
not right.
- If something goes wrong, the return value is negative, coded to
indicate what went wrong
What can go wrong?
- 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
- Check arguments
- Change receiver's state to SEND_BLOCKED
- Check the
sendQ
- If SENDQ_EMPTY
- Exit from kernel after scheduling
- else
- extract head of the send queue, the sender
- copy message from sender to receiver, after checking buffer
sizes
- change sender's state to REPLY_BLOCKED
- change receiver's state to READY
- put sender's tid into receiver's argument
- put receiver on its
readyQ
- set up receiver's return value
int Reply( Tid tid, char *reply, int rplen )
These are pretty self explanatory, except
- The Replyer need not be the Receiver, but must be in contact with the
Receiver
- When all goes well Reply leaves two tasks READY when it completes
Implementing Reply
- Check arguments
- sender (tid) must be REPLY_BLOCKED
- Copy message from replier to sender, checking buffer sizes
- Put sender on readyQ
- Set up sender's return value
- Change sender's state to READY
- Put replier on readyQ
- Set up replier's return value
- Change replier's state to READY.
Return to: