linux管道(pipe)系统调用

如何使用 C++ 在 Linux 环境下进行管道(pipe)系统调用

pipe() 系统调用

在 Linux 系统编程中,pipe() 是一个系统调用,用于创建一个匿名管道,实现同一进程或父子进程间的单向通信。管道有两个文件描述符:一个用于读取(读端),一个用于写入(写端)。

pipe() 函数原型

1
2
#include <unistd.h>
int pipe(int pipefd[2]);
  • pipefd是一个包含两个整数的数组,用于存储管道的文件描述符

    • pipefd[0]:管道的读端,用于读取数据
    • pipefd[1]:管道的写端,用于写入数据
  • 返回值:成功返回 0,失败返回 -1 并设置 errno

使用步骤

  1. 创建管道:调用 pipe() 函数

  2. 创建子进程:使用 fork() 创建子进程(管道通常用于父子进程间通信)

  3. 关闭不需要的端:

    • 父进程关闭读端(pipefd[0]),只写
    • 子进程关闭写端(pipefd[1]),只读(或根据需求调整,如父进程读、子进程写)
  4. 通信:通过 write() 写入数据,read() 读取数据

  5. 关闭管道:通信完成后关闭所有文件描述符

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <cstring>
#include <sys/wait.h>
#include <string>

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

// 1. 创建管道
if (pipe(pipefd) == -1) {
std::cerr << "管道创建失败: " << std::strerror(errno) << std::endl;
std::exit(EXIT_FAILURE);
}

// 2. 创建子进程
pid = fork();
if (pid == -1) {
std::cerr << "进程创建失败: " << std::strerror(errno) << std::endl;
std::exit(EXIT_FAILURE);
}

if (pid == 0) { // 子进程:读数据
// 3. 关闭子进程不需要的写端
close(pipefd[1]);

// 4. 从管道读取数据
ssize_t n = read(pipefd[0], buf, sizeof(buf) - 1);
if (n == -1) {
std::cerr << "读取失败: " << std::strerror(errno) << std::endl;
std::exit(EXIT_FAILURE);
}
buf[n] = '\0'; // 添加字符串结束符
std::cout << "子进程收到: " << buf << std::endl;

// 5. 关闭读端
close(pipefd[0]);
std::exit(EXIT_SUCCESS);
} else { // 父进程:写数据
// 3. 关闭父进程不需要的读端
close(pipefd[0]);

// 4. 向管道写入数据
std::string msg = "Hello from parent!";
if (write(pipefd[1], msg.c_str(), msg.length()) == -1) {
std::cerr << "写入失败: " << std::strerror(errno) << std::endl;
std::exit(EXIT_FAILURE);
}

// 5. 关闭写端(触发子进程的read()返回0)
close(pipefd[1]);

// 等待子进程结束
wait(NULL);
std::exit(EXIT_SUCCESS);
}
}

代码说明

  1. 管道创建pipe(pipefd) 成功后,pipefd[0]pipefd[1] 分别代表读端和写端

  2. 进程通信

    • 父进程通过 write(pipefd[1], ...) 向管道写入数据
    • 子进程通过 read(pipefd[0], ...) 从管道读取数据
  3. 关闭端的重要性

    • 不使用的端必须关闭,否则可能导致 read() 阻塞(等待数据)
    • 所有写端关闭后,read() 会返回 0(表示数据结束)
  4. 单向性:管道是单向的,数据只能从写端流向读端

核心原则:按需保留,及时关闭,全量清理

  1. “先关闭,后通信” 的原则:进程创建后(如 fork() 后),应立即关闭不需要的端,再进行读写操作。例如:

    • 父进程若负责写数据,应先关闭读端(pipefd[0]),仅保留写端(pipefd[1]);
    • 子进程若负责读数据,应先关闭写端(pipefd[1]),仅保留读端(pipefd[0])。避免因 “未及时关闭” 导致的意外数据交互(如子进程误写数据到本应关闭的写端)。
  2. 写端关闭的 “触发信号” 作用:写端完成数据发送后,必须主动关闭写端,这是告知读端 “数据已发送完毕” 的唯一方式。读端通过 read() 返回 0 感知 “所有写端已关闭”,从而正常退出读取逻辑。

    • 反例:若父进程写完数据后未关闭写端,子进程的 read() 会一直阻塞(等待更多数据),导致子进程无法退出。
  3. 多进程协作时的 “全关闭” 检查:若多个进程共享同一管道的写端(如父进程和多个子进程都向管道写数据),必须确保所有写端都关闭后,读端才能收到 EOF(read() 返回 0)

    • 需通过进程同步(如 waitpid)确保所有写进程都已关闭写端,避免读端提前退出或阻塞。