Copyright (c) Hyperion Entertainment and contributors.

Difference between revisions of "Exec Messages and Ports"

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
m (→‎Port2.c: Put ASOT_Message in upper case)
 
(22 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{CodeReview}}
 
 
== Exec Messages and Ports ==
 
== Exec Messages and Ports ==
   
For inter-process communication, Exec provides a consistent, high-performance mechanism of messages and ports. This mechanism is used to pass message structures of arbitrary sizes from task to task, interrupt to task, or task to software interrupt. In addition, messages are often used to coordinate operations between cooperating tasks. This chapter describes many of the details of using messages and ports that the casual Amiga programmer won't need. See the "Introduction to Exec" chapter of this manual for a general introduction to using messages and ports.
+
For inter-process communication, Exec provides a consistent, high-performance mechanism of messages and ports. This mechanism is used to pass message structures of arbitrary sizes from task to task, interrupt to task, or task to software interrupt. In addition, messages are often used to coordinate operations between cooperating tasks. This section describes many of the details of using messages and ports that the casual Amiga programmer won't need. See [[Introduction_to_Exec|Introduction to Exec]] for a general introduction to using messages and ports.
   
 
A ''message'' data structure has two parts: system linkage and message body. The system linkage is used by Exec to attach a given message to its destination. The message body contains the actual data of interest. The message body is any arbitrary data up to 64K bytes in size. The message body data can include pointers to other data blocks of any size.
 
A ''message'' data structure has two parts: system linkage and message body. The system linkage is used by Exec to attach a given message to its destination. The message body contains the actual data of interest. The message body is any arbitrary data up to 64K bytes in size. The message body data can include pointers to other data blocks of any size.
Line 12: Line 11:
 
== Message Ports ==
 
== Message Ports ==
   
Message ports are rendezvous points at which messages are collected. A port may contain any number of outstanding messages from many different originators. When a message arrives at a port, the message is appended to the end of the list of messages for that port, and a prespecified arrival action is invoked. This action may do nothing, or it may cause a predefined task signal or software interrupt (see the "Exec Interrupts" chapter).
+
Message ports are rendezvous points at which messages are collected. A port may contain any number of outstanding messages from many different originators. When a message arrives at a port, the message is appended to the end of the list of messages for that port, and a pre-specified arrival action is invoked. This action may do nothing, or it may cause a predefined task signal or software interrupt (see [[Exec_Interrupts|Exec Interrupts]]).
   
 
Like many Exec structures, ports may be given a symbolic name. Such names are particularly useful for tasks that must rendezvous with dynamically created ports. They are also useful for debugging purposes.
 
Like many Exec structures, ports may be given a symbolic name. Such names are particularly useful for tasks that must rendezvous with dynamically created ports. They are also useful for debugging purposes.
Line 68: Line 67:
 
=== Deleting a Message Port ===
 
=== Deleting a Message Port ===
   
Before a message port is deleted, all outstanding messages from other tasks must be returned. This is done by getting and replying to all messages at the port until message queue is empty. Of course, there is no need to reply to messages owned by the current task (the task performing the port deletion). Public ports attached to the system with AddPort() must be removed from the system with RemPort() before deallocation. CreatePort() and DeletePort() handle this automatically.
+
Before a message port is deleted, all outstanding messages from other tasks must be returned. This is done by getting and replying to all messages at the port until message queue is empty. Of course, there is no need to reply to messages owned by the current task (the task performing the port deletion). Public ports must be removed from the system properly before deallocation. If a signal was allocated for the message port, it must also be freed. FreeSysObject() handles all of this automatically.
   
The following example of port deletion is equivalent to the DeletePort() function. Note that DeletePort() must only be used on ports created with CreatePort().
+
Prior to V50 of the operating system, message ports must be freed using the correct corresponding function such as DeletePort() or DeleteMsgPort(). The FreeSysObject() function must only be used on ports which were allocated with AllocSysObject().
 
<pre>
 
void DeletePort(mp)
 
struct MsgPort *mp;
 
{
 
if ( mp-&gt;mp_Node.ln_Name ) RemPort(mp); /* if it was public... */
 
 
mp-&gt;mp_SigTask = (struct Task *) -1; /* Make it difficult to re-use the port */
 
mp-&gt;mp_MsgList.lh_Head = (struct Node *) -1;
 
 
FreeSignal( mp-&gt;mp_SigBit );
 
FreeMem( mp, (ULONG)sizeof(struct MsgPort) );
 
}
 
</pre>
 
 
To delete ports created with CreateMsgPort(), DeleteMsgPort() must be used. Note that these functions are only available in V36 and higher. If the port was made public with AddPort(), RemPort() must be used first, to remove the port from the system. Again, make sure all outstanding messages are replied to, so that the message queue is empty.
 
 
<pre>
 
struct MsgPort *newmp;
 
 
if (newmp)
 
{
 
if ( newmp-&gt;mp_Node.ln_Name ) RemPort(newmp); /* if it was public... */
 
DeleteMsgPort(newmp);
 
}
 
</pre>
 
   
 
=== How to Rendezvous at a Message Port ===
 
=== How to Rendezvous at a Message Port ===
 
 
   
 
The FindPort() function provides a means of finding the address of a public port given its symbolic name. For example, FindPort(&quot;Griffin&quot;) will return either the address of the message port named "Griffin" or NULL indicating that no such public port exists. Since FindPort() does not do any arbitration over access to public ports, the usage of FindPort() must be protected with Forbid()/Permit(). Names should be unique to prevent collisions among multiple applications. It is a good idea to use your application name as a prefix for your port name. FindPort() does not arbitrate for access to the port list. The owner of a port might remove it at any time. For these reasons a Forbid()/Permit() pair is required for the use of FindPort(). The port address can no longer be regarded as being valid after Permit() unless your application knows that the port cannot go away (for example, if your application created the port).
 
The FindPort() function provides a means of finding the address of a public port given its symbolic name. For example, FindPort(&quot;Griffin&quot;) will return either the address of the message port named "Griffin" or NULL indicating that no such public port exists. Since FindPort() does not do any arbitration over access to public ports, the usage of FindPort() must be protected with Forbid()/Permit(). Names should be unique to prevent collisions among multiple applications. It is a good idea to use your application name as a prefix for your port name. FindPort() does not arbitrate for access to the port list. The owner of a port might remove it at any time. For these reasons a Forbid()/Permit() pair is required for the use of FindPort(). The port address can no longer be regarded as being valid after Permit() unless your application knows that the port cannot go away (for example, if your application created the port).
Line 106: Line 77:
 
