CS604 - Operating Systems - Lecture Handout 08

User Rating:  / 0
PoorBest 

Related Content: CS604 - VU Lectures, Handouts, PPT Slides, Assignments, Quizzes, Papers & Books of Operating Systems

Summary

  • Interprocess communication (IPC) and process synchronization
  • UNIX/Linux IPC tools (pipe, named pipe—FIFO, socket, TLI, message queue, shared memory)
  • Use of UNIC/Linux pipe in a sample program

Interprocess Communication (IPC)

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.

Message Passing System

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:

  • Direct or indirect communication
  • Symmetric or asymmetric communication
  • Automatic or explicit buffering
  • Send by copy or send by reference
  • Fixed size or variable size messages

We now look at the different types of message systems used for IPC.

Direct Communication

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:

  • Send(P, message) – send a message to process P
  • Receive(Q, message) – receive a message from process Q.

A communication link in this scheme has the following properties:

  • A link is established automatically between every pair of processes that want to communicate. The processes need to know only each other’s identity to communicate
  • A link is associated with exactly two processes.
  • Exactly one link exists between each pair of processes.

Unlike this symmetric addressing scheme, a variant of this scheme employs asymmetric addressing, in which the recipient is not required to name the sender.

  • Send(P, message) – send a message to process P
  • Receive(id, message) – receive a message from any process; the variable id is set to the name of the process with which communication has taken place.

Indirect Communication

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:

  • Send(A, message) – send a message to mailbox A.
  • Receive(A, message) – receive a message from mailbox A.

A communication link in this scheme has the following properties:

  • A link is established between a pair of processes only if both members have a shared mailbox.
  • A link is associated with more than two processes.
  • A number of different links may exist between each pair of communicating processes, with each link corresponding to one mailbox.

Synchronization

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.

  • Blocking send: The sending process is blocked until the receiving process or the mailbox receives the message.
  • Non-blocking send: The sending process sends the message and resumes operation.
  • Blocking receive: The receiver blocks until a message is available.
  • Non-blocking receiver: The receiver receives either a valid message or a null.

Buffering

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:

  • Zero Capacity: The queue has maximum length zero, thus the link cannot have any messages waiting in it. In this case the sender must block until the message has been received.
  • Bounded Capacity: This queue has finite length n; thus at most n messages can reside in it. If the queue is not full when a new message is sent, the later is placed in the queue and the sender resumes operation. If the queue is full, the sender blocks until space is available.
  • Unbounded Capacity: The queue has infinite length; thus the sender never blocks.

UNIX/Linux IPC Tools

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.

  • Pipe
  • Named pipe (FIFO)
  • BSD Socket
  • TLI
  • Message queue
  • Shared memory
  • Etc.

Overview of read(), write(), and close() System Calls

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.

Pipes

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.

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.

half-duplex communication

UNIX-Linux pipe for IPC

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.