in C/C++, Linux

Introduction To Pipe In Multi-process

When we design a program with multi-processing,  we usually want processes to communicate with each other to finish a task. For example,in a program we may want process 1 to read a file from the system and do some operation, then send the the edited file directly to process 2 to do other operation.  

The technic we use here for inter-process communication is pipe. If we type "man pipe" in linux terminal, we can see its definition:

A unidirectional data channel that can be used for interprocess communication.
 
A pipe contains two ends: one for reading and the other one for writing

We can create pipe by using the following function in Linux:
int fd[2];
if (pipe(fd12) == 0)
    {
      fprintf(stderr, "pipe create error");
    }


we first declare a file descriptor array( integer type) with two elements and then send it to the function pipe(). The fd[0] will be assigned as the read end of the pipe and fd[1] will be assigned as the write end of the pipe.



When we call the pipe(fd[2]) function, the system will create a pipeline like this:




Then we use the fork() system call to create a process and the pipe line will be duplicated:


  
Notice that in the code blew, we close the reading or writing end in each process:

 

int fd[2];   // pipe endpoints
pid_t child_pid;

if (pipe(fd) == 0)
{
    fprintf(stderr, "pipe create error");
}

if ((child_pid = fork()) == 0)
{
    fprintf(stderr, "fork error");
}
else if (child_pid == 0) // this is the parent process( process 1)
{
    close(fd[0]); // close reading end of the pipe
    process_1(argc, argv, fd[1]); // write to fd[1]
if (waitpid(child_pid, NULL, 0) == 0) // wait for child
{
    err_sys("waitpid error"); }
}
else // this is the child process( process 2)
{
    close(fd[1]); // close writing end of the pipe
    process_2(argc, argv, fd[0]); // read from fd[0]
}


It is a good practice to close the unused ends after fork() and close all ends after read/write operation.If you do not close the pipe ends it may frozen your program in some situation. 
In our example, process 1 only use the write end to send data to process 2 and process 2 only use the read end to get data from process 1.So for a good programming style, we should close fd[0] in process 1 and close fd[1] in process 2.



The following code shows you the problem that do not close the close the pipe end:

void process_1(int argc, char *argv[], int fd)
{
  /* If you want to use read() and write(), this works:
   *
   * write(fd, "hello world \n", 12);
   */
FILE *fp = fdopen(fd, "w"); // use fp as if it had come from fopen()
FILE *file;
char *filename = "x.txt";
int temp_int;
int size = 0;

if (fp == NULL)
{ err_sys("fdopen(w) error"); }

//open file in read mode
file = fopen(filename, "r");

// The following is so we don't need to call fflush(fp) after each fprintf().
// The default for a pipe-opened stream is full buffering, so we switch to line
// buffering.
// But, we need to be careful not to exceed BUFFER_SIZE characters per output
// line, including the newline and null terminator.
static char buffer[BUFFER_SIZE]; // off the stack, always allocated
int ret = setvbuf(fp, buffer, _IOLBF, BUFFER_SIZE); // set fp to line-buffering
if (ret != 0)
{ err_sys("setvbuf error (parent)"); }

//get the char from file and write it to the pipe
while( (temp_int = fgetc(file)) != EOF ){
   fprintf((FILE*)fp, "%c", temp_int);
   size ++;
}
//fflush(fp);
fprintf(stdout, "P1: file %s, bytes %d \n", filename, size);
fclose(file);
}
void process_2(int argc, char *argv[], int fd)
{
  FILE *fp = fdopen(fd, "r");
if (fp == NULL)
{ err_sys("fdopen(r) error"); }

static char buffer[BUFFER_SIZE];
int ret = setvbuf(fp, buffer, _IOLBF, BUFFER_SIZE);
if (ret != 0)
{ err_sys("setvbuf error (child)"); }
//if not input any -m option

char line[BUFFER_SIZE];

while( fgets(line, BUFFER_SIZE,fp) != NULL){
fprintf(stdout,"%s",line);
}

//fflush(fp);
fclose( fp);
}


When you run it, the program will get stuck at function fgets( line, BUFFER_SIZE,fp_r) because we do not close the writing end of the pipe in process 1 and fgets will go into a infinite loop to wait the EOF. 

The solution is quiet simple: just adding the following line at the end of process 1:

fclose(fp); //close the end point

Write a Comment

Comment