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
的输入。
-
查找特定的进程
查找所有包含 “nginx” 字符串的进程
-
统计文件数量
统计当前目录下有多少个 .txt
文件
3 管道的使用、pipe 函数
3.1 管道的特质
管道 是一种基于内核缓冲区的通信机制,借助环形队列实现。其特点如下:
- 伪文件:不占用磁盘空间,仅存在于内核缓冲区。
- 数据一次性读取:一旦被读取,数据就不再存在于管道中。
- 单向流动:数据只能从一个方向流动。
- 局限性:
- 不能自己写自己读。
- 数据不可反复读取。
- 仅适用于有血缘关系的进程。
分配的内核缓冲区大小:在 Linux 系统中,管道缓冲区的大小默认是 64 KiB、在某些历史版本中,这个值可能是 4 KiB 或 8 KiB。允许管道容量系统上限 pipe-max-size。
3.2 管道的使用
管道 分为 读端和写端,需要两个文件描述符来管理管道:fd[0]
为读端,fd[1]
为写端。

管道 一般用于父子进程之间相互通信,基本用法如下:
- 父进程使用
pipe()
系统调用创建一个管道;
- 父进程使用
fork()
系统调用创建一个子进程;
- 父子进程 共享文件描述符,相当于 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]; 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) { close(fd[0]); dup2(fd[1], STDOUT_FILENO); execlp("ls", "ls", (char*)NULL);
perror("execlp ls error"); exit(1); } else if (pid > 0) { 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; } }
if (i == 0) { close(fd[0]); dup2(fd[1], STDOUT_FILENO); execlp("ls", "ls", (char*)NULL);
perror("execlp ls error"); exit(1); } else if (i == 1) { 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; } }
char* str1 = "Kirisame"; char* str2 = " Marisa"; if (i == 0) { close(fd[0]); printf("child 1 process is writing...\n"); write(fd[1], str1, strlen(str1)); close(fd[1]); } else if (i == 1) { 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; }
|