The following is an example of how to safely put a message to a specific port:
 
The following is an example of how to safely put a message to a specific port:
   
  +
<syntaxhighlight>
<pre>
 
#include &lt;exec/types.h&gt;
+
#include <exec/types.h>
#include &lt;exec/ports.h&gt;
+
#include <exec/ports.h>
   
BOOL MsgPort SafePutToPort(struct Message *message, STRPTR portname)
+
BOOL SafePutToPort(struct Message *message, CONST_STRPTR portname)
 
{
 
{
  +
IExec->Forbid();
struct MsgPort *port;
 
  +
  +
struct MsgPort *port = IExec->FindPort(portname);
  +
if (port != NULL)
  +
IExec->PutMsg(port,message);
  +
  +
IExec->Permit();
   
Forbid();
 
port = FindPort(portname);
 
if (port) PutMsg(port,message);
 
Permit();
 
 
return(port ? TRUE : FALSE); /* If FALSE, the port was not found */
 
return(port ? TRUE : FALSE); /* If FALSE, the port was not found */
   
Line 124: Line 97:
 
the message has been sent or not. */
 
the message has been sent or not. */
 
}
 
}
  +
</syntaxhighlight>
</pre>
 
   
 
== Messages ==
 
== Messages ==
Line 158: Line 131:
   
 
For this structure, the mn_Length field should be set to sizeof(struct XYMessage).
 
For this structure, the mn_Length field should be set to sizeof(struct XYMessage).
  +
  +
=== Creating a Message ===
  +
  +
Messages are allocated using AllocSysObject() with an object type of ASOT_MESSAGE. The ASOMSG_ReplyPort tag is set to the reply port if desired. Some messages may be sent in one direction in which case the ASOMSG_ReplyPort tag is not used. The size of the message is indicated with ASOMSG_Length and includes the size of the payload.
  +
  +
<syntaxhighlight>
  +
struct XYMessage xymsg = IExec->AllocSysObjectTags(ASOT_MESSAGE,
  +
ASOMSG_Size, sizeof(struct XYMessage),
  +
ASOMSG_ReplyPort, replyPort,
  +
TAG_END);
  +
</syntaxhighlight>
  +
  +
=== Deleting a Message ===
  +
  +
Messages are freed using FreeSysObject(). Programmers should be careful not to free a message which is still in use or queues in a message port.
   
 
=== Putting a Message ===
 
=== Putting a Message ===
Line 163: Line 151:
 
A message is delivered to a given destination port with the PutMsg() function. The message is queued to the port, and that port's arrival action is invoked. If the action specifies a task signal or a software interrupt, the originating task may temporarily lose the processor while the destination processes the message. If a reply to the message is required, the mn_ReplyPort field must be set up prior to the call to PutMsg().
 
A message is delivered to a given destination port with the PutMsg() function. The message is queued to the port, and that port's arrival action is invoked. If the action specifies a task signal or a software interrupt, the originating task may temporarily lose the processor while the destination processes the message. If a reply to the message is required, the mn_ReplyPort field must be set up prior to the call to PutMsg().
   
Here is a code fragment for putting a message to a public port. A complete example is printed at the end of the chapter.
+
Here is a code fragment for putting a message to a public port. A complete example is at the end of the article.
   
 
<syntaxhighlight>
 
<syntaxhighlight>
Line 171: Line 159:
 
#include <libraries/dos.h>
 
#include <libraries/dos.h>
   
int main();
 
 
BOOL SafePutToPort(struct Message *, CONST_STRPTR);
 
BOOL SafePutToPort(struct Message *, CONST_STRPTR);
   
Line 183: Line 170:
 
{
 
{
 
struct MsgPort *xyport, *xyreplyport;
 
struct MsgPort *xyport, *xyreplyport;
struct XYMessage *xymsg, *msg;
+
struct XYMessage *msg;
 
BOOL foundport;
 
BOOL foundport;
   
 
/* Allocate the message we're going to send. */
 
/* Allocate the message we're going to send. */
xymsg = IExec->AllocSysObjectTags(ASOT_MESSAGE,
+
struct XYMessage *xymsg = IExec->AllocSysObjectTags(ASOT_MESSAGE,
 
ASOMSG_Size, sizeof(struct XYMessage),
 
ASOMSG_Size, sizeof(struct XYMessage),
 
TAG_END);
 
TAG_END);
Line 217: Line 204:
 
}
 
}
 
else IDOS->Printf("Couldn't get memory for xymessage\n");
 
else IDOS->Printf("Couldn't get memory for xymessage\n");
  +
  +
return 0;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 224: Line 213:
 
A task may go to sleep waiting for a message to arrive at one or more ports. This technique is widely used on the Amiga as a general form of event notification. For example, it is used extensively by tasks for I/O request completion.
 
A task may go to sleep waiting for a message to arrive at one or more ports. This technique is widely used on the Amiga as a general form of event notification. For example, it is used extensively by tasks for I/O request completion.
   
The MsgPort.mp_SigTask field contains the address of the task to be signaled and mp_SigBit contains a preallocated signal number (as described in the "Exec Tasks" chapter).
+
The MsgPort.mp_SigTask field contains the address of the task to be signaled and mp_SigBit contains a preallocated signal number (as described in [[Exec_Tasks|Exec Tasks]]).
   
 
You can call the WaitPort() function to wait for a message to arrive at a port. This function will return the first message (it may not be the only) queued to a port. Note that your application must still call GetMsg() to remove the message from the port. If the port is empty, your task will go to sleep waiting for the first message. If the port is not empty, your task will not go to sleep. It is possible to receive a signal for a port without a message being present yet. The code processing the messages should be able to handle this. The following code illustrates WaitPort().
 
