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);
}