This article is mainly notes I have taken for CSCB09/CSC209 at UofT.


Pipes are one-way communication channel, they are buffers managed by the OS.

Pipes use file descriptors for pipe operations.

File Descriptors

There are four main operations: open, close, read and write.

int open(const char *pathname, int flags);

Flags can be O_RDONLY, O_WRONLY or O_RDWR.

ssize_t read(int fd, void *buf, size_t count);

Return number of bytes read, 0 EOF, -1 error.

ssize_t write(int fd, const void *buf, size_t count);

Return number of bytes written, -1 error

int close(int fd);

0 success, -1 error

You can use fileno to get the file descriptor for a FILE object.

On fork, children gets a copy of parent’s file table.

Create Pipes

To create a pipe, you call pipe, which will set two integers, one for read, one for write.

int fds[2];

fds[0] is now for reading, fds[1] is now for writing.

int main(void) {
    char buf[4];
    int p[2], nbytes;
    if(pipe(p) == -1) {
        // Error
       while((nbytes = read(p[0], buf, 4)) > 0)
           write (STDOUT_FILENO, buf, nbytes);
    } else {
        write(p[1], "hi", 3);
  • Write to pipe that no one can read, will get SIGPIPE.
  • Write to pipe and the buffer is full, it blocks.

  • Read from pipe that no one can write, will get EOF.
  • Read from pipe and there is no data, blocks.


dup2() function can be used to set one FD to be copy of another.

dup2(fd, fileno(stdin));
// This will make stdin fd instead.


/* equivalent to “sort < file1 | uniq” */
int fd[2], pid;
int filedes = open("file1", O_RDONLY);
dup2(filedes, fileno(stdin));

if((pid = fork()) == 0) {/* child */
    dup2(fd[1], fileno(stdout));
    close(fd[0]); close(fd[1]);
    execl("/usr/bin/sort", "sort", (char *) 0);
} else if(pid > 0){ /* parent */
    dup2(fd[0], fileno(stdin));
    close(fd[1]); close(fd[0]);
    execl("/usr/bin/uniq", "uniq", (char *) 0);
} else {
    perror("fork"); exit(1);