Programming FIFO/Named PIPE in Linux

Previous post covers pipe, an IPC mechanism for processes that have common parent process. We refer to processes that have common parent process as related process. But for unrelated processes, pipe cannot be used, because one process has no way of referring to pipes have been created by another process.

When two unrelated processes share some information, an identifier must be associated with the shared information. Therefore, one process can create the IPC object and other processes can refer to the IPC object by the identifier. Linux provides named pipe (also called FIFO) to communication through pipe in two unrelated processes.

A FIFO is created by mkfifo function,

#include <sys/types.h>

#include <sys/stat.h>

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

mkfifo creates a special file with name pathname. mode specifies the special FIFO file’s permissions. It is modified by the process’s umask: the created file will have the permission (mode & ~umask).

Once a FIFO is created, it can be operated like a usual file with the exception that both ends of FIFO need to be open first before reading and writing can begin. In other words, opening a file for reading blocks until another process open it for writing, and vice versa.

Below are two programs that uses named pipe to communicate with each other. It is modified from the pipe post example.

Firstly, the two programs need to agree on the pipe names. This is defined in a header file included by both of them.

#ifndef TEST_FIFO_H

#define TEST_FIFO_H

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <fcntl.h>

#include <sys/types.h>

#include <sys/stat.h>

 

#define CS_FIFO_NAME "/tmp/cs"

#define SC_FIFO_NAME "/tmp/sc"

 

//user read, user write, group read and other read

#define FIFO_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

 

#endif

Next, the server program creates two pipes for communication and opens one for read and one for write,

#include "fifo.h"

 

void createfifo() {

    int rv;

    if ((rv = mkfifo(CS_FIFO_NAME, FIFO_MODE)) == -1) {

        perror("error making cs fifo: ");

        return;

    }

    if ((rv = mkfifo(SC_FIFO_NAME, FIFO_MODE)) == -1) {

        perror("error making sc fifo: ");

        return;

    }

}

 

void server(int readfd, int writefd) {

    char msg[100];

    int n;

    if ((n = read(readfd, msg, 100)) < 0) {

        perror("error reading...");

    }

    msg[n] = '';

    printf("%d server received from client: %sn", getpid(), msg);

    printf("server enter something: ");

    fgets(msg, 100, stdin);

    write(writefd, msg, strlen(msg)-1); //-1: not send the newline

}

 

int main(int argc, char **argv) {

    int readfd, writefd;

    createfifo();

    readfd = open(CS_FIFO_NAME, O_RDONLY, 0);

    writefd = open(SC_FIFO_NAME, O_WRONLY, 0);

    server(readfd, writefd);

    close(readfd);

    close(writefd);

    return 0; 

}

The client program also opens the two fifos for write and read.

#include "fifo.h"

 

void client(int readfd, int writefd) {

    char msg[100];

    int n;

    char eof = EOF;

    printf("%d client enter something: ", getpid());

    fgets(msg, 100, stdin);

    if (write(writefd, msg, strlen(msg)-1) == -1) { //-1: not send the newline

        perror("error writing...");

        exit(0);

    }

    if((n = read(readfd, msg, 100)) < 0){

        perror("error reading...");

        exit(0);

    }

    msg[n] = '';

    printf("client received from server: %sn", msg);

}

 

void removefifo() {

    unlink(SC_FIFO_NAME);

    unlink(CS_FIFO_NAME);

}

 

int main(int argc, char **argv) {

    int readfd, writefd;

 

    writefd = open(CS_FIFO_NAME, O_WRONLY, 0);

    readfd = open(SC_FIFO_NAME, O_RDONLY, 0);

    client(readfd, writefd);

    close(readfd);

    close(writefd);

    removefifo();

    return 0;

}

Note that if we swap the order of the two lines in the client source code, a deadlock will occur.

writefd = open(CS_FIFO_NAME, O_WRONLY, 0);

readfd = open(SC_FIFO_NAME, O_RDONLY, 0);

This is because the server blocks at opening CS_FIFO_NAME for reading and waits for client to open it for writing, if the client opens SC_FIFO_NAME for reading first, it also blocks and waits for server open it for writing. Both programs stuck forever.

