Messages, like all other items, are assigned priority numbers. These priority numbers determine the order of messages within a message queue: higher- priority messages go to the front of the queue. Messages with the same priority are arranged in arrival order: earlier messages come first.
Item CreateMsgPort( const char *name, uint8 pri, uint32 signal )
name
, which it uses to name the message port; a 1-byte priority value, pri
, which assigns a priority to the message port; and a 32-bit signal mask, signal
, which assigns an allocated signal bit to the message port.The message port's name and priority don't change the way the message port operates, but are useful when another task tries to find the message port.
The signal mask specifies a signal to associate with the message port. The signal mask must contain only allocated signal bits. If you specify a signal mask of 0, then CreateMsgPort()
allocates a signal bit automatically. When the port is later deleted using DeleteMsgPort()
, the signal bit is automatically freed.
CreateMsgPort()
returns the item number of the newly created message port if successful, or returns a negative number (an error code) if unsuccessful. The message port now exists as an item in system memory, owned by the creating task.
The message port uses its assigned signal bit to signal its owner task whenever a message arrives at the port. The signal bit should not be freed until the message port is freed.
The item number returned by CreateMsgPort()
is a handle to the new port. The owner task should give it out to any task that may want to send a message to it. Sending a message requires specifying the item number of the message port to which the message is sent.
If the task sends more than 8 bytes of data and only points to the data in a data block that isn't carried with the message, it should create a standard message.
And if the task sends more than 8 bytes of data within the message, it should create a buffered message.
Item CreateMsg( const char *name, uint8 pri, Item mp )
name
, is the name for the message, which is useful for finding the message later. The second argument, pri
, sets the message's priority, which determines how it is sorted in a receiving task's message queue. It can be a value from 0 to 255. The third argument, mp
, is the item number of a message port that belongs to the creating task. This is the message's reply port, where it returns if the receiving task sends it back with a reply call.A standard message carries with it a pointer to a data block, and the size of the data block. The task that receives the message can obtain the pointer to the data block and access the data. If the receiving task has write permission to the pages of memory where the data block is located, it can even write to the data block directly. Because of the fact you pass a pointer to a data block, standard messages are often referred to as "pass-by-reference" messages.
The reply to a standard message can contain 4 bytes of data.
When executed, CreateMsg()
creates a standard message and returns the message's item number. If unsuccessful, it returns a negative number (an error code). Example 1 uses CreateMsg()
:
Example 1: Sample using CreateMsg( ).
static Item myMsg;
static Item mpItem;
/* Create a message */
myMsg = CreateMsg("Message1", 0, mpItem);
if (myMsg < 0)
{
printf("error creating Message\n");
return (-2);
}
To create a small message, use CreateSmallMsg()
:
Item CreateSmallMsg( const char *name, uint8 pri, Item mp )
CreateMsg().
You'll see the difference when you call SendSmallMsg()
(described later).
Because buffered messages require data to be copied into them before being sent, they are often referred to as "pass-by-value" messages.
With buffered messages, a sending task can send large amounts of read/write data to another task. The receiving task can modify this data and return it to the caller. This avoids granting the receiving task write permission to memory buffers in the sending task's address space.
To create a buffered message, use this call:
Item CreateBufferedMsg( const char *name, uint8 pri, Item mp, uint32 datasize )
CreateMsg()
, this call accepts arguments that point to a name, supply a
priority, and give the item number of a reply port for the message. The additional argument, datasize
, specifies the size, in bytes, of the data buffer to create.When the call executes, it creates a buffered message with its accompanying buffer. If successful, the call returns the item number of the message. If unsuccessful, it returns a negative number (an error code).
Err SendSmallMsg( Item mp, Item msg, uint32 val1, uint32 val2 )
mp
, is the item number of the message port to which the message is sent. The second argument, msg
, is the item number of the small message to send. The third argument, val1
, is the first 4 bytes of data to send in the message; the fourth argument, val2
, is the second 4 bytes of data to send in the message. (If you don't know the item number of the message port, but you know its name, you can use FindMsgPort()
to get the item number.)
When SendSmallMsg()
executes, it writes the first value into the msg_DataPtr
field of the message's data structure and the second value into the msg_DataSize
field of the structure. It then sends the message to the specified message port. It returns a 0 if the message is successfully sent, or a negative number (an error code) if unsuccessful.
You should not send a standard or buffered message with this call, because the values you supply can be read as an erroneous pointer and data size.
Err SendMsg( Item mp, Item msg, const void *dataptr, int32 datasize )
SendSmallMsg()
, this call accepts the item number of a message port to which to send the message, mp
, and the item number of the message to send, msg
. Instead of accepting values to store with the message, SendMsg()
accepts a pointer to a data block in the dataptr
argument and the size, in bytes, of the data block in the datasize
argument.
When SendMsg()
executes, it sends the message, returning a 0 if successful, or a negative number (an error code) if unsuccessful. Its effect on the message depends on whether the message is standard or buffered.
A standard message stores the data block pointer and the data size value in the message. The data block remains where it is, and the receiving task reads it there, using the pointer and size value to find it and know how far it extends.
With a buffered message, SendMsg()
checks the size of the data block to see if it will fit in the message's buffer. If it won't, the call returns an error. If it does fit, SendMsg()
copies the contents of the data block into the message's buffer. The receiving task can then read the data directly out of the message's buffer.
The advantage of a buffered message is that it carries the full data block within its buffer, so the sending task can immediately use the original data block's memory for something else. The task doesn't need to maintain the data block until it's sure that the receiving task has read the block. The data block is handled by the system, and is carried within the message in protected system memory.
To receive a message, a task can wait until it receives notification of a message at a message port, or it can check a message port directly to see if a message is there.
Item WaitPort( Item mp, Item msg )
WaitPort()
accepts the required argument, mp
, which contains the item number of the message port where the task receives messages. The call also accepts an optional argument, msg
, which contains the item number of a specific message the task expects to receive at the message port. (To wait for any incoming message, use 0 as the value of msg
.)
Note: If the message arrived before WaitPort()
is called, the task never enters the wait state.
When WaitPort()
executes, the task enters the wait state until it detects a message on the message port's message queue. At that point, if the call doesn't specify a specific message, the task exits the wait state, and the call returns the item number of the first message in the port's queue. The message is removed from the message queue, and the task can use its item number to find and read the message.
If a specific message was given to WaitPort()
in addition to a message port, the task waits until that message arrives at the message port. When it arrives, the task exits the wait state and the message is removed from the message queue.
Item GetMsg( Item mp )
GetMsg()
accepts a single argument: the item number of the message port where it wants to check for messages. When it executes, it checks the message port to see if there are any messages in the port's queue. If there are, it returns the item number of the first message in the queue and removes it from the queue. If there is no message at the message port, the call returns 0. If there is an error, the call returns an error code (a negative number).
Once the task has the pointer, it can determine the type of message it has received by inspecting the contents of the
Message *msg;
Item msgItem;
msg = (Message *)LookupItem(msgItem);
msg.n_Flags
field of the message structure. If the MESSAGE_SMALL
bit is set in this field, it means it is a small message. If the MESSAGE_PASS_BY_VALUE
bit is set, it means it is a buffered message. If neither of these bits is set, it means that it is a standard message.
When you set up communication channels among your tasks or threads using message ports, you should know what type of messages will be sent around to these ports, so that you don't always need to check the types.
msg_DataPtr
and msg_DataSize
fields. The information corresponds directly to the val1
and val2
arguments the sending task specified when it called SendSmallMsg()
. When you return such a message, you supply a 4-byte result code, and two new 32-bit values to put into msg_DataPtr
and msg_DataSize
.
msg_DataPtr
field of the message contains the address of a data area which you can access. The size of the data area is contained in msg_DataSize
. Typically, the data area points to a data structure belonging to the task that sent the message.
If the standard message comes from another task, as opposed to a sibling thread, the data area likely will only be readable by the receiving task, unless the sending task takes the extra steps to grant write permission to that data area. To determine whether your task can write to a data area pointed to in a standard message, use the IsMemWritable()
function.
If you send messages between threads and tasks of the same application, it is often not necessary to go through the extra trouble of checking whether the data area is writable before writing to it, since this can be part of the protocol setup within your application.
When a task replies to a standard message, it provides a 4-byte result code, and new values for msg_DataPtr
and msg_DataSize
. When a task calls ReplyMsg()
, it can leave the values to what they were by simply using msg_DataPtr
and msg_DataSize
as parameters to ReplyMsg()
.
msg_DataPtr
field of the message contains the address of a read-only data area, and msg_DataSize
specifies the number of useful bytes within this buffer. The task can read this data at will.When a task replies a buffered message, it supplies a 4-byte result code, and can optionally supply new data to be copied into the message's buffer. The task can determine the size available in the message's buffer by looking at the msg_DataPtrSize field of the message structure.
GetThisMsg()
call:
Item GetThisMsg( Item msg )
msg
, specifies the message to rescind. If the message is still on the message port it was sent to, it is removed and its item number returned. If the message was already removed from the port by the receiving task, then the function returns 0.
Once again, the three different message types require different handling when replying to a message.
Err ReplySmallMsg( Item msg, int32 result, uint32 val1, uint32 val2 )
ReplySmallMsg()
accepts four arguments. The first argument, msg
, is the item number of the message being returned in reply. The second argument, result
, is a 32-bit value written into the reply field of the message data structure. The third and fourth arguments, val1
and val2
, are data values written into the pointer and size fields of the message data structure (just as they are in SendSmallMsg()
). Note that no destination message port is specified because the message returns to its reply port, which is specified within the message data structure.
When ReplySmallMsg()
executes, it sends a message back in reply and returns a 0 if successful, or an error code (a negative number) if unsuccessful.
Err ReplyMsg( Item msg, int32 result, const void *dataptr, int32 datasize)
ReplyMsg()
accepts four arguments. The first two, msg
and result
, are the same as those accepted by ReplySmallMsg()
. The third, dataptr
, is a pointer to a data block in memory. The fourth, datasize
, is the size in bytes of that data block.
When ReplyMsg()
executes, it sends the message in reply and returns a 0 if successful, or an error code (a negative number) if unsuccessful. The effect the reply has on the message depends on whether the message is standard or buffered, just as it does in SendMsg()
.
If the message is standard, the data block pointer and the data size value are stored in the message, and are read as such by the task getting the reply. If the message is buffered, ReplyMsg()
checks the size of the data block to see if it will fit in the message's buffer. If it doesn't fit, the call returns an error. If it does fit, ReplyMsg()
copies the contents of the data block into the message's buffer and sends the message.
FindMsgPort()
call:
Item FindMsgPort( const char *name )
FindMsgPort()
the name of the message port to find, it returns the item number of that port. If the port doesn't exist, it returns a negative error code. If multiple ports of the same name exist, it returns the item number of the port with the highest priority.
The main()
routine of the program creates a message port where it can receive messages. It then spawns a thread. This thread creates its own message port and message. The thread then sends the message to the parent's message port. Once the parent receives the message, it sends it back to the thread.
Example 2: Samples using the message passing routines (msgpassing.c).
#include "types.h"
#include "item.h"
#include "kernel.h"
#include "task.h"
#include "msgport.h"
#include "operror.h"
#include "stdio.h"
/*****************************************************************************/
/* A signal mask used to sync the thread with the parent */
int32 parentSig;
/*****************************************************************************/
static void ThreadFunction(void)
{
Item childPortItem;
Item childMsgItem;
Item parentPortItem;
Err err;
Msg *msg;
printf("Child thread is running\n");
childPortItem = CreateMsgPort("ChildPort",0,0);
if (childPortItem >= 0)
{
childMsgItem = CreateSmallMsg("ChildMsg",0,childPortItem);
if (childMsgItem >= 0)
{
parentPortItem = FindMsgPort("ParentPort");
if (parentPortItem >= 0)
{
/* tell the paren't we're done initializing */
SendSignal(CURRENTTASK->t_ThreadTask->t.n_Item,parentSig);
err = SendSmallMsg(parentPortItem,childMsgItem,12,34);
if (err >= 0)
{
err = WaitPort(childPortItem,childMsgItem);
if (err >= 0)
{
msg = (Msg *)LookupItem(childMsgItem);
printf("Child received reply from parent: ");
printf("msg_Result %d, msg_DataPtr %d, msg_DataSize %d\n",
msg->msg_Result, msg->msg_DataPtr, msg-
>msg_DataSize);
}
else
{
printf("WaitPort() failed: ");
PrintfSysErr(err);
}
}
else
{
printf("SendSmallMsg() failed: ");
PrintfSysErr(err);
}
SendSignal(CURRENTTASK->t_ThreadTask->t.n_Item,parentSig);
}
else
{
printf("Could not find parent message port: ");
PrintfSysErr(parentPortItem);
}
DeleteMsg(childMsgItem);
}
else
{
printf("CreateSmallMsg() failed: ");
PrintfSysErr(childMsgItem);
}
DeleteMsgPort(childPortItem);
}
else
{
printf("CreateMsgPort() failed: ");
PrintfSysErr(childPortItem);
}
}
/*****************************************************************************/
int main(int32 argc, char **argv)
{
Item portItem;
Item threadItem;
Item msgItem;
Msg *msg;
parentSig = AllocSignal(0);
if (parentSig > 0)
{
portItem = CreateMsgPort("ParentPort",0,0);
if (portItem >= 0)
{
threadItem = CreateThread("Child",10,ThreadFunction,2048);
if (threadItem >= 0)
{
/* wait for the child to be ready */
WaitSignal(parentSig);
/* confirm that the child initialized correctly */
if (FindMsgPort("ChildPort") >= 0)
{
printf("Parent waiting for message from child\n");
msgItem = WaitPort(portItem,0);
if (msgItem >= 0)
{
msg = (Msg *)LookupItem(msgItem);
printf("Parent got child's message: ");
printf("msg_DataPtr %d, msg_DataSize %d\n",
msg->msg_DataPtr, msg->msg_DataSize);
ReplySmallMsg(msgItem,56,78,90);
}
else
{
printf("WaitPort() failed: ");
PrintfSysErr(msgItem);
}
}
/* wait for the thread to tell us it's done before we zap it */
WaitSignal(parentSig);
DeleteThread(threadItem);
}
else
{
printf("CreateThread() failed: ");
PrintfSysErr(threadItem);
}
DeleteMsgPort(portItem);
}
else
{
printf("CreateMsgPort() failed: ");
PrintfSysErr(portItem);
}
FreeSignal(parentSig);
}
else
{
printf("AllocSignal() failed");
}
return 0;
}