UNIX Pipes
I/O Redirection
- How does the shell do
- date > out
- The quick answer:
- We modify the file descriptor table
- We need to take a closer look at how the file descriptor table works
File Descriptor Table (FD Table)
- Each Process has a file descriptor table in the kernel (in the Process Control Block)
- fork() duplicates the file descriptor table
- Draw picture of two file descriptor tables with stdin, stdout, stderr.
Modifying the FD Table
- In order to redirect input/output, we need to modify the file descriptor table
- New system call:
- int dup(int oldfd)
- Makes a copy of the given fd in the first available fd table slot
- We can use close() to open up slots
Redirection Example
redirect.c
/* redirect.c - example of redirection */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
pid_t id;
int fd;
if ((fd = open("out", O_CREAT | O_WRONLY, 0644)) < 0) {
printf("cannot open out\n");
exit(1);
}
id = fork();
if (id == 0) {
/* we are in the child */
/* close stdout in child */
close(1);
dup(fd); /* replace stdout in child with "out" */
close(fd);
execl("/bin/date", "date", NULL);
}
/* we are in the parent */
close(fd);
id = wait(NULL);
return 0;
}
Interprocess Communication (IPC)
- It is often useful and necessary to allow different processes to communicate with each other
- There are two basic types of IPC
- Explicit mechanisms: System call interface such as pipes, sockets, message passing, and remote procedure call (RPC)
- Shared memory: Using virtual memory, allow processes to share a portion of memory. Processes communicate by accessing shared variables.
- Today we look at UNIX pipes; later we will look at shared memory, message passing, and RPC.
UNIX Pipes
- A UNIX pipe is an IPC mechanism
- A pipe allows for the exchange of data between processes
- A pipe is used to send a stream of character data
- Basic idea
- Create a pipe using the pipe() system call
- At the user level, a pipe is just a pair of file descriptors
- One for reading (0)
- One for writing (1)
- Use read() and write() system calls to receive and send data
- Order: FIFO (First in, first out)
Uses for Pipes
- General mechanism to allow related processes running on the same machine to communicate
- Note: we need something else to allow inter-machine communication (i.e., sockets)
- Use by the shell to "connect" programs
- ls | wc
- Gives the number of files in current directory
- who | wc
- Gives the number of users logged in
- who | sort | lpr # prints a sorted list of users
- Note that shell pipes are unidirectional
- System pipes can be bidirectional
The Pipe System Call
- int pipe(int filedes[2])
- Check return value for possible errors
- int filedes[2] is just an array of two ints
- filedes[0] is for reading (the "read end")
- filedes[1] is for writing (the "write end")
- Remember the read end is 0 (like stdin) and the write end is 1 (like stdout)
- Usage
int filedes[2];
pipe(filedes); - Draw picture of a pipe with "hello world"
Connecting Processes
- Consider a parent that wants to send data to a child
- In Parent:
- create a pipe
- pipe(filedes)
- fork() a child
- close(filedes[0]) (don't leave open ends)
- write to child with
- write(filedes[1], ...)
- close(filedes[1]) on completion (important)
- In Child:
- close(filedes[1]) (don't leave open ends)
- optional: redirect stdin and use execl()
- read from parent with
- read(filedes[0], ...)
- close(filedes[0]) when done
Notes on Pipes
- Can setup many configurations
- Parent writes to a child (1 pipe)
- Child writes to a parent (1 pipe)
- Parent writes to a child and child writes to parent (2 pipes)
- Parent connects to child (1 pipe, like the shell)
- Parent connects two children (1 pipe)
- One processes with a common parent can communicate with a standard pipe
- There is a "named" pipe that can exist in the filesystem
- You need to close the ends of the pipe to ensure correct termination
- Use dup() to redirect stdin or stdout to a pipe
Pipe Examples
pipe.c
/* pipe.c - example of a pipe */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
pid_t id;
int count;
char buf[100];
int fildes[2];
pipe(fildes);
id = fork();
if (id == 0) {
//sleep(1);
/* we are in the child */
/* close "write" end of pipe */
close(fildes[1]);
if ((count = read(fildes[0], buf, 100) < 0)) {
fprintf(stderr, "cannot read from pipe\n");
exit(1);
}
printf("buf[] = %s\n", buf);
close(fildes[0]);
exit(0);
} else {
/* we are in the parent */
/* close "read" end of pipe */
close(fildes[0]);
if (write(fildes[1], "Hello child!", 13) < 0) {
fprintf(stderr, "cannot write to pipe\n");
}
close(fildes[1]);
id = wait(NULL);
}
return 0;
}
pipe-exec.c
/* pipe-exec.c - use a pipe to send "input" to another program */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
pid_t id;
int count;
int fildes[2];
pipe(fildes);
if ((id = fork()) == 0) {
/* we are in the child */
close(0); /* close stdin */
dup(fildes[0]); /* put read end of pipe into stdin index */
close(fildes[1]); /* close "write" end of pipe */
if (execlp("sort", "sort", NULL) < 0) {
printf("execl failed\n");
exit(1);
}
} else {
/* we are in the parent */
/* close "read" end of pipe */
close(fildes[0]);
/* send some data to sort */
write(fildes[1], "bbb\n", 4);
write(fildes[1], "ttt\n", 4);
write(fildes[1], "aaa\n", 4);
/* close write end to tell sort we are done */
close(fildes[1]);
id = wait(NULL); /* wait for child */
}
return(0);
}