Note that similar to pipes, it is also possible to create NONBLOCK FIFOs. Also the PIPE_BUF limits apply to FIFOs.

For a thorough explanation on these subjects, one can refer to reference 2.

References:

1. mkfifo man page: http://linux.die.net/man/3/mkfifo

2. Unix Network Programming, Volumn 2.

Programming Pipe in Linux

Pipe is one of the message passing IPC techniques. A pipe is a one way communication channel. It is created by the pipe function,

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

The array fd[2] returns two file descriptors referring to two ends of the created pipe: fd[0] for reading and fd[1] for writing. Data written to the pipe is buffered by the kernel until it is read by the read end.

Below is a program use pipe to create a two way communication channel between two processes.

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

 

void client(int readfd, int writefd) {

    char msg[100];

    int n;

    char eof = EOF;

    printf("%d client enter something: ", getpid());

    fgets(msg, 100, stdin);

    if (write(writefd, msg, strlen(msg)-1) == -1) { //-1: not send the newline

        perror("error writing...");

        exit(0);

    }

    if((n = read(readfd, msg, 100)) < 0){

        perror("error reading...");

        exit(0);

    }

    msg[n] = '';

    printf("client received from server: %sn", msg);

}

 

void server(int readfd, int writefd) {

    char msg[100];

    int n;

    if ((n = read(readfd, msg, 100)) < 0) {

        perror("error reading...");

    }

    msg[n] = '';

    printf("%d server received from client: %sn", getpid(), msg);

    printf("server enter something: ");

    fgets(msg, 100, stdin);

    write(writefd, msg, strlen(msg)-1); //-1: not send the newline

}

 

int main(int argc, char **argv) {

    int pipe1fd[2], pipe2fd[2];

    int pid;

    if (pipe(pipe1fd) == -1) {

        perror("pipe:");

        return 0;

    }

    if (pipe(pipe2fd) == -1) {

        perror("pipe:");

        return 0;

    }

    pid = fork();

    if (pid == 0) {

        //child process

        close(pipe1fd[1]);  //close the write end

        close(pipe2fd[0]);  //close the read end

        client(pipe1fd[0], pipe2fd[1]);

        exit(0);

    } else {

        //parent process

        close(pipe1fd[0]);

        close(pipe2fd[1]);

        server(pipe2fd[0], pipe1fd[1]);

        wait(NULL);       //wait for child process to finish

        return 0;

    }

}

Compile the code using the command,

gcc -o pipe pipe.c

And below is a screenshot of a sample run,

Figure 1. Execution of Pipe Sample Program

The program above illustrates the steps of creating two pipes and use them for two way communication.

1. Create two pipes, pipe1 (pipe1fd[2]) and pipe2 (pipe2fd[2]).
2. call fork to create a child process
3. child process close write end of pipe1 (pipe1fd[1]) and read end of pipe2 (pipe2fd[0])
4. parent process close read end of pipe1 (pipe1fd[0]) and write end of pipe2 (pipe2fd[1])

POSIX defines “half-duplex” pipes, where communication is one way in pipes. System V Release 4 (SVR4) Unix implements “full-duplex” manner, which allows the two file descriptors for both reading and writing.

GNU/Linux implements “half-duplex” in a unique manner. One does not have to close one of them in order to use the other as shown in the above program. Comment out the close statements give the program below,

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

 

void client(int readfd, int writefd) {

    char msg[100];

    int n;

    char eof = EOF;

    printf("client enter something: ");

    fgets(msg, 100, stdin);

    if (write(writefd, msg, strlen(msg)-1) == -1) { //-1: not send the newline

        perror("error writing...");

        exit(0);

    }

    if((n = read(readfd, msg, 100)) < 0){

        perror("error reading...");

        exit(0);

    }

    msg[n] = '';

    printf("client received from server: %sn", msg);

}

 

void server(int readfd, int writefd) {

    char msg[100];

    int n;

    if ((n = read(readfd, msg, 100)) < 0) {

        perror("error reading...");

    }

    msg[n] = '';

    printf("server received from client: %sn", msg);

    printf("server enter something: ");

    fgets(msg, 100, stdin);

    write(writefd, msg, strlen(msg)-1); //-1: not send the newline

}

 

