Passing Messages


The message system allows tasks to send data to one another, providing information flow from task to task. Tasks must create the following elements for the message system to work:

Once the elements are in place, the sending task follows these procedures:

The receiving task does one of the following procedures:

Once a message arrives at a task's message port, the task follows these procedures:

The message system is very flexible, and allows a single task to create multiple message ports and multiple messages. A single message port can receive many messages; they're queued up and retrieved one at a time by the receiving task. A message port allows a single task to receive, in serial order, messages from many other tasks.

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.

Creating a Message Port

Before a task can send or receive a message, it must have at least created one message port. To create a message port, use this call:

Item CreateMsgPort( const char *name, uint8 pri, uint32 signal )
The call accepts three arguments: a name string, 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.

Creating a Message

To create a message to send to a specific message port, a task must first create its own message port to use as a reply port. Then, it must specify which of three message types to create:

If the task sends 8 or fewer bytes of data within the message itself, it should create a small message.

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.

Standard Messages

To create a standard message, use this call:

Item CreateMsg( const char *name, uint8 pri, Item mp )
The first argument, 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);
    }

Small Messages

Both standard and buffered messages have message structure fields with pointers to a data block and the size of the data block. A small message uses those same fields to store up to 8 bytes of data instead of storing a pointer and a block size value.

To create a small message, use CreateSmallMsg():

Item CreateSmallMsg( const char *name, uint8 pri, Item mp )
The arguments are identical to those for CreateMsg(). You'll see the difference when you call SendSmallMsg() (described later).

Buffered Messages

Some applications require a message that can carry data within the message itself. This lets the sender copy data into the message, send the message, and not have to keep around its original data buffer. The buffer is no longer needed because the data is now in the message itself. In addition, when the destination task returns a buffered message, it can also write data to the message's buffer, allowing a great deal of information to be returned.

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

Sending a Message

A task can send all three types of messages, but each requires different handling.

Small Messages

To send a small message, use this call:

Err SendSmallMsg( Item mp, Item msg, uint32 val1, uint32 val2 )
The first argument, 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.

Standard and Buffered Messages

To send a standard or buffered message, use this call:

Err SendMsg( Item mp, Item msg, const void *dataptr, int32 datasize )
Like 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.

Receiving a Message

Once a message is sent to a message port, it is held in the message port's message queue until the receiving task retrieves the message, or the sending task decides to pull it back. The messages within the queue are put in order first by priority, and then by order received.

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.

Waiting for a Message

To enter the wait state until a message arrives on a specific message port, a task uses this call:

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.

Checking for a Message

To check for a message at a message port without entering the wait state, a task uses this call:

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

Working With a Message

Once a task receives a message, it interprets the contents of the message to determine what the message means. To do this, the task must obtain a pointer to the Message structure associated with the message's item number:

Message *msg;
Item     msgItem;

   msg = (Message *)LookupItem(msgItem);

Once the task has the pointer, it can determine the type of message it has received by inspecting the contents of the 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.

Small Messages

When you receive a small message, the only information you can get from the message is contained in the 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.

Standard Messages

When you receive a standard message, the 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().

Buffered Messages

When a task receives a buffered message, the 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.

Pulling Back a Message

When a sending task sends a message, the task can pull the message out of a message queue before the message has been received. To do so, it uses the GetThisMsg() call:

Item GetThisMsg( Item msg )
The single argument, 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.

Replying to a Message

Once a message is received, the receiving task can reply, which returns a message to its reply port. The message contains 4 bytes that are set aside for a reply code; the replying task assigns an arbitrary value for storage there, often a value that reports the success or failure of message handling.

Once again, the three different message types require different handling when replying to a message.

Small Messages

To send back a small message in reply to the sending task, use this call:

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.

Standard and Buffered Messages

To send back a standard or buffered message in reply, use this call:

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.

Finding a Message Port

When a task sends a message to message a port, it needs to know the item number of the port. To get the item number of the port, it is often necessary to use the FindMsgPort() call:

Item FindMsgPort( const char *name )
When you provide 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.

Example Code for Messages

The code in Example 2 demonstrates how to send messages among threads or tasks.

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;
}