Unix Pipes
Overview
The purpose of this assignment is to become familiar with some fundamental methods of communication between processes. Specifically, it explores the use pipes. It also provides an introduction to basic UNIX file operations.
Activities
Work your way through the following exercises, demonstrating your knowledge of the material by answering the numbered questions.
Write a short multi-process program that demonstrates OS-level inter-process communication by raising and catching signals.
Submit a detailed lab report. Include the following
- the answers to the numbered questions (be sure to include the original question text before writing your answer)
- your source code
File I/O
This section describes some of the basics of UNIX file I/O - it is a prelude to understanding inter-process communication via pipes (see any UNIX reference book for more details). UNIX promotes device-independent input and output by the use of handles/descriptors. This means that handles are used for many different types of I/O (e.g. files, pipes, sockets, physical devices). A handle is also known as a file descriptor, which is basically an integer index into a table of entries, each of which is associated with a file or device. Each process initially has the file descriptors 0, 1, 2 (accessible via the fileno()
function call or the macros STDIN_FILENO
, STDOUT_FILENO
, STDERR_FILENO
). These file descriptors correspond to standard input (keyboard), standard output (buffered display), and standard error (unbuffered display). The file descriptor table for a process initially looks like this:
File Descriptor | Connected to |
---|---|
0 | standard input (stdin ) |
1 | standard output (stdout ) |
2 | standard error (stderr ) |
Basic I/O-related System Calls
Most UNIX I/O can be performed using the system calls open()
, close()
, read()
, and write()
. A file is opened by giving its name, the mode (read/write), and file permissions (if you are creating it). The associated file descriptor is returned. For example to open a file for reading only:
int fd_in = open("input.dat", O_RDONLY, 0);
or to open for writing only (and create it if it does not exist):
int fd_rec = open("record.dat", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
The third argument in the above call is a bitwise OR of two predefined constants:
S_IRUSR
: allows read permission for user ownerS_IWUSR
: allows write permission for user owner
To enable permissions (read/write/execute) for group/other you can also use other predefined constants such as: S_IXUSR
, S_IRGRP
, S_IWGRP
, S_IXGRP
, S_IROTH
, S_IWOTH
, S_IXOTH
.
A file is closed by giving its file descriptor as the only argument:
close(fd_in);
close(fd_rec);
Remember that closing a file does not delete the file, it only removes the connection between your program and the file.
Input and output (reading/writing) are symmetric system calls:
numActuallyRead = read (fd_in_source, address_of_destination, number_of_bytes_to_read);
numActuallyWritten = write(fd_out_destination, address_of_data_source, number_of_bytes_to_write);
When reading data from a source, your program may potentially encounter "error". For instance, it expects to read 100 bytes, but only 71 bytes are available to read. This is why the read()
system call has the return value to inform your program how many bytes are actually read. Similar ideas apply to write()
as well.
Familiarize yourself with the use of the open()
, close()
, read()
, and write()
system calls (from manual pages section 2)
Pipes
The pipe()
system call creates a unidirectional communication buffer space managed by the operating system. It also initializes two file descriptors - one associated with each end of the pipe (the "writing" end and the "reading" end). Using the read()
and write()
system calls, the pipe can be used for communication by connecting a process to each end. Note: redirection (as implemented via the
The following program illustrates the general concept of inter-process communication and many of the system calls discussed in this lab.
WARNING
The following sample program is designed for our Linux machines. Compiling it on WSL may show a false warning message.
Sample Program 1
[Gist]API rate limit exceeded for 35.168.124.254. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
WARNING
In order to de-clutter the code, the above program is intentionally written without a wait()
call in the parent timeline.
Perform the following and answer the questions:
- study, compile and run Sample Program 1
(4 pts) what exactly does the program do (i.e. describe its functionality)?
- Which process (parent or child) reads data from the keyboard?
- Which process (parent or child) writes data to the pipe?
- Which process (parent or child) reads data from the pipe?
- Which process (parent or child) prints to console?
(3 pts) The program is missing three system calls: one
close()
call in parent, oneclose()
call in child, and onewait()
call in parent. Add the missing calls in the proper spots. Recompile and run again to make sure the program still works as expected. Include the modified program in your report (or upload it separately from your report to Bb).(2 pts) create a diagram that visually describes the input/output structure of the executing programs. Show how the processes are connected to file handles using a similar diagram used in the pipe example in lecture:
- at point A (after the pipe is created)
- at point B (after the parent forks)
- at point C (after the parent and child closes one of the descriptors)
The figure below shows the expected diagram at point A. You should submit a total of 3 diagrams (including the one below)
Modify Sample Program 1 and add the following features:
- (1 pt) In addition to the existing pipe (Q1) setup a second pipe (Q2) to allow data transfer in the opposite direction.
- (2 pts) Upon receiving a sentence from Q1, the recipient counts the number of letter 'a' and sends the count back via Q2 to the other process which then prints the count to stdout.
- (2 pts) Add a loop in both the sender and recipient to allow the user to enter multiple sentences until a "quit" is typed.
- (2 pts) When the user enters "quit", counting letter 'a' should be skipped, and both processes should perform graceful cleanup: break the loop, close all descriptors, parent
wait()
, etc.
Input/Output Redirections
UNIX also permits file redirection, by modifying an entry in the file descriptor table. Suppose the process that owns the above table is executing the "ls" utility, and its output is redirected to a file as in:
# Redirect (normal) output of ls to a file
ls > directory.txt
then its file descriptor table would now appear as:
File Descriptor | Connected to |
---|---|
0 | standard input (stdin ) |
1 | file (directory.txt ) |
2 | standard error (stderr ) |
indicating that the normal stdout
is now being written to the file directory.txt
.
This redirection can also be performed from within a program by using the dup2()
system call, which closes an existing file descriptor and points it at the same file as another file descriptor (see the man pages for details). As its name implies, dup2
is used to copy or duplicate a file handle, thus creating an "alias" from an existing descriptor. The file redirection described above is implemented in the shell by using dup2()
.
Sample Program 2
[Gist]API rate limit exceeded for 35.168.124.254. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
(1 pt) Compile and run Sample Program 2. At each input prompt enter some name of your choice. Do you see the output
Your neighbor is: XXXXXX
printed on the screen? Explain what happened.(1 pt) Suppose a process has used
dup2()
to make its standard output point to a file namedtemp
. The process then issues afork()
system call to spawn a child. The child issues anexec()
system call to execute a different program. Where does the standard output of the child process go? Explain.(1 pt) Suppose a process issues a
fork()
system call to spawn a child, then the parent usesdup2()
to make its standard output point to a file namedtemp
. The child issues anexec()
system call to execute a different program. Where does the standard output of the child process go? Explain.