This chapter gives on overview of the most common local UNIX
interprocess communication facilities, including
This chapter describes these interprocess communication facilities
that are restricted to the use between processes that are executed
on one computer.
All ``local'' interprocess communication facilities rely on memory for
the exchange of information. Processes in the UNIX model can normally not
see/access the memory of another process (except for System V shared
memory regions, described later in this chapter).
Therefore the kernel (which has access
to all memory) has to copy the data first from the user address space
into some internal kernel buffers (caused by a ``write'' system call),
and from there to the address space of another process (caused by a ``read''
system call from that process). Figure 5 illustrates
this process.
Semaphores and other related IPC facilities do not copy information, but
modify memory that is controlled by the kernel.
How do processes get ``in touch'' with one another? Two basic approaches exist for this purpose:
The socket interface which provides distributed IPC facilities, but also IPC facilities that are only available between processes that run the same computer, is introduced in Chapter 6.1.
Most UNIX applications have to deal with signals. A good introduction in the terminology of signals is given in [Zlo91]:
[Signals] are a mechanism by which a process may be notified of, or affected by, an event occurring in the system. When the event that causes the signal occurs, the signal is said to be generated. When the appropriate action for the process in response to the signal is taken, the signal is said to be delivered. In the interim, the signal is said to be pending.
Signals are sometimes called ``software interrupts'', as they behave very
similarly to hardware interrupts. Signals usually occur asynchronously
(Figure 6).
Signals can be sent from one process to another process (or to itself),
or are generated by the kernel.
Signals that are not asynchronous are normally generated if an error
occurs like an erroneous arithmetic operation.
A process can choose how to handle a specific signal. A signal can be treated in the following ways:
Originally the signal() system call was used to influence the
behaviour of signals. The problem with the original implementation
of signals was that they were unreliable, signals could be lost.
A process also had to reinstall a signal handler every time it was invoked.
This was changed quite early by the AT&T and BSD version of UNIX, but
unfortunately the changes are not compatible. The POSIX.1 standard, which
defines a standard for reliable signals, is widely accepted in the UNIX
world. Therefore only the signal-related functions of this
standard are described here.
Every signal has a specific name. Table 3 states
the signals which every POSIX-compliant system has to support.
The system calls in Table 4 are useful for dealing with signals.
Signal | Default Action | Description |
SIGABRT | terminate w/core | Abnormal termination, normally caused by abort(). |
SIGALRM | terminate | Timeout, caused e.g. by alarm(). |
SIGFPE | terminate w/core | Erroneous arithmetic operation (e.g. divide by zero). |
SIGHUP | terminate | Hangup on controlling terminal. |
SIGILL | terminate w/core | Invalid hardware instruction. |
SIGINT | terminate | Interactive attention signal (interrupt). |
SIGKILL | terminate | Termination. Cannot be caught or ignored. |
SIGPIPE | terminate | Write on a pipe that is not open for reading by any process. |
SIGQUIT | terminate w/core | Interactive termination signal (quit). |
SIGSEGV | terminate w/core | Invalid memory reference. |
SIGTERM | terminate | Termination signal. |
SIGUSR1 | terminate | Application-defined signal 1. |
SIGUSR2 | terminate | Application-defined signal 2. |
Function | Description |
kill() | sends a signal to a process |
raise() | sends a signal |
alarm() | schedules an alarm |
pause() | suspends process execution |
sigaction() | examines and changes signal action |
sigprocmask() | examines and changes blocked signals |
sigpending() | examines pending signals |
sigsuspend() | waits for a signal |
Some ``slow'' system calls like read()s from pipes are interrupted
by a signal. They return the error code EINTR, and
the program has the responsibility
to issue the system call again if needed. As this is not always a desired
behaviour, some systems allow an automatic restart of these slow system
calls on a per-signal basis (option SA_RESTART,
a Berkeley extension, with sigaction()).
Signal handling is useful in many contexts. An example is to signal another
process that it is ready for a particular task or to do some clean-up
operations like removing temporary files if a SIGINT signal is caught.
The program local/signal.c illustrates the use of the described
signal functions. A more detailed description of POSIX.1 signal handling
can be found in [Zlo91, Chapter 5], [POSIX.1, Chapter 6], and
[Stev92, Chapter 10].
In terms of interprocess communication signals can not transport large amounts
of information from one process to another process. The advantage of signals
is that they normally occur completely asynchronously.
Signals were used in some of the performance measurement programs in
Chapter 7 to synchronize different processes.
Pipes are the oldest IPC mechanism in UNIX, therefore they are supported by
all UNIX systems.
Pipes are created with the pipe(int fd[2]) system call,
which creates two new descriptors.
These descriptors are connected, the first one is used for reading and
the second one for writing. The result of a pipe system call can be
seen in Figure 7.
Pipes are normally useless in a single process. They become useful when a
process
wants to communicate with its children or vice versa. Children are created
with the fork() system call. The result of a fork on a pipe is shown
in Figure 8.
If the data should flow from the child to the parent, the child closes the
read end of the pipe, and the parent process closes the write end. This
results in Figure 9.
The UNIX system guarantees that a certain number of bytes are written
atomically to a pipe. This number of bytes varies, but is normally 4096 or
8192 bytes (the system constant PIPE_BUF should be used for
portability). This can be used if several processes have to communicate with
one process.
The use of pipes has two major drawbacks:
FIFOs (described next) can solve the first problem, while stream pipes (Chapter 3.4)
solve the second one. Program local/pipe.c demonstrates the use of
pipes.
FIFOs (for First In, First Out), sometimes also called ``named pipes'', solve the problem that processes
have to be related when they wish to communicate. A FIFO is created
with the system call mkfifo(const char *pathname, mode_t mode), or the shell command mkfifo.
FIFOs use the pathname to establish a connection. A special file
in the file system is created with the permission bits mode
which can be seen by all processes.
A FIFO can then be used with the
normal I/O functions like open(), read(), write()
and close().
One or more processes may open a FIFO for reading and writing.
FIFOs are often created by shells and used by shell commands
to pass data from one process to
another. The use of FIFOs removes the necessity for temporary files.
Another use for FIFOs is the exchange of data in a client/server application
where the processes are not related (e.g. they are created dynamically from
different processes). The programs
local/fifoclient.c
and
local/fifoserver.c
demonstrate the use of FIFOs.
FIFOs have the drawback that communication is still only possible in one
direction. Either two FIFOs have to be used for communication in both
directions, or stream pipes (described next).
Another problem is the use of file names, as it may not be clear what
happens if a program crashes and leaving a FIFO in the file system, without
the other processes notifying the crash of the communication partner.
A so-called ``stream pipe'' is a bi-directional pipe. To obtain bi-directional
data flow between parent and child, only a single stream pipe is required
[Stev92]. Stream pipes can be named or unnamed.
In SVR4 every pipe is already a bi-directional pipe, so the normal
pipe() system call creates always an unnamed stream pipe.
In BSD an unnamed stream pipe can be obtained via the int socketpair(int domain, int type, int protocol, int fd[2]) system call like this:
int fd[2]; int success; success = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
For a description of socket parameters please refer to Chapter 6.1.
Named stream pipes are more difficult to establish. The function namedstreampipe() in the file local/namedstreampipe.c implements named stream pipes for BSD systems with the use of sockets. Named stream pipes are possible within SVR4, but have to be created by the superuser and are therefore not discussed further.
The System V IPC facilities were first added to System V. They include three different types of IPC with a very similar interface (see Table 5 for summary of system calls):
A System V IPC object (a message queue, a set of semaphores, or a region
of shared memory) has an unique identifier, a key. Keys are comparable
to the file descriptors used for normal I/O, but they exist system wide, they
are not related to processes. To access a certain System V
IPC object the key has to be known. Key identifiers have to be selected
carefully, otherwise conflicts between unrelated applications might
arise. Message queues, semaphores and shared memory are using different name
spaces, e.g. a key for a shared memory region is meaningless for
semaphores and messages. To get unique keys the
library function
key_t ftok(char *pathname, char proj) can be used.
This function converts the pathname of an existing accessible file
and a ``project identifier'' (an arbitrary 8-bit value)
into a (hopefully unique) key suitable for System V IPC.
Each IPC object has a set of IDs and permissions (reading and writing)
like files in a file system. This allows control over the access to a
specific IPC object. The owner ID and group ID specifies
processes with owner or group access privileges.
All three forms of System V IPC have built-in limits. These may be changeable on some systems by reconfiguring the kernel, others have fixed limits. The maximum number of message queues, shared memory regions and semaphore sets that a process can have on the three different systems that are used (and described) for performance measurements in Chapter 7 was derived with the program environment/sysvipclimits.c (and adjusted according to the ipcs command). A word of caution: on some systems the number of available IPC objects per process is equal to the number of system wide available IPC objects. On other systems the system wide number of IPC objects is higher than the number of IPC objects a process can obtain.
Message queues are a linked list of messages stored within the kernel and
identified by a message queue key [Stev92]. Message queues are used
to send data from one process to another process.
Every message can have a priority, and the receiver can specify the
priority of the messages it is willing to receive. This can also be used
to allow bi- or more-directional flow of data if every sender identifies
its data with a different priority.
The size of individual messages is often very limited, normally to about
2 to 4 Kb. Table 6 gives an overview of the
system calls that can be used to handle message queues.
System Call | Description |
msgget() | open an existing message queue or create a new queue |
msgctl() | performs various operations on a message queue, e.g. removing a message queue |
msgsnd() | place data onto a message queue |
msgrcv() | retrieve a message from a message queue |
The program local/message.c demonstrates the use of message
queues by implementing the classical producer/consumer problem.
Table 7 shows the maximum number of message queues
a process may have at best on a variety of systems.
Semaphores are used for managing a fixed amount of shared resources. The UNIX System V IPC semaphores have more capabilities than normal abstract semaphores [Stev92]:
Table 8 gives an overview of the
system calls that can be used to handle semaphore sets.
A problem with System V IPC semaphores is that the creation of semaphores
is independent of their initialization. This can lead to race conditions,
an atomic system call that handles this problem would be desirable.
The program local/semaphore.c shows how semaphores can be
used to coordinate two different processes. Table 9
shows the maximum number of of semaphore sets a process may have at best
on a variety of systems.
Shared memory overcomes the normal isolation between process address spaces.
It allows windows of memory to be shared among a number of processes
(possibly at different address spaces). The access to the shared memory
is not interlocked, therefore the processes using shared memory
normally have to coordinate the access themselves for example with
semaphores. Shared memory is accessed and manipulated using normal
instructions.
Table 10 gives an overview of the
system calls that can be used to handle shared memory regions.
System Call | Description |
shmget() | obtain a shared memory key |
shmctl() | performs various operations on a semaphore set, e.g. removing a shared memory region |
shmat() | attach a shared memory region to the address space |
shmdt() | detach a shared memory region from the address space |
The maximum number of shared memory regions a process can have on each
of the computer systems used varies greatly.
(see Table 11). The program local/shared.c
demonstrates the use of shared memory.
System V IPC facilities often create problems for system maintenance, as the
IPC structures are system wide without a reference count. If a program crashes
before the IPC structures are freed, or the program which uses these IPC
facilities has a bug, they remain in the system till it is rebooted. This
causes a problem, as the maximum number of semaphore sets etc. is limited.
For this reason two programs can be used to get information about System V IPC in a system or to delete them:
A large number of different local IPC facilities are available in UNIX. Signals, pipes, FIFOs, stream pipes, message queues, semaphores, and shared memory were described. Signals allow asynchronous reaction to special circumstances. Semaphores are used to synchronize access to shared limited resources. The use of pipes, FIFOs, stream pipes, and message queues, used to exchange data, is very similar. Example programs for all facilities demonstrate the use of these IPC facilities. Some IPC facilities have built-in limits, some of them are stated.