You can call the WaitPort() function to wait for a message to arrive at a port. This function will return the first message (it may not be the only) queued to a port. Note that your application must still call GetMsg() to remove the message from the port. If the port is empty, your task will go to sleep waiting for the first message. If the port is not empty, your task will not go to sleep. It is possible to receive a signal for a port without a message being present yet. The code processing the messages should be able to handle this. The following code illustrates WaitPort().
   
 
<syntaxhighlight>
 
<syntaxhighlight>
  +
struct MsgPort *xyport = IExec->AllocSysObjectTags(ASOT_PORT,
struct XYMessage *xy_msg;
 
struct MsgPort *xyport;
 
 
xyport = IExec->AllocSysObjectTags(ASOT_PORT,
 
 
ASOPORT_Name, "xyport",
 
ASOPORT_Name, "xyport",
 
TAG_END);
 
TAG_END);
  +
if (xyport == 0)
 
  +
if (xyport == NULL)
 
{
 
{
 
IDOS->Printf("Couldn't create xyport\n");
 
IDOS->Printf("Couldn't create xyport\n");
Line 241: Line 228:
 
}
 
}
   
xy_msg = IExec->WaitPort(xyport); /* go to sleep until message arrives */
+
struct XYMessage *xy_msg = IExec->WaitPort(xyport); /* go to sleep until message arrives */
 
</syntaxhighlight>
 
</syntaxhighlight>
   
A more general form of waiting for a message involves the use of the Wait() function (see the "Exec Signals" chapter). This function waits for task event signals directly. If the signal assigned to the message port occurs, the task will awaken. Using the Wait() function is more general because you can wait for more than one signal. By combining the signal bits from each port into one mask for the Wait() function, a loop can be set up to process all messages at all ports.
+
A more general form of waiting for a message involves the use of the Wait() function (see [[Exec_Signals|Exec Signals]]). This function waits for task event signals directly. If the signal assigned to the message port occurs, the task will awaken. Using the Wait() function is more general because you can wait for more than one signal. By combining the signal bits from each port into one mask for the Wait() function, a loop can be set up to process all messages at all ports.
   
 
Here's an example using Wait():
 
Here's an example using Wait():
Line 250: Line 237:
 
<syntaxhighlight>
 
<syntaxhighlight>
 
struct XYMessage *xy_msg;
 
struct XYMessage *xy_msg;
struct MsgPort *xyport;
 
uint32 usersig, portsig;
 
 
BOOL ABORT = FALSE;
 
BOOL ABORT = FALSE;
   
xyport = IExec->AllocSysObjectTags(ASOT_PORT,
+
struct MsgPort *xyport = IExec->AllocSysObjectTags(ASOT_PORT,
 
ASOPORT_Name, "xyport",
 
ASOPORT_Name, "xyport",
 
TAG_END);
 
TAG_END);
  +
 
if (xyport != NULL)
 
if (xyport != NULL)
 
