「Linux 系统编程」进程间通信方式、管道基本使用方法

1 Linux 进程间常用通信方式

在 Linux 系统中,进程间通信(IPC,InterProcess Communication)是多进程协作的基础。常见的 IPC 方式包括:

  • 管道(Pipe):简单易用,适用于有血缘关系的进程。
  • 信号(Signal):开销小,用于异步事件通知。
  • mmap 映射:允许非血缘关系进程间通过内存映射实现通信。
  • Socket(本地套接字):稳定可靠,可用于本地或网络通信。

其中,管道是最基本和常用的一种方式。



2 Linux 中的管道命令

管道(Pipe),符号为 |,是一种将一个命令的标准输出(stdout) 直接连接到另一个命令的标准输入(stdin) 的机制。

可以想象成一根真实的管道:第一个命令产生的数据像水一样从管道一端流入,第二个命令则在管道的另一端立即接收并处理这些数据。

2.1 管道的核心特点

  • 单向流动:数据 只能从左向右流动,即从前一个命令流向后一个命令。

  • 内存操作数据的传输发生在内存中,不需要经过磁盘上的临时文件,因此效率极高。

  • 顺序处理:后一个命令会等待前一个命令输出数据,并一行一行(或一块一块)地立即处理,而不是等待前一个命令全部执行完。

  • 匿名性:管道本身没有名字,只在创建它的 shell 会话中存在,命令执行完毕后就消失了。


2.2 Linux 管道命令简单示例

基本语法

1
command_A [arguments] | command_B [arguments]

command_A 的输出会作为 command_B 的输入。

  1. 查找特定的进程

    查找所有包含 “nginx” 字符串的进程

    1
    ps aux | grep nginx

  1. 统计文件数量

    统计当前目录下有多少个 .txt 文件

    1
    ls -l *.txt | wc -l


3 管道的使用、pipe 函数

3.1 管道的特质

管道 是一种基于内核缓冲区的通信机制,借助环形队列实现。其特点如下:

  • 伪文件:不占用磁盘空间,仅存在于内核缓冲区。
  • 数据一次性读取:一旦被读取,数据就不再存在于管道中。
  • 单向流动:数据只能从一个方向流动。
  • 局限性
    • 不能自己写自己读。
    • 数据不可反复读取。
    • 仅适用于有血缘关系的进程。

分配的内核缓冲区大小:在 Linux 系统中,管道缓冲区的大小默认是 64 KiB、在某些历史版本中,这个值可能是 4 KiB 或 8 KiB。允许管道容量系统上限 pipe-max-size。


3.2 管道的使用

管道 分为 读端和写端,需要两个文件描述符来管理管道:fd[0] 为读端,fd[1] 为写端。

管道 一般用于父子进程之间相互通信,基本用法如下:

  1. 父进程使用 pipe() 系统调用创建一个管道;
  2. 父进程使用 fork() 系统调用创建一个子进程;
  3. 父子进程 共享文件描述符,相当于 2个 fd 指向同一块内存空间。因此父子进程可以通过新创建的管道进行通信。

3.3 pipe 函数

1
2
3
#include <unistd.h>

int pipe(int fd[2]);
  • fd[0]:读端
  • fd[1]:写端
  • 返回值:成功返回 0;失败返回 -1 并设置 errno

3.4 pipe 父子进程通信示例

在下面的示例中,子进程负责从写端写入数据到管道中,父进程负责从读端读取子进程写入的数据。

在写端打开但子进程还会写入数据时,父进程读管道会 阻塞等待,直到子进程写入数据到管道中。

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {
int fd[2]; // fd[0] 读,fd[1] 写
pid_t pid;
char buf[1024];
char* str = "Hello, Marisa\n";

// 父进程创建管道
if (pipe(fd) == -1) {
perror("pipe error");
exit(1);
}

pid = fork(); // 创建子进程

// 子进程写,父进程读
if (pid == 0) {
// 子进程写,需要先关闭子进程的读端
close(fd[0]);
// 子进程的写操作
printf("child process is writing...\n");
write(fd[1], str, strlen(str));
close(fd[1]);
} else if (pid > 0){
// 父进程读,需要先关闭父进程的写端
close(fd[1]);
// 父进程的读操作
int bytes = read(fd[0], buf, sizeof(buf));
printf("parent process read %d bytes: %s\n", bytes, buf);
close(fd[0]);
}

return 0;
}


3.5 pipe 管道读写行为

读管道

  • 有数据:返回实际读取的字节数
  • 无数据
    • 无写端打开:返回 0(类似于读到文件末尾没有数据的情况)
    • 有写端打开:读取管道的进程会 阻塞等待

