home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Frozen Fish 1: Amiga
/
FrozenFish-Apr94.iso
/
bbs
/
alib
/
d1xx
/
d168
/
dillonstuff.lha
/
doc
/
dres
/
ipc.doc
next >
Wrap
Text File
|
1988-11-22
|
25KB
|
563 lines
IPC.DOC (DRES.LIBRARY)
Matthew Dillon
25 September 1988
Special note. I've tried my best, but this document is somewhat
lacking in the exact mechanics and implementation of IPC. Please
refer to the source module to resolve such questions.
The IPC section of the DRES.LIBRARY pertains to the sending of commands
and retrieval of results between arbitrary processes without conflict.
Processes which use IPC come in three flavors:
(1) A process which is able to accept and process IPC commands
(2) A process which might send IPC commands to other processes
(3) A process which does both.
The IPC mechanism implemented by this library is inherently asychronous
in nature (messages and ports), but also provides a synchronous call
specially tailored for cases (2) and (3). Doing IPC commands synchronously
has certain advantages, the greatest of which is that the implementation by
application software is greatly simplified. The synchronous call gets
around possible deadlocks in case (3) by interrupting itself and calling a
handler if incomming messages occur while it is waiting for the sent
message to complete.
The greatest feature of this IPC mechanism is a great flexibility in
who allocates and deallocates the message buffers. Both the source and
destination processes have a wide range of options concerning message
buffers. Unless otherwise specified, an allocated copy of the message
buffer is automatically made and used which allows the send or reply message
buffer used by the process to be immediately discarded. Additionaly, a
destination IPCPort can be flagged to force incomming messages to be
allocated no matter what the sender requests.
NOTE: The term 'allocation of messages' inherently means that if the
message is allocated, the user of the message can munge it however he likes
(exception: IF_GLOBAL messages that are propogated through all IPC ports).
Allocated messages are automatically FreeMem()d by the IPC subsystem or
by the receiver. Also, WHEN DUPLICATION OF MESSAGES OCCUR, THE ALLOCATED
MEMORY IS OF THE SAME TYPE AS THE ORIGINAL BUFFER.
NOTE: Do not get confused by the many different ways messages buffers
can be allocated. A message buffer will never be allocated-duplicate more
than once. For example, if the source specifies that it is passing an
allocated buffer, the further duplication is done by the IPC subsystem.
-The sender can allocate the message buffer himself and pass that,
pass a temporary buffer which the IPC subsystem duplicates, or pass
a static buffer to the destination without the overhead of allocation
and duplication.
-The receiver of a particular message can determine whether that message
was allocated and FreeMem() it himself, can allow the IPC subsystem
to free allocated messages, and can even force all incomming messages
on his IPC port to be allocated no matter what the sender specifies.
Forcing the IPC subsystem to allocate otherwise static messages is
a very useful ability to have. For instance, the parser employed by
the receiver of a message might destroy or modify the message buffer
during parser, while the original sender of the message might have
used a static string or otherwise expected the buffer not to be
screwed up. By the receiver being able to specify that he WANTS the
buffer to be an allocated duplicate because he intends to modify it
without permission is a good thing. Even though the sender passes
a static buffer to the IPC subsystem, that subsystem will allocate
and duplicate it for the receiver.
-In his reply to the sender, the receiver has the same sort of control
as sender in terms of how the reply message buffer is allocated. After
getting the reply back, the original sender can determine whether the
reply buffer was allocated and FreeMem() it himself, or allow the IPC
subsystem to free it when he is through with the reply.
-Finally, the receiver of the original message can even free his own
reply buffer when the original sender is through with it!
So in general, one can use either the IPC subsystem's automatic buffer
copying feature (the default), use no buffer copying at all, or use one's
own scheme.
MOST OF THESE ALLOCATION FEATURES ARE IMPLEMENTED THROUGH FLAGS. READ
THE DOCS BELOW ON OPENIPC() and SENDIPC().
APPLICATION NAME DOMAINS
An application for cases (1) or (3) must create a receiver port for
IPC messages with the OpenIPC() call. OpenIPC() takes a name and flags
argument. The Flags argument is discussed in OpenIPC() below. You do
not need a port to send messages, only to receive them.
The name of the port determines the type of messages it will receive.
The format is: <application_name>.<domain> , for example: "dmouse.CMD"
Currently, only the .CMD domain has a defined standard. It does not matter
whether multiple copies of the application are running, they can share the
same port name. You could have, say, two invocations of your favorite
editor each with five active projects. Support for this is discussed in
the ReplyIPC() call below.
While this port exists, other tasks may send IPC messages to it. You
must extract the messages (GetMsg()), process it, and then ReplyIPC() the
message to return it to the original sender.
The CloseIPC() call will shutdown an IPC port that you had previously
openning and return any pending messages by trying to pass them to the
next application of the same name (if none, an error is eventually returned
to the sender).
.CMD ports
Messages sent over .CMD ports are formatted as null-terminated
ascii-text. The send buffer (msg->TBuf) is formatted as follows:
project\0command\0 (\0 = NUL = ascii 0)
The \0 MUST be included as part of the buffer and buffer length. The
project name allows the application to identify which of its possibly
multiple projects is to be affected by the command. a NULL project name
(\0command\0) is allowed and refers to the 'active' project in the
application. The project name is ignored by applications which do not
support the notion of projects. The format of the command depends on the
application, but must be in ascii and terminate with a \0 (ascii 0).
The logical extension for the specification of multiple project names
is to allow comma delimited names and wildcards (* and ?). For example,
"*\0command\0" would refer to all projects, "a.c,b.c\0command\0" would
refer to two specific projects. This might not be supported by all
applications.
The format of the reply buffer depends on the success of the operation.
If the command was successful, the reply buffer will either be NULL or
contain some ascii/text response ending in \0. If the command was not
successful, IF_ERROR will be set in msg->RFlags and the reply buffer will
be either NULL or contain some ascii/text error message ending in \0.
The msg->Error integer error code will be non-zero if IF_ERROR was set,
possibly non-zero if IF_ERROR was not set to indicate a warning (IF_ERROR
always means Total-Failure). If msg->Error is not set by the application
which returns the error condition, it will be set by the IPC subsystem.
ParseCmd ParseCmd
argc = ParseCmd(buf, &argv, varget, varfree, &error, NULL);
int argc;
char **argv; (note, address passed)
char *((*varget)());
void (*varfree)();
long error; (note, address passed)
(note: Last NULL argument (0L) is required for future compatibility).
THIS COMMAND CAN EASILY BE USED WITH NON-IPC RELATED ROUTINES!
Do DME-type parsing on a string buffer. The buffer is terminated with
a \0 but may contain a \n at the end (which is ignored) to be compatible
with fgets() as well as gets(). NOTE that if the string is terminated
with a \<LF><NUL> i.e. "\\n\0", the backslash will override the standard
ignorance of the \n and parse it into the result.
DME-type parsing delimites fields by spaces except those enclosed in
matching parenthesis. Multiple levels of parenthesis is tracked and
the OUTERMOST SET REMOVED. Other special characters include shift-6
(^), backslash (\), and dollar ($):
form examples function
^<char> ^c ^d ^L embed a control character, case is ignored
you may not embed ^@ (0)
\<char> \\ \( \^ override the meaning of the next character,
allows embedding special characters.
$(varname) inline string variable, var name can be
anything except ')'.
$varname inline string variable, var name
restricted to alphanumeric and '_'.
Example: "echo (this (is a) test) $file/x a mess ^g \(hi! \)ug\\"
Result: argv[0] = "echo"
argv[1] = "this (is a) test"
argv[2] = "dnet:foo/x" (assumes $file == 'dnet:foo')
argv[3] = "a"
argv[4] = "mess"
argv[5] = "<CONTROL-G>" (i.e. ascii code 7)
argv[6] = "(hi!"
argv[7] = ")ug\"
argv[8] = NULL
Note: offset -1 of each argument contains a status byte. Currently,
bit 0 is defined to indicate whether the argument was paren'd or not.
This cannot otherwise be told as the highest level of parenthesis are
stripped. (argv[0][-1] & 1) == 0, (argv[1][-1] & 1) == 1.
--------
buf -\0 or \n\0 terminated buffer holding the command to parse.
The buffer is not modified and may be discarded after the
call returns.
&argv -address of a variable which will be initialized to point
to an allocated array of pointers to strings exactly the
way the main() argv works.
varget -Pointer to a subroutine which when given a \0 terminated
variable name will return a \0 terminated string or NULL
if that variable does not exist.
varget(varname)
char *varname;
This argument may be NULL indicating string variables are
not supported. The routine may return non-allocated
strings (i.e. static storage or string constants) in which
case there is no need for a varfree() function.
varfree -Pointer to a subroutine which when given the string
pointer returned by varget() will free it. This call
is made immediately after varget() is called and returns
nothing. You may pass NULL for this argument if no free
function exists (varget was NULL or returned static
strings).
&error -You must supply a pointer to a longword which will be
returned holding the error, if any, and the index where
the error occured, if any. The index is stored in the
lower 16 bits and the error # stored in the upper 16 bits:
1 << 16 = NO MEMORY
2 << 16 = STRING VARIABLE NOT FOUND (varget() returned
NULL), the lower 16 bits contain the index into
buf.
NULL -This argument is reserved for future expansion and must
be NULL (0L).
NOTE: The returned arg list may be munged however you wish, including
the pointers themselves, just as long as you pass the original
argv pointer (i.e. the entries may be munged) to FreeParseCmd().
0 is always returned on error, and error will be set properly.
Otherwise, argc is returned and error will hold 0.
The original buffer is not modified in any way.
NOTE: Since the outer set of parenthesis () are stripped, offset -1
of each arguments provides information as to whether the
argument was quoted or not. If bit 0 is set, the argument
was quoted, I.E.: (a) b c
(av[0][-1] & 1) = 1 av[0] = "a"
(av[1][-1] & 1) = 0 av[1] = "b"
(av[2][-1] & 1) = 0 av[2] = "c"
FreeParseCmd FreeParseCmd
(void) FreeParseCmd(argv)
This routine frees the storage referenced by the argv... the argv array
and the original strings are all freed. It is ok if Individual entries
in the argv array are munged.
OpenIPC OpenIPC
port = OpenIPC(name, flags)
PORT *port;
char *name;
long flags;
Create an IPC port of the specified name suitable for receiving IPC
commands on. If the port could not be created due to lack of memory,
NULL is returned, else an EXEC port is returned. The port is setup
as PA_SIGNAL and takes up a signal bit in your signal set. If the
application does not wish a signal-port, it may FreeSignal() the
signal and change the flags to something other than PA_SIGNAL.
Due to the fact that the signal was allocated for the calling task,
the port is normally usable only from the calling task. One can
change the ownership and port characterstics but always remember that
CloseIPC() can only be called from the owning task.
IF_ALLOC If IF_ALLOC is specified, *ALL* incomming message buffers
will be forced to be allocated even if the sender of the
message specifies IF_NOCOPY. I.E. you, as the receiver of
IPC messages on this port can specify this flag to guarentee
the msg->TBuf is allocated and thus modify the contents
of the buffer itself without confusing the original sender
(who may have specified a static string)
Note: However, if this message is to be passed on via
IF_GLOBAL or IF_NOTFND, you probably do not want to munge
the buffer.
IF_GLOBAL If IF_GLOBAL is specified, this port will accept global
messages. Global messages are always of the .CMD form
though the sender could easily put stuff beyond the second
\0 in the message buffer destined for specialized routines.
If not specified, IF_GLOBAL messages will NEVER propogate
through this port.
CloseIPC CloseIPC
(void) CloseIPC(port)
PORT *port;
Delete an IPC port previously obtained by OpenIPC(). The owning task
must make this call since it will FreeSignal() the allocated signal.
Note that if the port flags are set to something other than PA_SIGNAL,
no attempt will be made to free the possibly non-existant signal bit.
This call will pass any pending messages on to the next application
of the same name, and if no other exists, will return the messages
with an error. Pending messages with IF_GLOBAL set continue their
journey through the IPC ports flagged to accept IF_GLOBAL messages.
SendIPC SendIPC
msg = SendIPC(appname, buf, len, flags)
IPCMSG *msg;
char *appname;
APTR buf;
long len, flags;
This routine starts an asynchronous IPC request. It allocates and
initializes an IPCMSG and reply port and sends the message to the
destination application (appname). The IPCMSG is returned and it's
msg->RFlags holds the IF_ALLOCMSG internal flag to cause the IPCMSG
and replyport to be deallocated when you are through with the reply.
NOTE! The returned IPCMSG has already been sent to the destination
and is not owned by you. The idea is to then CheckMsg() and/or
WaitMsg() on it. The ANode filed of the IPCMSG, however, *IS* owned
by you and you may use it however you wish.
The buffer and buffer length you pass SendIPC() is used to allocate
a copy of the buffer (which is deallocated automatically when the
destination ReplyIPC()s). This means that you can discard/reuse your
buffer as soon as the call returns. However, if you specify the
IF_NOCOPY flag SendIPC() will use your buffer pointer and you must
be sure not to modify its contents until the message is returned to you.
If you specify IF_ALLOC it is assumed your buffer pointer and length
were AllocMem()d and the system will FreeMem() them when the destination
is through processing your message and calls ReplyIPC().
** See SendIPC2() for info on how to handle the message when it comes
back.
* If the 'name' is NULL, the first application in the application
list is called. This is usually used in conjunction with IF_GLOBAL
to indicate that the message should propogate to all IPC ports in the
system.
SendIPC2 SendIPC2
msg = SendIPC2(name, msg)
This routine is called by SendIPC(). This assumes you already have an
IPC message structure that you wish to use. The msg argument is
returned. The reply fields, including msg->Error and msg->Confirm,
are zerod.
The IPCMSG must be initialized as follows:
msg->Msg.mn_ReplyPort -must hold reply port for the message
msg->Msg.mn_Length -must hold length of IPCMSG structure ONLY
if you had specified the IF_ALLOCMSG in
msg->TFlags
msg->Msg.TBuf -holds the buffer pointer to the message
or NULL
msg->Msg.TLen -holds the length of the message
msg->Msg.TFlags -holds flags associated with the message:
IF_ALLOC * automatic deallocation on reply. This flag
is set automatically if you do not specify
IF_NOCOPY. If NEITHER IF_NOCOPY or IF_ALLOC
are specified, the IPC subsystem will
allocate a duplicate of your buffer, place
it in TBuf, and set this flag for you.
IF_GLOBAL * force propogate to all applications in
the system before returning to the sender.
IF_NOCOPY * force IPC system to use the buffer you
placed in TBuf. Otherwise, the IPC system
will allocate and copy your buffer into
its own and place that in TBuf.
IF_ALLOCMSG * IPC system will deallocate the IPCMSG
itself when FreeIPC() is called. This
flag is set automatically when the IPC
system has allocate the IPCMSG in the
first place.
WARNING! Upon return, msg->TBuf will not necessarily point to your
buffer, even if you specified IF_NOCOPY or IF_ALLOC.
Handling the message when it comes back
After you make the call, you must wait for the message to complete
via WaitMsg and/or CheckMsg(). Note that WaitMsg() will remove the
message from its reply port. If you do not use WaitMsg(), you must
remember to remove the message before continuing.
Then, process the reply (msg->RBuf, msg->RLen, and msg->RFlags).
The msg->RBuf IS ALLOWED TO BE NULL. msg->RFlags will indicate
any errors:
IF_ERROR|IF_NOTFND|IF_NOAPP: no application of that name exists
IF_ERROR|IF_NOTFND : application exists, but the requested
project does not exist. OR the
command does not exist for this
application.
IF_ERROR : The application exists and can handle
the project & command, but an error
occured during interpretation of the
command.
IF_ALLOC : The reply buffer was allocated. You
have the option of FreeMem()ing it
yourself or allowing the system to
do so. If you do it, you must set
RBuf = NULL.
NOTE: msg->Error does not need to be set by the receiver. If
msg->Error is 0 and IF_ERROR is set, msg->Error will be set to
20 by the IPC subsystem.
NOTE: In the case of IF_ERROR, the reply buffer might still be
non-NULL and contain an error message.
When through dealing with the reply, you MUST call FreeIPC(msg). This
routine will deallocate the reply buffer if msg->RFlags had IF_ALLOC
set by the destination and also free the message it self if msg->TFlags
contained IF_ALLOCMSG set by SendIPC() or the user before a SendIPC2().
Additionaly, if msg->Confirm was set by the destination, this function
vector will be called with the message pointer as an argument to allow
the destination to deallocate its reply. NOTE!!! If the message has
IF_GLOBAL set in msg->TFlags, msg->Confirm should NOT be touched by
receiving applications.
DoIPC2 DoIPC2
(void) DoIPC2(name, msg, collfunc, collport)
long flags
char *name;
IPCMSG *msg;
void (*collfunc)();
PORT *collport;
This is the hall mark of the IPC library and allows one to send
SEMI-SYNCHRONOUS messages while still retaining the ability to
process incomming messages on your own IPC port (case #3). The IPC
message msg must be setup as in SendIPC2(). The collision function
and collision port may be NULL if you wish.
This command sends the message and then waits for the reply. If while
waiting for the reply a message comes in on the collision port (usually
your IPC port), the collision vector will be called with the collision
port as an argument, allowing you to process incomming messages while
waiting for one you sent. By properly designing your software, you
can allow multiply stacked DoIPC2() calls.
When the message finally comes back, this call returns. The application
must still deal with its reply buffer and call IPCFree() when through.
NOTE: The collision function is called as a subroutine of this routine
and thus if it is a general command-handling routine remember that
your command interpreter might have to be synchronously reentrant.
DoIPC2() is certainly reentrant. A4 and A5 are not screwed up by
DoIPC2() so you do not have to reload your data segment base register
from the collision vector routine if using the small data model.
(Compatible with both Aztec and Lattice C).
ReplyIPC ReplyIPC
(void) ReplyIPC(msg, buf, len, flags)
IPCMSG *msg;
APTR buf; (may be NULL & len would then be 0)
long len;
long flags;
You must reply to all IPC messages you receive on your IPC port by
calling ReplyIPC(). ReplyIPC() works almost exactly like IPCSend(),
but deals with the RBuf, RFlags, and RLen fields of the IPC structure.
Normally, the IPC system will allocate a duplicate of your reply buffer
and stick that in msg->RBuf which allows you to throw away your buffer
after calling ReplyIPC(). Various flags override this feature:
IF_ALLOC - The buffer is automatically deallocated when the
source calls FreeIPC(). If this flag exists the
IPC system will not allocate a duplicate buffer.
IF_NOCOPY - The IPC system will not allocate a duplicate buffer.
Normally, (1) neither of these flags is specified, or (2) IF_ALLOC
is specified. Specifying IF_ALLOC alone means that the IPC system
assumes you have AllocMem()d the buffer yourself and want it
automatically freed when the source (original sender) calls
FreeIPC().
Specifying both IF_ALLOC and IF_NOCOPY is equivalent to specifying
just IF_ALLOC. Specifying just IF_NOCOPY means that the IPC system
will use your buffer pointer and will NOT attempt to deallocate it.
In this case, you are responsible for keeping the buffer intact
until the source calls FreeIPC(). This is possible through the
use of the msg->Confirm vector. ** you cannot use msg->Confirm
for IF_GLOBAL packets! Because there is only one vector entry and
possibly multiple users of the message.
NOTE: The buffer pointer placed in msg->RBuf will either be yours
or the allocated duplicate. msg->RBuf will be set to NULL when (if)
the system deallocates it.
If you specify IF_NOTFND, your buffer and length are completely
ignored and the message is passed on to the next application program
with the same name. IF THE ORIGINATOR SPECIFIED IF_GLOBAL WHEN HE
SENT THE MESSAGE, AND YOU DID NOT SPECIFY IF_NOTFND, the message
will be passed on to the next application anyway and your reply will be
passed on along with it. In the same way, if you were not the first
application to get this message (it was passed to you), the
msg->RBuf/RLen/RFlags fields may already have something in them. If
your reply buffer pointer is not the same address as the one already in
msg->RBuf and msg->RFlags held IF_ALLOC, the old buffer in msg->RBuf
will be deallocated before processing continues with your ReplyIPC().
** Warning, msg->RBuf will be replaced in the same manner msg->TBuf
is by an allocated duplicate unless you specify otherwise.
FreeIPC FreeIPC
(void) FreeIPC(msg)
IPCMSG *msg;
This routine must be called when the original sender of an IPC message
has sent the message, waited for the reply, removed the message from
the reply port, and processed the reply buffer. This serves to free
the message and any associated buffers as specified below:
If msg->RFlags has IF_ALLOC set the reply buffer is immediately
deallocated and msg->RBuf set to NULL.
If msg->Confirm is non-NULL the vector will be called with the IPCMSG
as an argument.
If msg->TFlags holds IF_ALLOCMSG, the msg will be FreeMem()d with the
length msg->Msg.mn_Length. IF_ALLOCMSG is not usually set by the
user... it is set by SendIPC() which allocates a message/replyport
and wants both to be deallocated on completion.