int main(int argc, char **argv) {

    int pipe1fd[2], pipe2fd[2];

    int pid;

    if (pipe(pipe1fd) == -1) {

        perror("pipe:");

        return 0;

    }

    if (pipe(pipe2fd) == -1) {

        perror("pipe:");

        return 0;

    }

    pid = fork();

    if (pid == 0) {

        //child process

//        close(pipe1fd[1]);  //close the write end

//        close(pipe2fd[0]);  //close the read end

        client(pipe1fd[0], pipe2fd[1]);

        exit(0);

    } else {

        //parent process

//        close(pipe1fd[0]);

//        close(pipe2fd[1]);

        server(pipe2fd[0], pipe1fd[1]);

        wait(NULL);       //wait for child process to finish

        return 0;

    }

}

Compile the code using,

gcc -o pipe2 pipe2.c

And below is a screenshot of a run,

Figure 2. Execution of Modified Pipe Sample Program

popen and pclose

Linux provides two functions (popen and pclose) to create pipe to or from a process. It avoids the usage of pipe, fork, close, and wait.

#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

popen function starts a process by creating a pipe, forking and invoking a shell. The command argument accepts a shell command line, and the command is passed to /bin/sh using -c flag. The type argument expects either “r” or “w”. Note that since a pipe is unidirectional, we cannot pass both “r” and “w”, and the returned pipe stream is either read-only or write-only.

If the returned pipe stream is read-only, the calling process reads from the standard output.

If the returned pipe stream is write-only, the calling process writes to the standard input.
pclose function waits for the associated process to terminate and returns the exit status of the command passed to popen.

Note that with popen and pclose, it is not convenient to create two pipes for two way communication. Below is a sample program using popen and pclose,

#include <stdlib.h>

#include <unistd.h>

 

void createTest() {

    FILE *f;

    f = fopen("test.txt", "w");

    fprintf(f, "hello worldn");

    fclose(f);

}

 

int main(int argc, char **argv) {

    char buf[100], command[100];

    FILE *pf;

    createTest();

    sprintf(command, "cat test.txt");

    pf = popen(command, "r");   //read from cat

    while (fgets(buf, 100, pf) != NULL) {

        printf("%s", buf);

    }

    pclose(pf);

    return 0;

}

Compile the code using the command below,

gcc -o pipe3 pipe3.c

And then run the program,

./pipe3

The program should give output as “hello world”. The code creates a new process for “cat test.txt” command, and reads from the pipe stream between cat command and itself.

PIPE_BUF

Writing less than PIPE_BUF bytes is atomic. Writing more than PIPE_BUT bytes may not be atomic: the kernel may interleave the data with data written by other processes. For example, if two processes trying to write “aaaaaa” and “bbbbb” respectively to the same pipe. If the writes are atomic, the content is either “aaaaaabbbbb” or “bbbbbaaaaaa”. But if it’s not atomic, the content can be something like “aaabbaabbba”.

In Linux, PIPE_BUF is set at limits.h, and the program below can print it out.

#include <stdio.h>

#include <limits.h>

 

int main(void) {

    printf("%dn", PIPE_BUF);

    return 0;

}

On my Ubuntu Linux, the value is 4096.

Non Block Pipe

The default pipe blocks both reads and writes. A non blocking pipe can be created by fcntl with O_NONBLOCK flag. The code block below illustrates how to create a non block pipe,

#include <fcntl.h>

 

void nonblock(int fd) {

    int flags;

    if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {

        perror("F_GETFL error:");

        return;

    }

    flags |= O_NONBLOCK;

    if (fcntl(fd, F_SETFL, flags) < 0) {

        perror("F_SETFL error:");

    }

}

More details about pipes can be referred at reference 4.

References:
1. POSIX wikipedia page: http://en.wikipedia.org/wiki/POSIX
2. Understanding Linux Kernel Inter-process Communication: Pipes, FIFO & IPC: http://linux.omnipotent.net/article.php?article_id=12504&page=2
3. popen Linux man page.
4. Unix Network Programming, Volume 2.
5. http://manpages.courier-mta.org/htmlman7/pipe.7.html