KitFreeMiNT
Interprocess communication
MiNT provides many forms of interprocess communication (IPC): signals, FIFOs, shared memory, message passing, and semaphores.
- III.1. Signals
- III.2. FIFOs
- III.3. Shared memory
- III.4. Rendezvous
- III.5. Semaphores
III.1. Signals
MiNT introduces the new (to TOS) concept of a signal. If you're familiar with Unix/Posix signals, then MiNT signals will will be easy to learn; but note that there are some (not so subtle) differences between MiNT and Unix! A signal is a small non-negative integer that represents an exceptional event; usually something that is very urgent. It's somewhat like an interrupt or exception to the CPU, only it's implemented in the operating system instead of in the hardware. Like many exceptions (bus errors, etc.) signals are usually fatal. Most signals can be caught by programs (so that a program-defined routine is called) or ignored; if a signal is caught or ignored, it is no longer fatal. Signals can also be blocked; a blocked signal is not acted upon until it is unblocked.
A signal is said to be sent to a process when the exceptional condition related to that signal occurs, or when another process sends the signal with the Pkill() system call. The signal is said to be delivered to the process when that process wakes up and begins to take whatever actions are appropriate for the signal.
Note that there may be a considerable time interval between the sending of a signal and its delivery. For example, if process A has blocked the SIGHUP signal (signal 1), then no SIGHUP will be delivered to it until it has unblocked that signal, even if process B sends it SIGHUP with the Pkill() system call. Note also that a signal is not necessarily delivered the same number of times that it is sent. If both process B and process C send SIGHUP to process A, when process A unblocks SIGHUP only one SIGHUP will be delivered.
This is because signals are like flags; once a flag has been set (the signal is sent) setting the flag again will have no effect until it has been cleared (the signal is delivered).
III.1.1. What signals are there?
There are 32 possible signals, 0-31. Not all of these have
been assigned a meaning under MiNT. Here are the ones that
have been given a meaning; we give the symbolic name for the
signal, the corresponding integer, and the traditional
meaning for the process that the signal is sent to. Any
signal not listed here should be considered as reserved and
should not be used by applications. Unless otherwise noted,
the default action for signals is to terminate the process.
#define SIGNULL 0 /* No default action */
This isn't really a signal at all; it is never delivered to processes and has no effect. It exists only so that processes can test to see if a particular child process has exited, by attempting to send SIGNULL to the child. If the child exists, the attempt will succeed but nothing will be done. If the child has terminated, the caller will get an error. It is not possible to catch or block this signal, since it is never sent to processes anyway.
#define SIGHUP 1 The terminal that you're connected to is no longer valid.
This signal is commonly sent by, for example, window managers when the user has closed the window. Processes should not attempt any I/O to their controlling terminal after receiving this signal, and indeed should probably exit unless the user has specifically asked them to continue.
#define SIGINT 2 Please stop what you're doing.
This signal is sent when the user presses control-C. It usually means that the user wishes the process to stop its current task. Non-interactive processes should generally exit when they receive this signal; interactive processes may wish to catch SIGINT so that the user can use it to break out of time-consuming tasks and return to a command prompt.
#define SIGQUIT 3 Stop what you're doing, something's gone wrong!
This signal
is sent when the user presses control-. It usually indicates
a desire to immediately abort the process because of an error
that the user has noticed. It is generally thought to be
stronger than SIGINT, and exiting (perhaps after cleaning
up data structures) is an appropriate response to this.
#define SIGILL 4 An illegal instruction has been encountered.
This corresponds to the 680x0 illegal instruction trap, and usually indicates a very serious error; catching this signal is generally unwise.
#define SIGTRAP 5 The single-step trace trap has been encountered.
This corresponds to the 680x0 trace trap, and is usually activated (and handled) by debuggers; user programs shouldn't catch this.
#define SIGABRT 6 An awful error has occured.
This is commonly sent by the abort library function, and indicates that something has gone very, very wrong (for example, data structures have been unexpectedly corrupted). It is unlikely that anything useful can be done after this signal is sent; programs should not normally catch or ignore SIGABRT.
#define SIGPRIV 7 Privilege violation.
An attempt has been made to execute an instruction in user mode that is normally restricted to supervisor mode; this corresponds to the 680x0 privilege violation exception, and indicates a serious error.
#define SIGFPE 8 Division by zero or a floating point error has occured.
Processes may ignore or catch this signal (which corresponds to the 680x0 division by zero trap) and deal with it as they see fit.
#define SIGKILL 9 / Cannot be blocked or caught / Die!
This signal will never be seen by a process; nor can processes block it. Sending SIGKILL is a way to be sure of killing a run-away process. Use it only as a last resort, since it gives the process no chance to clean up and exit gracefully.
#define SIGBUS 10 Bus error.
Corresponds to the 680x0 bus error exception, and indicates a very serious error; programs should generally not attempt to ignore or catch this signal.
#define SIGSEGV 11 Illegal memory reference.
Corresponds to the 680x0 address error exception, and indicates a very serious error; catching or ignoring this signal is not recommended.
#define SIGSYS 12 Bad argument to a system call.
This signal is sent when an
illegal (usually, out of range) parameter is sent to a system
call, and when that system call does not have any nice way
to report errors. For example, Super(0L) when the system is
already in supervisor mode causes SIGSYS to be raised. Note
that the kernel does not always detect illegal/out of range
arguments to system calls, only sometimes.
#define SIGPIPE 13 A pipe you were writing to has no readers.
Programs may catch this signal and attempt to exit gracefully after receiving it; note that exiting is appropriate because the standard output is probably no longer connected to anything.
#define SIGALRM 14 The alarm you set earlier has happened.
This signal is sent to processes when the alarm clock set by Talarm (q.v.) expires. It's very common to catch this signal and from the signal handler jump to a known point in the program; for example, to indicate a timeout while attempting to communicate over a serial line.
#define SIGTERM 15 Please die.
This is a polite form of SIGKILL (#9). Programs should respect this nice request; they may want to catch the signal to perform some cleanup actions, but they should then exit (since if they don't, the user will probably get mad and send SIGKILL later anyway...). This is the signal that is sent when a process is dragged to the trashcan on the desktop.
#define SIGSTOP 17 / Default action: suspend the process / Suspend yourself.
This signal is sent to a process when it should be stopped temporarily. SIGSTOP is used primarily by debuggers and similar programs; suspensions requested directly by the user usually are signalled by SIGTSTP (q.v.) This signal cannot be ignored, blocked, or caught.
#define SIGTSTP 18 / Default action: suspend the process / The user is asking you to suspend yourself.
This signal is sent immediately when the user presses the control-Z key, and is sent when the process tries to read a control-Y key in cooked mode (for a delayed stop). In both cases, the process should suspend itself. Since this is the default action, no special handling is normally required, although some programs may wish to save the screen state and restore it when they are unsuspended.
#define SIGCONT 19 / Default action: continue a stopped process / You are being restarted after having been suspended.
This signal is sent by shells to resume a suspended process. If the process is not suspended, the signal does nothing. This signal cannot be blocked, but it is possible to install a handler for it (this is rarely necessary, though).
#define SIGCHLD 20 / Default action: no action taken / One of your children has been suspended or has exited.
This signal is sent by the kernel to the parent of any process that is terminated (either because of a signal or by a Pterm(), Pterm0(), or Ptermres() system call) or which is suspended because of a signal. Programs that are concerned with the status of their children (for example, shells) may wish to catch this signal; after a SIGCHLD has been received, the Pwait3() system call may be used to determine exactly which child has exited or been suspended. Note that the Psigaction() system call may be used to force SIGCHLD to be delivered only when a child process terminates (so that suspension of child processes, for example via job control, does not raise SIGCHLD.
#define SIGTTIN 21 / Default action: suspend the process / Attempt to read from a terminal you don't own.
This signal is sent to any process that attempts to do input from a terminal with a different process group than their own. Usually, this happens if the user has started the job in the background; the process will be suspended until the user explicitly brings it to the foreground with the appropriate shell command (at which time the shell will reset the terminal's process group and send the stopped process a SIGCONT signal to tell it to continue).
NOTE: in fact, SIGTTIN and SIGTTOU are sent to all processes in the same process group as the process that attempted to do the i/o; this is for compatibility with Unix, and it simplifies the implementation of job control shells.
#define SIGTTOU 22 / Default action: suspend the process / Attempt to write to a terminal that you don't own.
Similar to SIGTTIN (q.v.). Processes should normally respect the user's job control and should override or ignore SIGTTOU only in situations where a very critical error has occured and a message must be printed immediately.
#define SIGXCPU 24 Your CPU time limit has been exhausted.
Sent to processes when they have consumed more than the maximum number of milliseconds of CPU time allowed by the Psetlimit() system call. The signal will continue to be sent to the process until it exits; if a process does catch this signal, it should do whatever clean up actions are necessary and then terminate.
#define SIGWINCH 28 / Default action: no action taken / The window you were running in has changed size.
This signal is sent to processes by some window managers to indicate that the user has changed the size of the window the process is running in. If the process cares about the window size, it may catch this signal and use an Fcntl() call to inquire about the new window size when the signal is received. See the documentation for Fcntl() for details.
#define SIGUSR1 29
#define SIGUSR2 30
These two signals are reserved for applications, which may define whatever meaning they wish for them. Note, however, that these signals do terminate processes by default, so don't send them to a process which isn't prepared to deal with them.
III.2. FIFOs
FIFOs are first in first out message queues. Pipes are a
special kind of (unidirectional) FIFO. FIFOs are represented
by files in the subdirectory pipe on drive u:. They are
created with the Fcreate(name, flags) system call. name will
be the name under which the FIFO is known (maximum 13
characters); flags is explained below.
The returned file handle is treated just like an ordinary
file, and may be written to and read from (unless the FIFO is
unidirectional, in which case it may only be written to). The
program that creates the FIFO is normally called the
server. Other programs (clients) may use the Fopen(name,
mode) system call to open the other end of the FIFO and read
the data that the server writes, or write data for the server
to read. When the last program (either client or server)
using a FIFO closes it, the FIFO is deleted automatically.
Note that one program can be both client and server, if it
creates a FIFO with Fcreate() and then opens it again with
Fopen(). Also, children of the server can inherit the
Fcreate'd file handle and thus have access to the server
side of the FIFO.
The bits in the flags argument to Fcreate have the following meanings:
0x01
Make the FIFO unidirectional (server can write, clients can read).
0x02
Cause reads to return EOF if no other processes are writing, and writes to raise the SIGPIPE signal if no other processes are reading. The default action (if this flag is not given) is to block waiting for reads and writes.
0x04
Make the FIFO a pseudo-tty; to client processes, the FIFO
will act just like a terminal with the server typing the
characters; for example, if the server writes a control-C,
SIGINT will be sent to clients. Data can be passed through
such a FIFO in long words rather than bytes, if the
Fputchar() system call is used. This allows the server
program to pass the extended BIOS information (such as the
shift key status and scan code) to be returned by Bconin()
calls on the client side of the FIFO. The extra 3 bytes of
the longword could also be used for other out of band data.
0x20
Make the FIFO support Unix style read semantics; i.e.
Fread() will return as soon as any bytes are available on the
FIFO, and will return the number of bytes read. If this flag
is clear, then Fread() will not return until the number of
bytes specified in the Fread() call have been read, or until
there are no more writers on the FIFO.
Attempting to Fcreate() a FIFO with the same name as an already existing one will result in an access error (i.e. the Fcreate() will fail). Pipes may be created through the Fpipe() system call as well as through the Fcreate()/Fopen() pair; the former method is easier, since the kernel takes care of name conflicts, etc. Pipes created in this way will be unidirectional, and will have Unix read semantics. FIFOs may be locked by processes via the Fcntl() system call, as follows:
/* values for the l_type field */
#define F_RDLCK 0
#define F_WRLCK 1
#define F_UNLCK 3
struct flock {
short l_type; /* type of lock */
short l_whence; /* what is the lock relative to? */
long l_start; /* start of locked region */
long l_len; /* 0 for `rest of file' */
short l_pid; /* set by F_GETLK */
};
The l_whence field takes a value like the whence argument of
Fseek(): zero means from the beginning, one means from the current position, and two means from the end. The l_start
field is added to the appropriate offset in the file and the
lock starts there.
Fcntl(fd, &lock, F_SETLK)
Set a lock as specified by the lock structure. The current version of MiNT only understands locks on the whole FIFO, so lock.l_start and lock.l_len should both be 0. If lock.l_type is F_UNLCK, then the lock is released. Otherwise, the whole file is locked. Future versions of MiNT may distinguish between read and write locks, but for now all locks are treated as write locks (F_WRLCK) and block both reads and writes. If another process has locked the fifo, the Fcntl() call returns EACCDN (-36). If a process holding a lock terminates, the FIFO is automatically unlocked.
Fcntl(fd, &lock, F_GETLK)
If a lock exists on the FIFO, set lock to indicate what kind of lock it is; otherwise, set lock.l_type to F_UNLCK.
Locks are only advisory; that is, a lock on a file prevents
obtaining another lock, but does not actually prevent reads
and writes. Thus, programs may ignore locks if they choose to
do so. However, if all programs that use a FIFO also use
locks, two clients' data will not be not mixed together in
that FIFO.
III.2.1. Using FIFOs
FIFOs are actually very easy to use. Here are some things to keep in mind:
- (1) The server program (the one that's going to be listening for
requests from clients) should create the FIFO with Fcreate(). The
file descriptor returned from Fcreate() is the
serverdescriptor; descriptors returned by Fopen() will beclientdescriptors. Data written to the server descriptor can be read by client descriptors, and vice-versa. - (2) FIFOs are by default bidirectional. You can create a single
directional FIFO by creating the FIFO
read only; in this case, only the server descriptor can be written to, and only client descriptors can be read from. - (3) Be careful not to mix data up; if two clients are trying to read
from the same FIFO at the same time, they each may read data intended
for the other. The easiest way to avoid this is by having every
client lock the FIFO before accessing it, and unlock the FIFO when
finished. It's also possible (if you're careful) to use the fact that
all writes of <1024 bytes are atomic (i.e. take place in one
chunk) to avoid interleaving of data; but locks are probably safer.
Here is a sample pair of applications. The server program
(fortserv.c) creates a FIFO and listens on it for requests.
When it receives a request, it writes a cute saying (a
fortune cookie) back to the FIFO. The client program
(fortune.c) opens the FIFO, writes a request, reads the
response, and prints the result on the terminal. These are
very simple minded applications, but it should give you an
idea of the flavour of how to use FIFOs for interprocess
communication.
/* fortune server: send cookies to clients */
/* illustrates server side use of fifos */
/*
* This program opens a fifo ("u:\pipe\fortune")
* and listens to requests on that fifo. When it
* gets a request (consisting of a single '?'
* character) it writes back as a reply a 1 byte
* "length" followed by a randomly selected saying.
* BUGS:
* - maximum of 255 characters for a fortune
* - the fortunes aren't particularly interesting
*/
#ifdef __GNUC__
#include <minimal.h>
#endif
#include <osbind.h>
#include <mintbind.h>
#include <string.h>
#define FIFONAME "u:\\pipe\\fortune"
#define MAXSIZE 255
/* witty (?) sayings */
char * sayings[] = {
"Core fault -- program dumped.",
"Don't worry, be happy!",
"Help! I'm trapped in a fortune cookie factory!",
"\"Home is where you wear-a your hat.\"",
"I want a cookie.",
"MS-DOS: just say \"no\".",
"Never play leapfrog with a unicorn.",
"No matter where you go, there you are.",
"Sorry, I'm out of short, pithy sayings today.\nTry again later.",
"They say that playing NetHack is like walking into a death trap.",
"Vision hazy, try again later.",
"What? You expected something funny?",
"Why is it that UFO's always seem to visit idiots?",
"Your puny intellect is no match for our superior weapons.",
};
#define NUMSAYINGS (sizeof(sayings) / sizeof(char *))
/* file descriptor for the fortune fifo */
int fd;
/* send a witty saying out through the fifo */
void
send_saying()
{
int i;
char *s;
char tmpbuf[MAXSIZE+1];
/* pick a saying at random */
i = ((unsigned)Random() >> 1) % NUMSAYINGS;
s = sayings[i];
/* construct the message to send */
i = (int)strlen(s);
tmpbuf[0] = i;
strcpy(tmpbuf+1,s);
/* we really should check for an error */
(void)Fwrite(fd, (long)i+1, tmpbuf);
}
/* main function: create the fifo, then sit around
* listening for requests
*/
int
main(argc, argv, envp)
int argc;
char **argv, **envp;
{
char c;
long r;
fd = Fcreate(FIFONAME, 0);
if (fd < 0) {
Cconws("Couldn't create ");
Cconws(FIFONAME);
Cconws("!\r\n");
Pterm(1);
}
for(;;) {
r = Fread(fd, 1L, &c);
if (r != 1) { /* read error?? */
break;
}
if (c == '?') { /* request for saying */
send_saying();
}
/* could have other requests here */
}
return 0;
}
/* fortune client: get a fortune cookie from
* the fortune server
*/
/* illustrates client side use of fifos */
#ifdef __GNUC__
#include <minimal.h>
#endif
#include <osbind.h>
#include <mintbind.h>
#define FIFONAME "u:\\pipe\\fortune"
#define BUFSIZ 256
#define F_SETLKW 7
struct flock {
short l_type; /* type of lock */
#define F_RDLCK 0
#define F_WRLCK 1
#define F_UNLCK 3
short l_whence; /* SEEK_SET, SEEK_CUR,
SEEK_END */
long l_start; /* start of locked region */
long l_len; /* length of locked region */
short l_pid; /* pid of locking process
(F_GETLK only) */
};
int
main(argc, argv, envp)
int argc;
char **argv, **envp;
{
int fd;
char buf[BUFSIZ];
unsigned char len;
struct flock lock;
long r;
/* open the fifo */
fd = Fopen(FIFONAME, 2);
if (fd < 0) {
Cconws("Couldn't open ");
Cconws(FIFONAME);
Cconws("!\r\n");
Pterm(1);
}
/* get a lock; this makes sure that two fortune
* programs don't try to send requests and read
* replys at the same time
*/
lock.l_type = F_WRLCK;
/* lock the whole file, only thing that makes sense
* for a fifo
*/
lock.l_whence = 0;
lock.l_start = lock.l_len = 0L;
r = Fcntl(fd, &lock, F_SETLKW);
if (r != 0) {
Cconws("Couldn't get a lock!\r\n");
Pterm(r);
}
/* write the request */
Fwrite(fd, 1L, "?");
/* wait for a reply */
/* the fortune server writes a 1 byte length,
* followed by the fortune itself
*/
r = Fread(fd, 1L, &len);
if (r != 1L || len != Fread(fd, (long)len, buf)) {
Cconws("Error reading fortune!\r\n");
Pterm(1);
}
buf[len] = 0;
/* unlock the fifo */
lock.l_type = F_UNLCK;
(void) Fcntl(fd, &lock, F_SETLKW);
Fclose(fd);
/* now write the fortune to the screen */
Cconws(buf);
Cconws("\r\n");
return 0;
}
III.3. Shared memory
Children created with the Pexec(4,...) or with Pexec(104,...) share all of their parent's memory, as do children created with the Pvfork() system call. Hence, they may communicate with their parent (or with each other) via global variables. A more general shared memory mechanism is provided by u:\shm. Files in that directory represent blocks of memory. A program may offer to share its memory by creating a file in u:\shm and executing an Fcntl() call (SHMSETBLK) to associate a block of memory with the file. Other programs may then open the file and do a SHMGETBLK call to gain access to that memory.
To create a shared memory file, a program uses the Fcreate() call to create a file in u:\shm, e.g.:
fd = Fcreate('u:\\shm\\my.share', 0);
It then uses an Fcntl() call to attach a block of memory (previously allocated by Malloc() or Mxalloc()) to the file:
blk = Malloc(128L);
Fcntl(fd, blk, SHMSETBLK);
Several things should be noted when creating a shared memory file:
- (1) The file's attributes must be 0. Read-only shared memory, or shared memory with other attributes, is not yet implemented, but may be in the future.
- (2) Two shared memory files cannot have the same name. An attempt to create a new shared memory file with the same name as an existing one will fail with an access denied error (EACCDN).
- (3) Once the block of memory has been attached to the file, it may be accessed by any application that opens the file.
- (4) A shared memory file (and associated block) remain allocated even after the program which created it terminates. It can be deleted (and the associated memory freed) with an Fdelete() system call.
- (5) The size of the shared memory file will be the actual size of the memory block. This may be somewhat larger than the size requested in the Malloc() or Mxalloc() request, due to memory rounding.
To use a shared memory block, a client application must open the file and use the SHMGETBLK Fcntl() to gain access to it. For example:
fd = Fopen("u:\\shm\\my.share', 2);
blk = Fcntl(fd, 0L, SHMGETBLK);
Fclose(fd); /* optional -- see below */
Things to note:
- (1) The address of the shared memory block is returned by the Fcntl() call. NOTE THAT THIS ADDRESS MAY BE DIFFERENT FOR DIFFERENT PROGRAMS. That is, a shared memory block that appears at address 0x01000100 in one program may appear at address 0x0007f000 in another. In particular, shared memory blocks should not contain absolute addresses (e.g. pointers).
- (2) The extra argument passed to Fcntl() is reserved for future expansion; use 0L for now to ensure compatibility with future versions of MiNT.
- (3) The mode argument in the Fopen() function must be an accurate reflection of how the program plans to use the memory; read and write access permissions will be enforced in future versions of MiNT.
- (4) If no SHMSETBLK has been made for the file, a SHMGETBLK Fcntl() will return a NULL pointer to indicate an error.
- (5) If a program is finished with a shared memory block and no longer wishes to use it, it should call Mfree() with the address of the block (i.e. the address returned by Fcntl(fd, 0L, SHMGETBLK)).
III.3.1. Deleting a shared memory file
The Fdelete() system call may be used to delete a shared memory file. This will not necessarily free the associated memory; the memory will actually be freed only after (1) the file has been deleted, and (2) all processes using the memory have freed the memory, either directly or as a result of the process terminating.
Fdelete() will fail if the shared memory file is still open. Processes may omit the Fclose() call if they wish this to happen; it's a way of informing the process trying to delete the file that people are still interested in it. Note that it is not harmful to allow the Fdelete() to occur, since (as noted above) the memory will not actually be freed until everyone is finished with it; but sometimes it may be useful for programs to know that the memory is still in use.
III.4. Rendezvous
The Pmsg() system call provides a simple message based form of IPC. See the manual page for Pmsg() for further details.
III.5. Semaphores
Semaphores may be created and otherwise accessed via the Psemaphore() system call. They are a way to control exclusive access to a resource, with true blocking if desired. See Psemaphore()'s manual page for more details.