写管道

  • 无读端打开:触发 SIGPIPE,进程异常终止
  • 有读端打开
    • 管道数据已满:阻塞等待,直到管道有数据被读取,可以继续写入数据
    • 管道数据未满:返回写入的字节数


4 使用 pipe 管道实现父子进程 ls | wc -l

实现的命令为 ls | wc -l,用于统计并显示当前目录下文件的数量。

可以创建父子进程,其中:

  • 子进程负责执行 ls 命令(使用 execlp 函数)。

    ls 命令会将当前目录下的所有文件名称打印到标准输出(STDOUT_FILENO),因此需要 借助 dup2 函数将标准输出(STDOUT_FILENO)重定向到管道的写端 fd[1]

  • 父进程负责执行 wc -l 命令(使用 execlp 函数)

    wc -l 命令原本是从标准输入(STDIN_FILENO)中读取数据然后统计数据的行数,因此需要 借助 dup2 函数将标准输入(STDIN_FILENO)重定向到管道的读端 fd[0]

最终实现如下:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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

if (pipe(fd) == -1) {
perror("pipe error");
exit(1);
}

pid = fork(); // 创建子进程

if (pid == 0) {
// 子进程负责执行 ls,先将标准输出重定向到写端
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", (char*)NULL);

perror("execlp ls error");
exit(1);
} else if (pid > 0) {
// 父进程负责执行 wc -l,先将标准输入重定向到读端
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("wc", "wc", "-l", (char*)NULL);

perror("execlp wc -l error");
exit(1);
} else if (pid == -1) {
perror("fork error");
exit(1);
}

return 0;
}



5 使用 pipe 管道实现兄弟进程间通信

同样可以以实现 ls | wc -l 命令为例,通过兄弟两个子进程之间管道通信来实现这一命令。

父进程循环创建 2 个子进程,第 1 个子进程负责执行 ls 命令,将 ls 命令输出的数据写入到管道;第 2 个子进程负责执行 wc -l 命令,从管道中读取数据,作为输入传给 wc -l 命令。最终输出结果。

父进程需要关闭读写端,并且需要 等待 2 个子进程执行结束 才退出。

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
int fd[2];
pid_t pid;
int i;

if (pipe(fd) == -1) {
perror("pipe error");
exit(1);
}

for (i = 0; i < 2; i ++ ) {
pid = fork(); // 创建子进程
if (pid == -1) {
perror("fork error");
exit(1);
}
if (pid == 0) {
break; // 子进程退出,不继续 fork
}
}


if (i == 0) {
// 第 1 个子进程负责执行 ls,先将标准输出重定向到写端
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", (char*)NULL);

perror("execlp ls error");
exit(1);
} else if (i == 1) {
// 第 2 个进程负责执行 wc -l,先将标准输入重定向到读端
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("wc", "wc", "-l", (char*)NULL);

perror("execlp wc -l error");
exit(1);
} else if(pid > 0) {
// 父进程需要关闭读写端,还需要等待两个子进程执行结束
close(fd[0]);
close(fd[1]);
for (i = 0; i < 2; i ++ ){
wait(NULL);
}
} else if (pid == -1) {
perror("fork error");
exit(1);
}

return 0;
}



6 使用 pipe 实现多个读写端操作

一个 pipe 有一个写端多个读端,或者有多个写端一个读端,都是允许的。

示例:父进程读,两个子进程写

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

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

if (pipe(fd) == -1) {
perror("pipe error");
exit(1);
}

for (i = 0; i < 2; i ++ ) {
pid = fork(); // 创建子进程
if (pid == -1) {
perror("fork error");
exit(1);
}
if (pid == 0) {
break; // 子进程退出,不继续 fork
}
}

char* str1 = "Kirisame";
char* str2 = " Marisa";

if (i == 0) {
// 第 1 个子进程写数据
close(fd[0]);
printf("child 1 process is writing...\n");
write(fd[1], str1, strlen(str1));
close(fd[1]);
} else if (i == 1) {
// 第 2 个进程也写数据
close(fd[0]);
printf("child 2 process is writing...\n");
write(fd[1], str2, strlen(str2));
close(fd[1]);
} else if(pid > 0) {
// 父进程读数据
close(fd[1]);
for (i = 0; i < 2; i ++ ) {
wait(NULL); // 等待两个子进程都写完
}
int bytes = read(fd[0], buf, sizeof(buf));
printf("parent process read %d bytes: %s\n", bytes, buf);
close(fd[0]);
} else if (pid == -1) {
perror("fork error");
exit(1);
}

return 0;
}


「Linux 系统编程」进程间通信方式、管道基本使用方法
https://marisamagic.github.io/2025/09/04/20250904/
作者
MarisaMagic
发布于
2025年9月4日
许可协议