Pipe

The pipe is one of the most fundamental inter-process communication (IPC) mechanisms, created by the pipe function:

#include <unistd.h>
int pipe(int filedes[2]);

When pipe is called, the kernel allocates a buffer (called a pipe) for communication. The pipe has a read end and a write end. The filedes parameter passes two file descriptors back to the user program: filedes[0] refers to the read end, and filedes[1] refers to the write end. Reading from or writing to these file descriptors actually reads from or writes to the kernel buffer. pipe returns 0 on success and -1 on failure.

Pipe Example

A child process sends data to its parent process via a pipe. Communication is limited to parent-child processes.

#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>

int main () {
    char* msg;
    char buf[20];
    int pipe_filed[2];
    pipe(pipe_filed);
    pid_t pid = fork();
    if(pid < 0) {
        perror("fork errir.");
        exit(1);
    } else if (0 == pid) {
        msg = "child";
        write(pipe_filed[1], msg, sizeof(msg));
        printf("child process send: %s\n", msg);
    } else {
        read(pipe_filed[0], buf, sizeof(buf));
        printf("parent process recv: %s\n", buf);

        int status;
        wait(&status);
        if (WIFEXITED(status))
            printf("Child exited with code %d\n", WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
            printf("Child terminated abnormally, signal %d\n", WTERMSIG(status));
    }

    return 0;
}

A single pipe provides only one-way communication between two processes. In the example above, the child writes and the parent reads. If bidirectional communication is needed (parent writing to child as well), a second pipe must be opened.

Named Pipe (FIFO)

The pipe described above has a limitation: it can only be used between parent and child processes. For communication between unrelated processes, a named pipe (FIFO) is required.

#include<sys/types.h>
#include<sys/stat.h>

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

This creates a special FIFO file at the path specified by pathname, with permissions given by mode. Returns 0 on success, or -1 on failure with the error code stored in errno.

Named Pipe Example

Communication between arbitrary (non-parent-child) processes. Process A sends to Process B.

Sending process:

/* send process*/
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<string.h>

int main () {
    if (-1 == mkfifo("comm", 0666)) {
        if (EEXIST != errno) {
            perror("mkfifo failure.");
            exit(EXIT_FAILURE);
        }
    }

    int fd = open("comm", O_WRONLY);
    if (fd < 0) {
        perror("open pipe failure.");
    }
    
    char* msg = "process of send.";
    write(fd, msg, strlen(msg));
    close(fd);

    return 0;
}

Receiving process:

/* recv process*/
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<string.h>

int main () {
    if (-1 == mkfifo("comm", 0666)) {
        if (EEXIST != errno) {
            perror("mkfifo failure.");
            exit(EXIT_FAILURE);
        }
    }

    int fd = open("comm", O_RDONLY);
	if (fd < 0) {
        perror("open pipe failure.");
    }
    char* buf = (char*)malloc(80);
    bzero(buf, 80);
	read(fd, buf, 80);
	printf("recv from other process: %s\n", buf);
    close(fd);
	free(buf);

    return 0;
}

Special Cases

The following 4 special cases must be considered when using pipes (assuming blocking I/O — the O_NONBLOCK flag is not set):

  • Write end closed (refcount == 0), still reading: If all file descriptors pointing to the write end of the pipe are closed, and a process is still reading from the read end, then after all remaining data in the pipe has been read, the next read returns 0 — just like reaching the end of a file.

  • Write end open (refcount > 0), no data written: If some file descriptors pointing to the write end remain open, but no process is writing data to the pipe, and a process is reading from the read end, then after all remaining data is consumed, the next read blocks until new data becomes available in the pipe.

  • Read end closed (refcount == 0), still writing: If all file descriptors pointing to the read end of the pipe are closed, and a process attempts to write to the write end, that process receives the SIGPIPE signal, which typically causes the process to terminate abnormally.

  • Read end open (refcount > 0), no data consumed: If some file descriptors pointing to the read end remain open, but no process is reading from the pipe, and a process writes to the write end, then once the pipe buffer is full, the next write blocks until space becomes available in the pipe.

In essence, the behavior follows the same blocking and synchronization semantics as other I/O events.