Related Content: CS604 - VU Lectures, Handouts, PPT Slides, Assignments, Quizzes, Papers & Books of Operating Systems
IPC provides a mechanism to allow processes to communicate and to synchronize their actions without sharing the same address space. We discuss in this section the various message passing techniques and issues related to them.
The function of a message system is to allow processes to communicate without the need to resort to the shared data. Messages sent by a process may be of either fixed or variable size. If processes P and Q want to communicate, a communication link must exist between them and they must send messages to and receive messages from each other through this link. Here are several methods for logically implementing a link and the send and receive options:
We now look at the different types of message systems used for IPC.
With direct communication, each process that wants to communicate must explicitly name the recipient or sender of the communication. The send and receive primitives are defined as:
A communication link in this scheme has the following properties:
Unlike this symmetric addressing scheme, a variant of this scheme employs asymmetric addressing, in which the recipient is not required to name the sender.
With indirect communication, messages can be sent to and received from mailboxes.
Here, two processes can communicate only if they share a mailbox. The send and receive
primitives are defined as:
A communication link in this scheme has the following properties:
Communication between processes takes place by calls to send and receive primitives (i.e., functions). Message passing may be either blocking or non-blocking also called as synchronous and asynchronous.
Whether the communication is direct or indirect, messages exchanged by the processes reside in a temporary queue. This queue can be implemented in three ways:
UNIX and Linux operating systems provide many tools for interprocess communication, mostly in the form of APIs but some also for use at the command line. Here are some of the commonly supported IPC tools in the two operating systems.
We need to understand the purpose and syntax of the read, write and close system calls so that we may move on to understand how communication works between various Linux processes. The read system call is used to read data from a file descriptor. The synopsis of this system call is:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
read() attempts to read up to count bytes from file descriptor fd into the buffer
starting at buf. If count is zero, read() returns zero and has no other results. If count
is greater than SSIZE_MAX, the result is unspecified. On success, read() returns the
number of bytes read (zero indicates end of file) and advances the file position pointer by
this number.
The write() system call is used to write to a file. Its synopsis is as follows:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
write() attempts to write up to count bytes to the file referenced by the file
descriptor fd from the buffer starting at buf. On success, write() returns the number
of bytes written are returned (zero indicates nothing was written) and advances the file
position pointer by this number. On error, read() returns -1, and errno is set
appropriately. If count is zero and the file descriptor refers to a regular file, 0 will be
returned without causing any other effect.
The close() system call is used to close a file descriptor. Its synopsis is:
#include <unistd.h>
int close(int fd);
close() closes a file descriptor, so that it no longer refers to any file and may be reused. If fd is the last copy of a particular file descriptor the resources associated with it are freed; if the descriptor was the last reference to a file which has been removed usin unlink(2) the file is deleted. close() returns zero on success, or -1 if an error occurred.
A UNIX/Linux pipe can be used for IPC between related processes on a system.
Communicating processes typically have sibling or parent-child relationship. At the
command line, a pipe can be used to connect the standard output of one process to the
standard input of another. Pipes provide a method of one-way communication and for this
reason may be called half-duplex pipes.
The pipe() system call creates a pipe and returns two file descriptors, one for
reading and second for writing, as shown in Figure 8.1. The files associated with these
file descriptors are streams and are both opened for reading and writing. Naturally, to use
such a channel properly, one needs to form some kind of protocol in which data is sent
over the pipe. Also, if we want a two-way communication, we'll need two pipes.
Figure 8.1 A UNIX/Linux pipe with a read end and a write end
The system assures us of one thing: the order in which data is written to the pipe, is the same order as that in which data is read from the pipe. The system also assures that data won't get lost in the middle, unless one of the processes (the sender or the receiver) exits prematurely. The pipe() system call is used to create a read-write pipe that may later be used to communicate with a process we'll fork off. The synopsis of the system call is:
#include <unistd.h>
int pipe (int fd[2]);
Each array element stores a file descriptor. fd[0] is the file descriptor for the read end
of the pipe (i.e., the descriptor to be used with the read system call), whereas fd[1] is the
file descriptor for the write end of the pipe. (i.e., the descriptor to be used with the write
system call).The function returns -1 if the call fails. A pipe is a bounded buffer and the
maximum data written is PIPE_BUF, defined in <sys/param.h> in UNIX and in <linux/param.h> in Linux as 5120 and 4096, respectively.
Lets see an example of a two-process system in which the parent process creates a
pipe and forks a child process. The child process writes the ‘Hello, world!’ message to
the pipe. The parent process reads this messages and displays it on the monitor screen.
Figure 8.2 shows the protocol for this communication and Figure 8.3 shows the
corresponding C source code.
In the given program, the parent process first creates a pipe and then forks a child process. On successful execution, the pipe() system call creates a pipe, with its read end descriptor stored in pipefd[0] and write end descriptor stored in pipefd[1]. We call fork() to create a child process, and then use the fact that the memory image of the child process is identical to the memory image of the parent process, so the pipefd[] array is still defined the same way in both of them, and thus they both have the file descriptors of the pipe. Further more, since the file descriptor table is also copied during the fork, the file descriptors are still valid inside the child process. Thus, the parent and child processes can use the pipe for one-way communication as outlined above.