1. An overview of the

1.1 A pipe is a byte stream:

  • With pipes, there are no messages or message boundaries, meaning that the reading process can read blocks of any size, regardless of the block size that the writing process writes to the pipe.
  • The data passed by the pipe is sequential, that is, bytes are read from the pipe in the same order as they are written to the pipe.

1.2 Reading data from pipes:

  • A pipe with empty read data will block until at least one byte has been written to the pipe.
  • If the write end of the pipe is not closed, reading data will be blocked until the write end of all pipes is closed.
  • The write end of the pipe is closed, and the process can read any block of data until it has read all the data in the pipe: read() returns 0.

1.3 The pipe is one-way:

  • The direction of data transfer in the pipe is one-way, with one end of the pipe used for writing and the other for reading.

1.4 Pipe capacity is limited:

  • A pipe is a buffer maintained in kernel memory that has limited storage capacity.
  • After the pipe is filled, subsequent writes to the pipe are blocked until a reading process reads the pipe.

1.5 Operations that can be guaranteed to write no more than PIPE_BUF bytes are atomic:

  • If multiple processes write to the same pipe, then if they write no more than PIPE_BUF bytes at any one time, you can be sure that the data written will not get mixed up.
  • If a block of data is written larger than a PIPE_BUF byte, the kernel splits the data into smaller pieces and transfers them, attaching subsequent data as the reader consumes it from the pipe. (The write() call blocks until all data is written to the pipe), so when multiple writing processes write to a large chunk of data, data may cross.

2. Create and use pipes

#include<unistd.h>int pipe(int filedes[2]); //return 0 on success,or -1 on error
Copy the code
  • Filedes [0] represents the reading end of the pipeline;

  • Filedes [1] represents the writing end of the pipeline;

  • Use read() and write() system calls to perform I/O on the pipe.

3. Close the unused pipe file descriptor

3.1 Reasons why the reading process needs to close the pipe write descriptor it holds:

Read () blocks until all pipe write descriptors are closed.

3.2 The reason why the writing process needs to close the pipe read descriptor it holds:

When a process attempts to write to a pipe but no process has an open read descriptor for the pipe, the kernel sends the writing process a SIGPIPE signal (which kills the process by default), which the process can capture or ignore, and the write operation returns an EPIPE error. Receiving a SIGPIPE signal or an EPIPE error can tell you the status of the pipe, avoiding the risk that writes will be blocked, as writing without reading will fill the pipe and subsequent write requests will be blocked.

3.3 Reasons for closing unused pipe file descriptor:

The pipeline file descriptors are closed for all processes before the pipeline is destroyed and the resources it occupies are freed for reuse by other processes. At the same time, the data in the pipe is lost.

4. Pipes can be used as a way to synchronize processes

The parent builds a pipe before creating the child. Each child process inherits the file descriptors on the writing side of the pipe and closes them after completing the action. When all child processes close the file descriptor on the write side of the pipe, the parent process’s read() on the pipe terminates and returns end-of-file (0). At this point, the parent process can do other work. (The parent must close the writing end of the pipe or it will block forever.)

   switch (fork()) {
        case -1:
                ERR_EXIT("fork");
        caseZero:if (close(pfd[0]) == -1)
                    ERR_EXIT("close");
                
                //Child does some work,and lets parent know It‘s done
                
                if(close(pfd[1])==-1)
                    ERR_EXIT("close");

                //child now carries on to do other things...
               
                _exit(EXIT_SUCCESS);
        default:
                break;
    }

    if(close(pfd[1])==-1)
        ERR_EXIT("close");
    //parent may do other work,then synchronizes with children

    if(read(pfd[0],&dummy,1)! =0)//block ERR_EXIT("read");
 
Copy the code

5. Pipe to shell command communication: popen()

#include<stdio.h>
FIFE *popen(const char *command,const char *mode);
    //return file stream,or NULL on error
int pclose(FILE *stream);
    //return termination status of child process,or -1 on error
Copy the code

The popen() function creates a pipe, and then a child process is created to execute the shell, which in turn creates a child process to execute the command string. The mode parameter is a string that determines whether the calling process is reading data from the pipe (mode is r) or writing data to the pipe (mode is W).

The value of mode determines whether the standard output of the command executed is connected to the writing end of the pipe or its standard input to the reading end of the pipe.

Calling process fp – (fork (), exec () – > / bin/sh – (fork (), exec () – > command (stdout) – (pipe) – > fp call process

a)mode is r

Calling process fp – (fork (), exec () – > / bin/sh – (fork (), exec () – > command (stdin)

Call the process fp–(pipe)–> command stdin

b) mode is w