CS 350, Operating Systems



CS 350, Operating Systems

N. M. Guydosh

12/2/03

Some Background on UNIX Pipes:

This article is based on material from Bach, “The Design of the UNIX Operating System”, Stevens, “Advanced programming in the UNIX Environment”, and Haviland et. al., “UNIX System Programming”

The concept of a file in UNIX is very general. Not only does it apply to our normal idea of files and directories, but it also covers devices and pipes. Without covering file system theory, we will look at pipes as a special type of file.

A pipe is sometimes called a FIFO (first-in-first-out) file. Data written in, and read out of such a file follows the FIFO protocol. Data in such files is not stored permanently as in a “regular” file, but is transient as in a queue. Once data is read from a pipe, it cannot be read again, and must be read in the same order that it was written (FIFO).

Pipes allow the transfer of data between processes in FIFO order. They can be used for process synchronization. Processes can communicate via pipes without knowing what processes are at the other end of the pipe. There are two kinds of pipes: named and unnamed. Named pipes are activated by using the open(...) system call in the same way as in ordinary files. The named pipe will permanently exist in a directory structure and is accessed by a path name. there will be more on this later. Unnamed pipes, on the other hand, are transient (do not reside as a name in a directory), and are created and activated using the pipe(...) system call. Thus the pipe(...) call for unnamed pipes is essentially the same as the open(...) call for named pipes and ordinary files. The semantics of named and unnamed pipes are the same. Any two processes can access and use a named pipe. But unnamed pipes can be used only by “related” processes, ie., only parent and children, or siblings can communicate via an unnamed pipe.

Unamed Pipes

The pipe system call is as follows:

int fd[2]; /* file descriptor */

int pipe(fd);

fd (file descriptor) is an array of two integers: fd[0] for reading, and fd[1] for writing. Calling pipe creates these two values. A normal return is 0, and a -1 indicates a failure. Except for Solaris, pipes are not full duplex, ie., they are half duplex meaning that data is sent only in one direction: the sender writes to fd[1] and the receiver reads from fd[0]. Reading and writing is done the same way as in ordinary file I/O:

write(fd[1], buf, count);

read(fd[0], buf, count);

where buf is a pointer to the source data to be written in a write() call, or a pointer to the destination in memory for the data in a read() call. count is the number of bytes to be written or read.

write() writes the designated number of bytes to the pipe. There is a system determined capacity which limits the total amount of data which could be written to a pipe before it is read. If this limit is reached by the writer, the writing process sleeps awaiting for a reader to drain data from the pipe. The system awakens the sleeping writer when this happens.

read() will read the designated number of bytes from the pipe and leave any remaining bytes in the pipe. Reading logically removes the data from the pipe. If a read() is attempted when the pipe is empty, the read sleeps awaiting for a writer to write more data to the pipe. The system awakens the sleeping reader when this happens.

A pipe is closed in the same way as for a regular file:

close(fd[0]);

close(fd[1]);

Note that unused “channels” in half-duplex pipes should be closed, ie., the writer to fd[1] must first close fd[0], and the reader from fd[0] must close fd[1].

Full Duplex Pipes

Under SVR4 UNIX a pipe is full duplex. Both descriptors fd[0] and fd[1] can be both written to and read from. Full duplex pipes are sometimes called “stream” pipes. POSIX UNIX supports only half-duplex pipes. In terms of commercial systems, Solaris (bingsuns/south pod machine) supports full duplex and Linux supports only half-duplex. Thus, the full duplex problem can only be tested on bingsuns/south pod machine, whereas the half duplex problem (which simulates full duplex using two half-duplex pipes) can be tested on any UNIX system including Linux. The basic principles for half duplex pipes above also apply to full duplex with the exception that there is no need to close unused channels (file descriptor components) as was required for half duplex. The system calls for creating, reading and writing a full-duplex pipe with file descriptor fd[ ] are as follows:

int fd[2];

pipe(fd);

...

/* full-duplex dialog between two “related” processes, say parent and child: */

/* the numbers are the logical sequence of events */

/* parent: */

write(fd[0], buff, count); /* 1 */

...

read(fd[0], buff, count); /* 4 */

/* child: */

read(fd[1], buff, count); /* 2 */

...

write(fd[1], buff, count); /* 3 */

