Linux- pipe() system call

pipeline

Pipes are a simple and effective way for inter-process communication (IPC). In UNIX and UNIX-like operating systems (such as Linux), pipes provide a mechanism for one process to send its output to the input of another process. Pipes are typically used for one-way transmission of data streams.

Under the hood, a pipe is actually a buffer maintained by the operating system kernel. One process writes data to one end of the pipe (the write end), while another process can read data from the other end of the pipe (the read end).

The basic idea of ​​pipeline implementation

  1. Buffer management : The kernel maintains a buffer that is used to store data written to the pipe.
  2. Synchronization and mutual exclusion : The kernel also provides synchronization and mutual exclusion mechanisms to ensure that data can be safely transferred from one process to another.
  3. File descriptor : From a user-mode perspective, a pipe is actually a pair of file descriptors, one of which is used for reading and the other for writing.

pipe()

In UNIX-like operating systems, pipe()system calls are used to create a new pipe, an IPC (Inter-Process Communication) mechanism that allows one-way data transfer between two processes. One process writes to one end of the pipe, while another process reads from the other end of the pipe.

function prototype

Pipes are created via the following function prototype:

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

parameter

  • pipefd: This is an intarray containing two elements. When the function call is successful, pipefd[0]it will become the file descriptor of the read end (read end) of the pipe, and pipefd[1]it will become the file descriptor of the write end (write end) of the pipe.

return value

  • Success: Returns 0.
  • Failure: Return -1 and set errno.

working principle

  1. Data flow direction : data pipefd[1]flows from (writing end) to pipefd[0](reading end).

  2. Blocking and non-blocking : pipe()Usually blocking. In other words, the read operation will block until data is written; the write operation will also block until the reader reads the data.

  3. Data buffering : Data is first written to the kernel buffer and then read from the buffer by read operations.

  4. Inheritance of file descriptors : pipe()The created file descriptors can fork()be later inherited by the child process, which makes it pipe()very suitable for communication between parent-child processes or sibling processes.

  5. Closing rules : When the write end is closed, any attempt to read from the read end will return immediately, and the read data length is 0 (indicating EOF). When the read side is closed, any attempt to write to the write side will cause SIGPIPEa signal to be sent.

Application scenarios

Pipes are often used in multi-process applications, such as the pipe operator in shell commands |, which allows the output of one command to be used as the input of another command.

To sum up, pipe()it is a low-level IPC mechanism for transferring data between two processes simply and efficiently. However, it has limitations, such as only one-way communication, and is usually used between processes with a common ancestor.

Example

The following C program example demonstrates how to use the pipe(), fork()and read()/write()functions to implement simple inter-process communication. In this example, the parent process writes a string to the pipe, and the child process reads and prints the string from the pipe.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    
    
    int pipefd[2];
    pid_t pid;

    // 创建管道
    if (pipe(pipefd) == -1) {
    
    
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 创建子进程
    pid = fork();
    if (pid == -1) {
    
    
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
    
      // 子进程
        char buffer[128];
        close(pipefd[1]);  // 关闭不需要的写端

        // 从管道读取数据
        read(pipefd[0], buffer, sizeof(buffer));
        printf("Child read: %s\n", buffer);

        close(pipefd[0]);  // 关闭读端
        exit(EXIT_SUCCESS);
    } else {
    
      // 父进程
        const char *msg = "Hello, Pipe!";
        close(pipefd[0]);  // 关闭不需要的读端

        // 写入数据到管道
        write(pipefd[1], msg, strlen(msg) + 1);
        printf("Parent wrote: %s\n", msg);

        close(pipefd[1]);  // 关闭写端

        // 等待子进程结束
        wait(NULL);
    }

    return 0;
}

Code analysis

  1. Create Pipe : Use to pipe(pipefd)create a new pipe. After success, pipefd[0]it is the reading end and pipefd[1]the writing end.

  2. Create child process : Use to fork()create a new process.

  3. Parent process :

    • Close the reading side as the parent process only needs to write.
    • Use write()to write a string to a pipe.
    • Close the write side.
  4. Child process :

    • Close the write side because the child process only needs to read.
    • Use read()to read data from the pipe.
    • Print the read data.
    • Close the read end.
  5. Parent process waits : The parent process waits for wait(NULL)the child process to end.

This is a simple pipe()example of inter-process communication. Run this program and you should see output similar to the following:

Parent wrote: Hello, Pipe!
Child read: Hello, Pipe!

This indicates that the data written by the parent process was successfully read by the child process.


[ Note ] When a process (parent process) calls to fork()create a child process, the child process will inherit the file descriptor table of the parent process. This means that the value of pipefd[0]and pipefd[1]in the child process will be the same as the value in the parent process, they point to the same pipe.

Specifically, the parent and child processes will have file descriptors pointing to the same kernel pipe object. This allows parent and child processes to communicate through this pipe.

Because the child process inherits the file descriptor of the parent process, so:

  • In the child process, pipefd[0]it is still the read end of the pipe.
  • In the child process, pipefd[1]it is still the write end of the pipe.

fork()This is why you usually see some close()calls after creating a pipe and afterwards: each process usually only needs one end of the pipe, so the unneeded end is closed. Doing so helps avoid potential deadlocks and resource leaks.

For example, in the sample code above:

  • The child process has the write side closed ( close(pipefd[1]);) because it is only reading from the pipe.
  • The parent process has closed the read side ( close(pipefd[0]);) because it is only writing data to the pipe.

read()This approach makes the pipeline easier to manage and ensures that related operations (such as and write()) return correctly when all writes or reads are closed .

Guess you like

Origin blog.csdn.net/weixin_43844521/article/details/133134736