{
 
{
portsig = 1 << xyport-&gt;mp_SigBit;
+
uint32 portsig = 1 << xyport->mp_SigBit;
usersig = SIGBREAKF_CTRL_C; /* User can break with CTRL-C. */
+
uint32 usersig = SIGBREAKF_CTRL_C; /* User can break with CTRL-C. */
 
for (;;)
 
for (;;)
 
{
 
{
signal = IExec->Wait(portsig | usersig); /* Sleep till someone signals. */
+
uint32 signal = IExec->Wait(portsig | usersig); /* Sleep till someone signals. */
   
if (signal &amp; portsig) /* Got a signal at the msgport. */
+
if (signal & portsig) /* Got a signal at the msgport. */
 
{ . . .
 
{ . . .
 
}
 
}
if (signal &amp; usersig) /* Got a signal from the user. */
+
if (signal & usersig) /* Got a signal from the user. */
 
{
 
{
 
ABORT = TRUE; /* Time to clean up. */
 
ABORT = TRUE; /* Time to clean up. */
Line 280: Line 266:
 
</syntaxhighlight>
 
</syntaxhighlight>
   
  +
{{Note|text=WaitPort() only returns a pointer to the first message in a port. It does not actually remove the message from the port queue.|title=WaitPort() Does Not Remove A Message}}
{| class="wikitable"
 
| ''WaitPort() Does Not Remove A Message''. WaitPort() only returns a pointer to the first message in a port. It does not actually remove the message from the port queue.
 
|}
 
   
 
=== Getting a Message ===
 
=== Getting a Message ===
Line 314: Line 298:
 
Notice that the reply does not occur until ''after'' the message values have been used.
 
Notice that the reply does not occur until ''after'' the message values have been used.
   
Often the operations associated with receiving a message involve returning ''results'' to the originator. Typically this is done within the message itself. The receiver places the results in fields defined (or perhaps reused) within the message body before replying the message back to the originator. Receipt of the replied message at the originator’s reply port indicates it is once again safe for the originator to use or change the values found within the message.
+
Often the operations associated with receiving a message involve returning ''results'' to the originator. Typically this is done within the message itself. The receiver places the results in fields defined (or perhaps reused) within the message body before replying the message back to the originator. Receipt of the replied message at the originator reply port indicates it is once again safe for the originator to use or change the values found within the message.
   
 
The following are two short example tasks that communicate by sending, waiting for and replying to messages. Run these two programs together.
 
The following are two short example tasks that communicate by sending, waiting for and replying to messages. Run these two programs together.
Line 320: Line 304:
 
==== Port1.c ====
 
==== Port1.c ====
   
  +
<syntaxhighlight>
<pre>
 
;/* port1.c - Execute me to compile with SAS C 5.10
+
// port1.c - port and message example, run at the same time as port2.c
LC -b1 -cfistq -v -y -j73 port1.c
 
Blink FROM LIB:c.o,port1.o TO port1 LIBRARY LIB:LC.lib,LIB:Amiga.lib
 
quit
 
   
  +
#include <exec/types.h>
* port1.c - port and message example, run at the same time as port2.c
 
  +
#include <exec/ports.h>
*/
 
  +
#include <dos/dos.h>
 
#include &lt;exec/types.h&gt;
+
#include <proto/exec.h>
#include &lt;exec/ports.h&gt;
+
#include <proto/dos.h>
#include &lt;dos/dos.h&gt;
 
#include &lt;clib/exec_protos.h&gt;
 
#include &lt;clib/alib_protos.h&gt;
 
#include &lt;stdio.h&gt;
 
 
#ifdef LATTICE
 
int CXBRK(void) { return(0); } /* Disable Lattice CTRL-C handling */
 
int chkabort(void) {return(0);}
 
#endif
 
   
 
struct XYMessage {
 
struct XYMessage {
 
struct Message xym_Msg;
 
struct Message xym_Msg;
WORD xy_X;
+
int16 xy_X;
WORD xy_Y;
+
int16 xy_Y;
 
};
 
};
   
void main(int argc, char **argv)
+
int main()
 
{
 
{
struct MsgPort *xyport;
 
struct XYMessage *xymsg;
 
ULONG portsig, usersig, signal;
 
 
BOOL ABORT = FALSE;
 
BOOL ABORT = FALSE;
   
if (xyport = CreatePort(&quot;xyport&quot;, 0))
+
struct MsgPort *xyport = IExec->AllocSysObjectTags(ASOT_PORT,
  +
ASOPORT_Name, "xyport",
  +
TAG_END);
  +
  +
if (xyport != NULL)
 
{
 
{
portsig = 1 &lt;&lt; xyport-&gt;mp_SigBit; /* Give user a `break' signal. */
+
uint32 portsig = 1U << xyport->mp_SigBit; /* Give user a `break' signal. */
usersig = SIGBREAKF_CTRL_C;
+
uint32 usersig = SIGBREAKF_CTRL_C;
   
printf(&quot;Start port2 in another shell. CTRL-C here when done.\n&quot;);
+
IDOS->Printf("Start port2 in another shell. CTRL-C here when done.\n");
 
do
 
do
{ /* port1 will wait forever and reply */
+
{ /* port1 will wait forever and reply */
signal = Wait(portsig | usersig); /* to messages, until the user breaks. */
+
uint32 signal = IExec->Wait(portsig | usersig); /* to messages, until the user breaks. */
  +
struct XYMessage *xymsg = 0;
   
 
/* Since we only have one port that might get messages we */
 
/* Since we only have one port that might get messages we */
if (signal &amp; portsig) /* have to reply to, it is not really necessary to test for */
+
if (signal & portsig) /* have to reply to, it is not really necessary to test for */
 
{ /* the portsignal. If there is not message at the port, xymsg */
 
{ /* the portsignal. If there is not message at the port, xymsg */
   
while(xymsg = (struct XYMessage *)GetMsg(xyport)) /* simply will be NULL. */
+
while(xymsg = (struct XYMessage *)IExec->GetMsg(xyport)) /* simply will be NULL. */
 
{
 
{
printf(&quot;port1 received: x = %d y = %d\n&quot;, xymsg-&gt;xy_X, xymsg-&gt;xy_Y);
+
IDOS->Printf("port1 received: x = %d y = %d\n", xymsg->xy_X, xymsg->xy_Y);
   
xymsg-&gt;xy_X += 50; /* Since we have not replied yet to the owner of */
+
xymsg->xy_X += 50; /* Since we have not replied yet to the owner of */
xymsg-&gt;xy_Y += 50; /* xymsg, we can change the data contents of xymsg. */
+
xymsg->xy_Y += 50; /* xymsg, we can change the data contents of xymsg. */
   
printf(&quot;port1 replying with: x = %d y = %d\n&quot;, xymsg-&gt;xy_X, xymsg-&gt;xy_Y);
+
IDOS->Printf("port1 replying with: x = %d y = %d\n", xymsg->xy_X, xymsg->xy_Y);
ReplyMsg((struct Message *)xymsg);
+
IExec->ReplyMsg((struct Message *)xymsg);
 
}
 
}
 
}
 
}
   
if (signal &amp; usersig) /* The user wants to abort. */
+
if (signal & usersig) /* The user wants to abort. */
 
{
 
{
while(xymsg = (struct XYMessage *)GetMsg(xyport)) /* Make sure port is empty. */
+
while(xymsg = (struct XYMessage *)IExec->GetMsg(xyport)) /* Make sure port is empty. */
ReplyMsg((struct Message *)xymsg);
+
IExec->ReplyMsg((struct Message *)xymsg);
 
ABORT = TRUE;
 
ABORT = TRUE;
 
}
 
}
 
}
 
}
 
while (ABORT == FALSE);
 
while (ABORT == FALSE);
DeletePort(xyport);
+
IExec->FreeSysObject(ASOT_PORT, xyport);
 
}
 
}
else printf(&quot;Couldn't create 'xyport'\n&quot;);
+
else IDOS->Printf("Couldn't create 'xyport'\n");
  +
return 0;
 
}
 
}
  +
</syntaxhighlight>
</pre>
 
   
 
==== Port2.c ====
 
==== Port2.c ====
   
  +
<syntaxhighlight>
<pre>
 
;/* port2.c - Execute me to compile me with SAS C 5.10
+
// port2.c - port and message example, run at the same time as port1.c
LC -b1 -cfistq -v -y -j73 port2.c
 
Blink FROM LIB:c.o,port2.o TO port2 LIBRARY LIB:LC.lib,LIB:Amiga.lib
 
quit
 
   
  +
#include <exec/types.h>
** port2.c - port and message example, run at the same time as port1.c
 
  +
#include <exec/ports.h>
*/
 
  +
#include <exec/memory.h>
  +
#include <dos/dos.h>
  +
#include <proto/exec.h>
  +
#include <proto/dos.h>
   
  +
BOOL SafePutToPort(struct Message *, CONST_STRPTR);
#include &lt;exec/types.h&gt;
 
#include &lt;exec/ports.h&gt;
 
#include &lt;exec/memory.h&gt;
 
#include &lt;dos/dos.h&gt;
 
#include &lt;clib/exec_protos.h&gt;
 
#include &lt;clib/alib_protos.h&gt;
 
#include &lt;stdio.h&gt;
 
 
#ifdef LATTICE
 
int CXBRK(void) { return(0); } /* Disable Lattice CTRL-C handling */
 
int chkabort(void) {return(0);}
 
#endif
 
 
BOOL SafePutToPort(struct Message *, STRPTR);
 
   
 
struct XYMessage {
 
struct XYMessage {
 
struct Message xy_Msg;
 
struct Message xy_Msg;
WORD xy_X;
+
int16 xy_X;
WORD xy_Y;
+
int16 xy_Y;
 
};
 
};
   
void main(int argc, char **argv)
+
int main()
 
{
 
{
struct MsgPort *xyreplyport;
+
struct MsgPort *xyreplyport = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
  +
struct XYMessage *xymsg, *reply;
 
  +
if (xyreplyport != NULL)
/* Using CreatePort() with no name */
 
if (xyreplyport = CreatePort(0,0)) /* because this port need not be public. */
 
 
{
 
{
if (xymsg = (struct XYMessage *) AllocMem(sizeof(struct XYMessage), MEMF_PUBLIC | MEMF_CLEAR))
+
struct XYMessage *reply = NULL;
  +
  +
struct XYMessage *xymsg = IExec->AllocSysObjectTags(ASOT_MESSAGE,
  +
ASOMSG_Size, sizeof(struct XYMessage),
  +
ASOMSG_ReplyPort, xyreplyport,
  +
TAG_END);
  +
  +
if (xymsg != NULL)
 
{
 
{
xymsg-&gt;xy_Msg.mn_Node.ln_Type = NT_MESSAGE; /* make up a message, */
+
xymsg->xy_X = 10; /* our special message information. */
xymsg-&gt;xy_Msg.mn_Length = sizeof(struct XYMessage); /* including the reply port. */
+
xymsg->xy_Y = 20;
xymsg-&gt;xy_Msg.mn_ReplyPort = xyreplyport;
 
xymsg-&gt;xy_X = 10; /* our special message information. */
 
xymsg-&gt;xy_Y = 20;
 
   
printf(&quot;Sending to port1: x = %d y = %d\n&quot;, xymsg-&gt;xy_X, xymsg-&gt;xy_Y);
+
IDOS->Printf("Sending to port1: x = %d y = %d\n", xymsg->xy_X, xymsg->xy_Y);
  +
 
/* port2 will simply try to put */
 
/* port2 will simply try to put */
  +
if (SafePutToPort((struct Message *)xymsg, "xyport")) /* one message to port1 wait for */
 
if (SafePutToPort((struct Message *)xymsg, &quot;xyport&quot;)) /* one message to port1 wait for */
 
 
{ /* the reply, and then exit */
 
{ /* the reply, and then exit */
WaitPort(xyreplyport);
+
IExec->WaitPort(xyreplyport);
if (reply = (struct XYMessage *)GetMsg(xyreplyport))
+
if (reply = (struct XYMessage *)IExec->GetMsg(xyreplyport))
printf(&quot;Reply contains: x = %d y = %d\n&quot;, /* We don't ReplyMsg since */
+
IDOS->Printf("Reply contains: x = %d y = %d\n", /* We don't ReplyMsg since */
xymsg-&gt;xy_X, xymsg-&gt;xy_Y); /* WE initiated the message. */
+
xymsg->xy_X, xymsg->xy_Y); /* WE initiated the message. */
   
 
/* Since we only use this private port for receiving replies, and we sent */
 
/* Since we only use this private port for receiving replies, and we sent */
Line 456: Line 422:
 
/* habit to always handle all messages at the port before you delete it. */
 
/* habit to always handle all messages at the port before you delete it. */
 
}
 
}
else printf(&quot;Can't find 'xyport'; start port1 in a separate shell\n&quot;);
+
else IDOS->Printf("Can't find 'xyport'; start port1 in a separate shell\n");
FreeMem(xymsg, sizeof(struct XYMessage));
+
IExec->FreeSysObject(ASOT_MESSAGE, xymsg);
 
}
 
}
else printf(&quot;Couldn't get memory\n&quot;);
+
else IDOS->Printf("Couldn't get memory\n");
DeletePort(xyreplyport);
+
IExec->FreeSysObject(ASOT_PORT, xyreplyport);
 
}
 
}
else printf(&quot;Couldn't create xyreplyport\n&quot;);
+
else IDOS->Printf("Couldn't create xyreplyport\n");
  +
return 0;
 
}
 
}
   
  +
BOOL SafePutToPort(struct Message *message, CONST_STRPTR portname)
 
BOOL SafePutToPort(struct Message *message, STRPTR portname)
 
 
{
 
{
  +
IExec->Forbid();
struct MsgPort *port;
 
  +
struct MsgPort *port = IExec->FindPort(portname);
 
  +
if (port != NULL) IExec->PutMsg(port, message);
Forbid();
 
port = FindPort(portname);
+
IExec->Permit();
if (port) PutMsg(port, message);
 
Permit();
 
 
return(port ? TRUE : FALSE); /* FALSE if the port was not found */
 
return(port ? TRUE : FALSE); /* FALSE if the port was not found */
   
 
/* Once we've done a Permit(), the port might go away and leave us with an invalid port */
 
/* Once we've done a Permit(), the port might go away and leave us with an invalid port */
 
} /* address. So we return just a BOOL to indicate whether the message has been sent or not. */
 
} /* address. So we return just a BOOL to indicate whether the message has been sent or not. */
  +
</syntaxhighlight>
</pre>
 
   
 
== Function Reference ==
 
== Function Reference ==

Latest revision as of 23:36, 21 January 2021

Exec Messages and Ports

For inter-process communication, Exec provides a consistent, high-performance mechanism of messages and ports. This mechanism is used to pass message structures of arbitrary sizes from task to task, interrupt to task, or task to software interrupt. In addition, messages are often used to coordinate operations between cooperating tasks. This section describes many of the details of using messages and ports that the casual Amiga programmer won't need. See Introduction to Exec for a general introduction to using messages and ports.

A message data structure has two parts: system linkage and message body. The system linkage is used by Exec to attach a given message to its destination. The message body contains the actual data of interest. The message body is any arbitrary data up to 64K bytes in size. The message body data can include pointers to other data blocks of any size.

Messages are always sent to a predetermined destination port. At a port, incoming messages are queued in a first-in-first-out (FIFO) order. There are no system restrictions on the number of ports or the number of messages that may be queued to a port (other than the amount of available system memory).

Messages are always queued by reference, i.e., by a pointer to the message. For performance reasons message copying is not performed. In essence, a message between two tasks is a temporary license for the receiving task to use a portion of the memory space of the sending task; that portion being the message itself. This means that if task A sends a message to task B, the message is still part of the task A context. Task A, however, should not access the message until it has been replied; that is, until task B has sent the message back, using the ReplyMsg() function. This technique of message exchange imposes important restrictions on message access.

Message Ports

Message ports are rendezvous points at which messages are collected. A port may contain any number of outstanding messages from many different originators. When a message arrives at a port, the message is appended to the end of the list of messages for that port, and a pre-specified arrival action is invoked. This action may do nothing, or it may cause a predefined task signal or software interrupt (see Exec Interrupts).

Like many Exec structures, ports may be given a symbolic name. Such names are particularly useful for tasks that must rendezvous with dynamically created ports. They are also useful for debugging purposes.

A message port consists of a MsgPort structure as defined in the <exec/ports.h> and <exec/ports.i> include files. The C structure for a port is as follows:

struct MsgPort {
    struct Node  mp_Node;
    UBYTE        mp_Flags;
    UBYTE        mp_SigBit;
    struct Task *mp_SigTask;
    struct List  mp_MsgList;
};
mp_Node
is a standard Node structure. This is useful for tasks that might want to rendezvous with a particular message port by name.
mp_FLags
are used to indicate message arrival actions. See the explanation below.
mp_SigBit
is the signal bit number when a port is used with the task signal arrival action.
mp_SigTask
is a pointer to the task to be signaled. If a software interrupt arrival action is specified, this is a pointer to the interrupt structure.
mp_MsgList
is the list header for all messages queued to this port. (See Exec Lists and Queues).

The mp_Flags field contains a subfield indicated by the PF_ACTION mask. This sub-field specifies the message arrival action that occurs when a port receives a new message.

The possibilities are as follows:

PA_SIGNAL
This flag tells Exec to signal the mp_SigTask using signal number mp_SigBit on the arrival of a new message. Every time a message is put to the port another signal will occur regardless of how many messages have been queued to the port.
PA_SOFTINT
This flag tells Exec to Cause() a software interrupt when a message arrives at the port. In this case, the mp_SigTask field must contain a pointer to a struct Interrupt rather than a Task pointer. The software interrupt will be Caused every time a message is received.
PA_IGNORE
This flag tells Exec to perform no operation other than queuing the message. This action is often used to stop signaling or software interrupts without disturbing the contents of the mp_SigTask field.

It is important to realize that a port's arrival action will occur for each new message queued, and that there is not a one-to-one correspondence between messages and signals. Task signals are only single-bit flags so there is no record of how many times a particular signal occurred. There may be many messages queued and only a single task signal; sometimes however there may be a signal, but no messages. All of this has certain implications when designing code that deals with these actions. Your code should not depend on receiving a signal for every message at your port. All of this is also true for software interrupts.

Creating a Message Port

To create a new message port use AllocSysObject() with an object type of ASOT_PORT. If you want to make the port public, you will need to use the ASOPORT_Name tag. Don't make a port public when it is not necessary for it to be so.

Prior to V50 of the operating system, functions such as CreatePort() and CreateMsgPort() were often used. Some older applications may even have created their own static message ports by hand. Although all of the older methods still function for backwards compatibility, they should no longer be used.

Using dynamic message ports with ASOT_PORT is the only way to ensure your applications will remain compatible and its message ports automatically freed by the system when required.

Deleting a Message Port

Before a message port is deleted, all outstanding messages from other tasks must be returned. This is done by getting and replying to all messages at the port until message queue is empty. Of course, there is no need to reply to messages owned by the current task (the task performing the port deletion). Public ports must be removed from the system properly before deallocation. If a signal was allocated for the message port, it must also be freed. FreeSysObject() handles all of this automatically.

Prior to V50 of the operating system, message ports must be freed using the correct corresponding function such as DeletePort() or DeleteMsgPort(). The FreeSysObject() function must only be used on ports which were allocated with AllocSysObject().

How to Rendezvous at a Message Port

The FindPort() function provides a means of finding the address of a public port given its symbolic name. For example, FindPort("Griffin") will return either the address of the message port named "Griffin" or NULL indicating that no such public port exists. Since FindPort() does not do any arbitration over access to public ports, the usage of FindPort() must be protected with Forbid()/Permit(). Names should be unique to prevent collisions among multiple applications. It is a good idea to use your application name as a prefix for your port name. FindPort() does not arbitrate for access to the port list. The owner of a port might remove it at any time. For these reasons a Forbid()/Permit() pair is required for the use of FindPort(). The port address can no longer be regarded as being valid after Permit() unless your application knows that the port cannot go away (for example, if your application created the port).

The following is an example of how to safely put a message to a specific port:

#include <exec/types.h>
#include <exec/ports.h>
 
BOOL SafePutToPort(struct Message *message, CONST_STRPTR portname)
{
    IExec->Forbid();
 
    struct MsgPort *port = IExec->FindPort(portname);
    if (port != NULL)
        IExec->PutMsg(port,message);
 
    IExec->Permit();
 
    return(port ? TRUE : FALSE);      /* If FALSE, the port was not found */
 
    /* Once we've done a Permit(), the port might go away and leave us with
       an invalid port address. So we return just a BOOL to indicate whether
       the message has been sent or not. */
}

Messages

As mentioned earlier, a message contains both system header information and the actual message content. The system header is of the Message form defined in <exec/ports.h>. This structure is as follows:

struct Message {
    struct Node     mn_Node;
    struct MsgPort *mn_ReplyPort;
    UWORD           mn_Length;
};
mn_Node
is a standard Node structure used for port linkage.
mn_ReplyPort
is used to indicate a port to which this message will be returned when a reply is necessary.
mn_Length
indicates the total length of the message, including the Message structure itself.

This structure is always attached to the head of all messages. For example, if you want a message structure that contains the x and y coordinates of a point on the screen, you could define it as follows:

struct XYMessage {
    struct Message xy_Msg;
    UWORD          xy_X;
    UWORD          xy_Y;
}

For this structure, the mn_Length field should be set to sizeof(struct XYMessage).

Creating a Message

Messages are allocated using AllocSysObject() with an object type of ASOT_MESSAGE. The ASOMSG_ReplyPort tag is set to the reply port if desired. Some messages may be sent in one direction in which case the ASOMSG_ReplyPort tag is not used. The size of the message is indicated with ASOMSG_Length and includes the size of the payload.

struct XYMessage xymsg = IExec->AllocSysObjectTags(ASOT_MESSAGE,
  ASOMSG_Size, sizeof(struct XYMessage),
  ASOMSG_ReplyPort, replyPort,
  TAG_END);

Deleting a Message

Messages are freed using FreeSysObject(). Programmers should be careful not to free a message which is still in use or queues in a message port.

Putting a Message

A message is delivered to a given destination port with the PutMsg() function. The message is queued to the port, and that port's arrival action is invoked. If the action specifies a task signal or a software interrupt, the originating task may temporarily lose the processor while the destination processes the message. If a reply to the message is required, the mn_ReplyPort field must be set up prior to the call to PutMsg().

Here is a code fragment for putting a message to a public port. A complete example is at the end of the article.

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <libraries/dos.h>
 
BOOL SafePutToPort(struct Message *, CONST_STRPTR);
 
struct XYMessage {
    struct Message xy_Msg;
    uint16         xy_X;
    uint16         xy_Y;
};
 
int main()
{
    struct MsgPort *xyport, *xyreplyport;
    struct XYMessage *msg;
    BOOL   foundport;
 
    /* Allocate the message we're going to send. */
    struct XYMessage *xymsg = IExec->AllocSysObjectTags(ASOT_MESSAGE,
      ASOMSG_Size, sizeof(struct XYMessage),
      TAG_END);
 
    if (xymsg != NULL)
    {
        /* The replyport we'll use to get response */
        if (xyreplyport = IExec->AllocSysObject(ASOT_PORT, NULL)) {
 
            xymsg->xy_Msg.mn_ReplyPort = xyreplyport;
            xymsg->xy_X = 10;
            xymsg->xy_Y = 20;
 
            /* Now try to send that message to a public port named "xyport".
             * If foundport eq 0, the port isn't out there.
             */
            if (foundport = SafePutToPort((struct Message *)xymsg, "xyport"))
            {
 
            . . .                /* Now let's wait till the someone responds... */
 
            }
            else IDOS->Printf("Couldn't find 'xyport'\n");
 
            IExec->FreeSysObject(ASOT_PORT, xyreplyport);
        else IDOS->Printf("Couldn't create message port\n");
 
        IExec->FreeSysObject(ASOT_MESSAGE, xymsg);
    }
    else IDOS->Printf("Couldn't get memory for xymessage\n");
 
    return 0;
}

Waiting for a Message

A task may go to sleep waiting for a message to arrive at one or more ports. This technique is widely used on the Amiga as a general form of event notification. For example, it is used extensively by tasks for I/O request completion.

The MsgPort.mp_SigTask field contains the address of the task to be signaled and mp_SigBit contains a preallocated signal number (as described in Exec Tasks).

You can call the WaitPort() function to wait for a message to arrive at a port. This function will return the first message (it may not be the only) queued to a port. Note that your application must still call GetMsg() to remove the message from the port. If the port is empty, your task will go to sleep waiting for the first message. If the port is not empty, your task will not go to sleep. It is possible to receive a signal for a port without a message being present yet. The code processing the messages should be able to handle this. The following code illustrates WaitPort().

struct MsgPort *xyport = IExec->AllocSysObjectTags(ASOT_PORT,
  ASOPORT_Name, "xyport",
  TAG_END);
 
if (xyport == NULL)
{
    IDOS->Printf("Couldn't create xyport\n");
    return RETURN_FAIL;
}
 
struct XYMessage *xy_msg = IExec->WaitPort(xyport);  /* go to sleep until message arrives */

A more general form of waiting for a message involves the use of the Wait() function (see Exec Signals). This function waits for task event signals directly. If the signal assigned to the message port occurs, the task will awaken. Using the Wait() function is more general because you can wait for more than one signal. By combining the signal bits from each port into one mask for the Wait() function, a loop can be set up to process all messages at all ports.

Here's an example using Wait():

struct XYMessage *xy_msg;
BOOL ABORT = FALSE;
 
struct MsgPort *xyport = IExec->AllocSysObjectTags(ASOT_PORT,
  ASOPORT_Name, "xyport",
  TAG_END);
 
if (xyport != NULL)
{
    uint32 portsig = 1 << xyport->mp_SigBit;
    uint32 usersig = SIGBREAKF_CTRL_C;            /* User can break with CTRL-C.  */
    for (;;)
    {
        uint32 signal = IExec->Wait(portsig | usersig);  /* Sleep till someone signals.  */
 
        if (signal & portsig)              /* Got a signal at the msgport. */
        {   .  .  .
        }
        if (signal & usersig)              /* Got a signal from the user.  */
        {
            ABORT = TRUE;                  /* Time to clean up.            */
             . . .
        }
        if (ABORT) break;
    }
    IExec->FreeSysObject(ASOT_PORT, xyport);
}
else IDOS->Printf("Couldn't create xyport\n");
WaitPort() Does Not Remove A Message
WaitPort() only returns a pointer to the first message in a port. It does not actually remove the message from the port queue.

Getting a Message

Messages are usually removed from ports with the GetMsg() function. This function removes the next message at the head of the port queue and returns a pointer to it. If there are no messages in a port, this function returns a zero.

The example below illustrates the use of GetMsg() to print the contents of all messages in a port:

while (xymsg = IExec->GetMsg(xyport))
    IDOS->Printf("x=%ld y=%ld\n", xymsg->xy_X, xymsg->xy_Y);

Certain messages may be more important than others. Because ports impose FIFO ordering, these important messages may get queued behind other messages regardless of their priority. If it is necessary to recognize more important messages, it is easiest to create another port for these special messages.

Replying

When the operations associated with receiving a new message are finished, it is usually necessary to send the message back to the originator. The receiver replies the message by returning it to the originator using the ReplyMsg() function. This is important because it notifies the originator that the message can be reused or deallocated.

The ReplyMsg() function serves this purpose. It returns the message to the port specified in the mn_ReplyPort field of the message. If this field is zero, no reply is returned.

The previous example can be enhanced to reply to each of its messages:

while (xymsg = IExec->GetMsg(xyport)) {
    IDOS->Printf("x=%ld y=%ld\n", xymsg->xy_X, xymsg->xy_Y);
    IExec->ReplyMsg(xymsg);
}

Notice that the reply does not occur until after the message values have been used.

Often the operations associated with receiving a message involve returning results to the originator. Typically this is done within the message itself. The receiver places the results in fields defined (or perhaps reused) within the message body before replying the message back to the originator. Receipt of the replied message at the originator reply port indicates it is once again safe for the originator to use or change the values found within the message.

The following are two short example tasks that communicate by sending, waiting for and replying to messages. Run these two programs together.

Port1.c

// port1.c - port and message example, run at the same time as port2.c
 
#include <exec/types.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <proto/exec.h>
#include <proto/dos.h>
 
struct XYMessage {
    struct Message xym_Msg;
    int16          xy_X;
    int16          xy_Y;
};
 
int main()
{
    BOOL ABORT = FALSE;
 
    struct MsgPort *xyport = IExec->AllocSysObjectTags(ASOT_PORT,
      ASOPORT_Name, "xyport",
      TAG_END);
 
    if (xyport != NULL)
    {
        uint32 portsig = 1U << xyport->mp_SigBit;       /* Give user a `break' signal. */
        uint32 usersig = SIGBREAKF_CTRL_C;
 
        IDOS->Printf("Start port2 in another shell.  CTRL-C here when done.\n");
        do
        {                                                   /* port1 will wait forever and reply   */
            uint32 signal = IExec->Wait(portsig | usersig); /* to messages, until the user breaks. */
            struct XYMessage *xymsg = 0;
 
                                   /* Since we only have one port that might get messages we     */
            if (signal & portsig)  /* have to reply to, it is not really necessary to test for   */
            {                      /* the portsignal. If there is not message at the port, xymsg */
 
                while(xymsg = (struct XYMessage *)IExec->GetMsg(xyport))  /* simply will be NULL. */
                {
                    IDOS->Printf("port1 received: x = %d y = %d\n", xymsg->xy_X, xymsg->xy_Y);
 
                    xymsg->xy_X += 50;       /* Since we have not replied yet to the owner of    */
                    xymsg->xy_Y += 50;       /* xymsg, we can change the data contents of xymsg. */
 
                    IDOS->Printf("port1 replying with: x = %d y = %d\n", xymsg->xy_X, xymsg->xy_Y);
                    IExec->ReplyMsg((struct Message *)xymsg);
                }
            }
 
            if (signal & usersig)  /* The user wants to abort. */
            {
                while(xymsg = (struct XYMessage *)IExec->GetMsg(xyport))  /* Make sure port is empty. */
                    IExec->ReplyMsg((struct Message *)xymsg);
                ABORT = TRUE;
            }
        }
        while (ABORT == FALSE);
            IExec->FreeSysObject(ASOT_PORT, xyport);
        }
    else IDOS->Printf("Couldn't create 'xyport'\n");
    return 0;
}

Port2.c

// port2.c - port and message example, run at the same time as port1.c
 
#include <exec/types.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <proto/exec.h>
#include <proto/dos.h>
 
BOOL SafePutToPort(struct Message *, CONST_STRPTR);
 
struct XYMessage {
    struct Message xy_Msg;
    int16          xy_X;
    int16          xy_Y;
};
 
int main()
{
    struct MsgPort *xyreplyport = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
 
    if (xyreplyport != NULL)
    {
        struct XYMessage *reply = NULL;
 
        struct XYMessage *xymsg = IExec->AllocSysObjectTags(ASOT_MESSAGE,
          ASOMSG_Size, sizeof(struct XYMessage),
          ASOMSG_ReplyPort, xyreplyport,
          TAG_END);
 
        if (xymsg != NULL)
        {
            xymsg->xy_X = 10;   /* our special message information. */
            xymsg->xy_Y = 20;
 
            IDOS->Printf("Sending to port1: x = %d y = %d\n", xymsg->xy_X, xymsg->xy_Y);
 
                                                                   /* port2 will simply try to put  */
            if (SafePutToPort((struct Message *)xymsg, "xyport"))  /* one message to port1 wait for */
            {                                                      /*  the reply, and then exit     */
                IExec->WaitPort(xyreplyport);
                if (reply = (struct XYMessage *)IExec->GetMsg(xyreplyport))
                    IDOS->Printf("Reply contains: x = %d y = %d\n",   /* We don't ReplyMsg since   */
                            xymsg->xy_X, xymsg->xy_Y);                /* WE initiated the message. */
 
                      /* Since we only use this private port for receiving replies, and we sent     */
                      /* only one and got one reply there is no need to cleanup. For a public port, */
                      /* or if you pass a pointer to the port to another process, it is a very good */
                      /* habit to always handle all messages at the port before you delete it.      */
            }
            else IDOS->Printf("Can't find 'xyport'; start port1 in a separate shell\n");
            IExec->FreeSysObject(ASOT_MESSAGE, xymsg);
        }
        else IDOS->Printf("Couldn't get memory\n");
        IExec->FreeSysObject(ASOT_PORT, xyreplyport);
    }
    else IDOS->Printf("Couldn't create xyreplyport\n");
    return 0;
}
 
BOOL SafePutToPort(struct Message *message, CONST_STRPTR portname)
{
    IExec->Forbid();
    struct MsgPort *port = IExec->FindPort(portname);
    if (port != NULL) IExec->PutMsg(port, message);
    IExec->Permit();
    return(port ? TRUE : FALSE); /* FALSE if the port was not found */
 
         /* Once we've done a Permit(), the port might go away and leave us with an invalid port    */
}        /* address. So we return just a BOOL to indicate whether the message has been sent or not. */

Function Reference

The following chart gives a brief description of the Exec functions that control inter-task communication with messages and ports. See the SDK for details about each call.

Function Description
AddPort() Add a public message port to the system list.
AllocSysObject(ASOT_PORT) Allocate and initialize a new message port.
FindPort() Find a public message port in the system list.
FreeSysObject(ASOT_PORT) Free a message port.
GetMsg() Get next message from the message port.
PutMsg() Put a message to a message port.
RemPort() Remove a message port from the system list.
ReplyMsg() Reply to a message on its reply port.