Advanced IPC Facilities
Advanced IPC Facilities
Introduction
Interprocess communication (IPC) refers to the coordination of activities among cooperating
processes. A common example of this need is managing access to a given system resource. To
carry out IPC, some form of active or passive communication is required.
The increasingly important role that distributed systems play in modern computing environments
exacerbates the need for IPC. Systems for managing communication and synchronization between
cooperating processes are essential to many modern software systems. IPC has always played a
prominent role in UNIX-variant operating systems, but it has been largely overlooked for systems
running the Windows NT operating system. This paper will discuss some of the IPC options that
are available to programmers using UNIX and describe the corresponding techniques available to
programmers writing for Windows NT. Features and mechanisms specific to Windows NT will
also be discussed. The conclusion will offer a summary of the available techniques as applicable to
Windows NT.
2. Pipes
Pipes are a simple synchronized way of passing information between two processes. A pipe can be
viewed as a special file that can store only a limited amount of data and uses a FIFO access
scheme to retrieve data. In a logical view of a pipe, data is written to one end and read from the
other. The processes on the ends of a pipe have no easy way to identify what process is on the
other end of the pipe.
The system provides synchronization between the reading and writing process. It also solves the
producer/consumer problem: writing to a full pipe automatically blocks, as does reading from an
empty pipe. The system also assures that there are processes on both ends of the pipe at all time.
The programmer is still responsible, however, for preventing deadlock between processes.
Pipes come in two varieties:
Unnamed. Unnamed pipes can only be used by related processes (i.e. a process and one of its
child processes, or two of its children). Unnamed pipes cease to exist after the processes are
done using them.
Named. Named pipes exist as directory entries, complete with permissions. This means that
they are persistent and that unrelated processes can use them.
2.1 UNIX
Most UNIX systems limit pipes to 5120K (typically ten 512K chunks). The unbuffered system call
write() is used to add data to a pipe. Write() takes a file descriptor (which can refer to the pipe), a
buffer containing the data to be written, and the size of the buffer as parameters. The system
assures that no interleaving will occur between writes, even if the pipeline fills temporarily. To get
data from a pipe, the read() system call is used. Read() functions on pipes much the same as it
functions on files. However, seeking is not supported and it will block until there is data to be
read.
The pipe() system call is used to create unnamed pipes in UNIX. This call returns two pipes. Both
support bidirectional communication (two pipes are returned for historical reasons: at one time
pipes were unidirectional so two pipes were needed for bidirectional communication). In a fullduplex environment (i.e. one that supports bidirectional pipes) each process reads from one pipe
and writes to the other; in a half-duplex (i.e. unidirectional) setting, the first file descriptor is
always used for reading and the second for writing.
Pipes are commonly used on the UNIX command line to send the output of one process to another
process as input. When a pipe is used both processes run concurrently and there is no guarantee as
to the sequence in which each process will be allowed to run. However, since the system manages
the producer/consumer issue, both proceed per usual, and the system provides automatic blocking
as required.
Using unnamed pipes in a UNIX environment normally involves several steps:
Create the pipe(s) needed
Generate the child process(es)
Close/duplicate the file descriptors to associate the ends of the pipe
Close the unused end(s) of the pipe(s)
Perform the communication
Close the remaining file descriptors
Wait for the child process to terminate
To simplify this process, UNIX provides two system calls that handle this procedure. The call
popen() returns a pointer to a file after accepting a shell command to be executed as input. Also
given as input is a type flag that determines how the returned file descriptor will be used. The
popen() call automatically generates a child process, which exec()s a shell and runs the indicated
command. Depending on the flag passed in, this command could have either read or write access
to the file. The pclose() call is used to close the data stream opened with popen(). It takes the file
descriptor returned by popen() as its only parameter.
Named pipes can be created on the UNIX command line using mknod, but it is more interesting to
look at how they can be used programatically. The mknod() system call, usable only by the
superuser, takes a path, access permissions, and a device (typically unused) as parameters and
creates a pipe referred to by the user-specified path. Often, mkfifo() will be provided as an
additional call that can be used by all users but is only capable of making FIFO pipes.
2.2 Windows NT
Windows NT supports both named and unnamed pipes, although it refers to the latter as
anonymous pipes.
The CreatePipe() function creates an anonymous pipe and returns two handles. One handle is a
read handle to the pipe and the other is a write handle to the pipe; neither pipe can perform the
opposite operation. When the pipe is created the server requests that it be implemented using a
programmer-defined buffer size. To communicate using the pipe, the server must pass one of the
handles to another process. Usually, this is done through inheritance; that is, the process allows the
handle to be inherited by a child process. The process can also send the handle to an unrelated
process using another form of interprocess communication, such as shared memory.
A server can send either the read handle or the write handle to the pipe client, depending on
whether the client should use the anonymous pipe to send information or receive information. To
read from the pipe, the pipe's read handle is used as a parameter to the call ReadFile(). The
ReadFile() call returns when another process has written to the pipe. ReadFile() call can also
return if all write handles to the pipe have been closed or if an error occurs before the read
operation has been completed.
3. Sockets
A socket is a software abstraction that is used to create a channel-like interface between processes.
The channel allows bi-directional communication between processes, but does little to structure
the data being transmitted. In general, the serving machine in a system that uses socket
communication follows several steps to initiate communication:
Create a socket
Map the socket to a local address
Listen for client requests
When the client process wishes to begin a transaction with the server, it follows similar steps:
Create a socket
Determine the location (system name and port number) of the server
Begin sending and/or receiving data
ports. Finally, these classes still rely on WinSock v1.1; they do not take advantage of the new
features or enhanced efficiency of WinSock v2.2 yet.
4. Shared Memory
Shared memory allows multiple processes to share virtual memory space so changes made by one
process are instantly reflected in other processes. This affords the fastest possible IPC but is not
necessarily the easiest to coordinate between the two processes. Shared memory is unique in that it
allows random access of data, whereas most other IPC mechanisms mandate sequential access to
data. Typically, one process creates a shared segment, and needs to set access permissions for the
segment. It is then mapped into the processs address space after the process attaches to it. Usually
the creating process initializes the memory, but after this other processes that have been given
access permission to the segment can use it freely. When another process does use it, it is mapped
into its address space. Oftentimes semaphores are used by the original process to be sure that only
one other process is allowed to access the memory at any one time, although a readers/writers lock
may be a more suitable solution. When no process needs the memory segment anymore, the
creating process can delete it.
Windows NT requires the decision to be made earlier in the process, however; the second type of
mapping has not yet been presented.
13
14
The OpenFileMapping() call returns a handle to a file-mapping kernel object that was previously
created using CreateFileMapping(). As parameters it takes the level of access that is desired for the
segment, whether or not child processes should inherit the handle, and the name of the memory
region to open (as specified when CreateFileMapping() was called). After the handle to the kernel
object is acquired, a pointer to the memory that it represents is required. The MapViewOfFile()
call can be used to get this pointer. MapViewOfFile() takes the handle returned by
CreateFileMapping(), the desired level of access to the memory, and the size of the region to map
as parameters. It returns a pointer to the beginning of the memory segment.
When the shared map is no longer required, it should be released using UnmapViewOfFile(). It
takes the address of the memory (as returned by MapViewOfFile()) as a parameter. The
programmer should also be careful to close the handle to the file-mapping kernel object.
5. Mailslots
Mailslots, available only in Windows NT, provide one-way communication between processes. A
mailslot is a pseudofile; it resides in memory, and standard file functions are used to access it. The
data in a mailslot message can be in any form. When all handles to a mailslot are closed, the
mailslot and all the data it contains are deleted, so mailslots cannot be used for long-term storage.
A mailslot server is a process that creates and owns a mailslot. When the server creates a mailslot,
it receives a mailslot handle. This handle must be used when a process reads messages from the
mailslot. Only the process that creates a mailslot or obtains a handle to can read from the mailslot.
All mailslots are local to the process that creates them; a process cannot create a remote mailslot.
A mailslot client is a process that writes a message to a mailslot. Any process that has the name of
a mailslot can put a message there. New messages follow any existing messages in the mailslot.
Processes that create mailslots act as servers to other processes that send messages to it by writing
a message to its mailslot. Incoming messages are always appended to the mailslot and are saved
until the mailslot server has read them. A process can be both a mailslot server and a mailslot
client, so two-way communication is possible using multiple mailslots. This is useful for
implementing a simple message-passing facility within a domain
Mailslot clients can send a message to any number of mailslot servers located anywhere on the
network with a single call, as long as the mailslots all share the same name. The message can also
be restricted to the local machine or a specific machine on the network. Messages broadcast to all
mailslots on a domain can be no longer than 400 bytes, whereas messages sent to a single mailslot
are limited only by the maximum message size specified by the mailslot server when it created the
mailslot.
6. Conclusions
6.1 Review
Both UNIX and Windows NT provide efficient pipe implementations that are excellent in their
own unique ways. The UNIX implementation is very simple to use whereas the Windows NT
implementation is quite complex (in comparison), but affords the programmer substantially more
flexibility. The abundance of information available at runtime allows powerful servers handling
multiple clients to be created. The possibility for pipes to alternate between being synchronous and
asynchronous is also a nice feature. Windows NT also natively supports multiple pipes, does not
restrict the use of unnamed pipes to related processes, and allows bi-directional communication
using a single pipe. Under both operating systems, pipes provide a reliable means for IPC, and the
two parties can verify receipt of data in real-time.
15