Note that it the roles of fd[0] and fd[1] may be interchanged between the parent and child. All that is required is that the read and write ends of the pipe are opposite indices on each end. You cannot write to fd[0] and read from fd[0] on the other end . The following would also work, BUT HAS THE POTENTIAL OF A RACE CONTITION, AND IS NOT RECOMMENDED:

/* parent: */

write(fd[0], buff, count); /* 1 */

...

read(fd[1], buff, count); /* 4 */

/* child: */

read(fd[1], buff, count); /* 2 */

...

write(fd[0], buff, count); /* 3 */

Named Pipes

Named pipes differ from unnamed pipes in the following ways;

• They reside as a file name in a file listing on UNIX (ls –l), for example a pipe called fifo would be listed as:

prw - - - - - - - 1 cs350 users 0 2003-04-26 16:00 fifo

Note the “p” on the first position in this entry indicates a pipe (analogous to “d” for a directory)

• Processes that have no parent/sibling relationship with each other may communicate over a named pipe. Recall the only parent/child or sibling processes could communicate over unnamed pipes.

• They must explicitly be created if not previously created using the system call mkfifo(…) before calling open (see below or more detail). mkfifo creates the file associated with the pipe.

• The created pipe can then be opened using the open(…) call rather than the pipe(…) call used for unnamed pipes (see below for more detail).

• You may optionally delete the pipe using the remove(…) system call (see below for more detail).

• Less significantly, they are called “FIFO”s

Creation of the named pipe:

Use the mkfifo system call according to the following API:

int mkfifo(const char *pathname, mode_t mode);

pathname is the specification of the pipe file as a file name suffixed with the complete path leading to that file name. If it is given as “immediate data” (not a variable name), the put it in double quotes. If you omit the path prefix to the file name, it will default to the current directory that you are using (at least for bingsuns). The mode parameter is the permission to give the pipe as an octal number. For example to assign read and write permissions for the owner (you), use 0600. If the return value is negative, the call failed, or the pipe already exists. You may assume that a –1 return code means the latter. If the return is not negative, then a new named pipe was successfully created. An example is mkfifo(myfifo, 0600); this creates the named pipe ‘myfifo” in your current directory with permissions 600.

Opening a named pipe:

For unnamed pipes, this task was accomplished using the pipe(…) call. For named pipes, we treat the pipe much the same way as a file is treated. It gets opened with the same open call as used for files according to the following API:

int open (char *pipe_name, flag);

where pipe_name is the same pipe name defined by calling mkfifo above. As with mkfifo, it geheraly is preceded by a path name, but will default to the current directory if it is omitted. pipe_name is quoted string if specified directly. The flag parameter can by used to set access permissions and/or blocking characteristics for acces the pipe. In this project make the sender open it as write-only with non-blocking. This is done by using O_WRONLY | O_NONBLOCK for this flag. The O_WRONLY component opens it for writing, would cause the open to block until another process opened it for reading. Thus we also add the second component (O_NONBLOCK) to this flag. O_NONBLOCK will allow the open to immediately return (even in the absence of a another process opening it for reading), and in addition, it will cause non-blocking writes to this pipe. The return value is the file descriptor to be used later in writing and indicates a failure if it is negative. An example of a sender opening a pipe would be:

fd = open(“myfifo”, O_WRONLY | O_NONBLOCK);

The receiver on a pipe should open the pipe for reading and writing by setting the flag parameter to O_RDWR. The reason we did not use a simple read-only flag (O_RDONLY) is because at least one process must have the pipe open for writing for a read to block on empty. The when the read also opens for writing this is guaranteed (even if the writer was not “launched” as yet). We want the reader to block on empty pipe and wait for something to be sent. An example of a receiver opening a pipe would be:

fd = open(“myfifo”, O_RDWR).

Deleting a named pipe

You may optionally delete a named pipe from the file directory when the programs using it are done wioith it. If it is not done from within a program, then it can be deleted as any other file with the UNIX rm command. To delete it from inside of the program, use the remove system call. It has the API:

int remove(pipe_name);

example remove(“my_fifo”);

A return of 0 means a successful result.

Closing named pipes

Although named pipes are closed in much the same way as are the unnamed pipes, we mention it here for completeness. The call is

int close(int fd);

where fd is the file descriptor of the names pipe, and a return of 0 means success. All pipes should be closed after the processes are cone with them. The last process to complete should do it.

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download