linux系统中进程通信之管道
一、进程间通信概述
进程间通信简介
Linux 下的进程间通信方式基本上继承自 UNIX 平台。
在发展历史上,AT&T 贝尔实验室和**加州大学伯克利分校(UCB)**是两大贡献者:
AT&T:主要改进和扩展 UNIX 早期的通信方式,形成了 System V IPC。
UCB(伯克利):突破单机限制,引入了 Socket 通信机制。
目前 Linux 同时继承了这两种方式的优点。
进程间通信分类和特点
1.管道(Pipe / FIFO)
无名管道:用于具有亲缘关系(父子进程)的通信。
命名管道(FIFO):既可用于有亲缘关系进程,也可用于无亲缘关系进程。
特点:适合一对一通信,数据以字节流方式传输。
2.信号(Signal)
特点:异步通信机制,软件层模拟中断,用于通知进程某个事件的发生(类似 STM32 中断)。
应用:进程控制,如暂停、终止、继续运行等。
3.消息队列(Message Queue)
特点:内核维护消息链表,支持多个消息存放,允许有序、按权限的读写操作。
优势:克服了管道不便管理的问题,数据更灵活。
4.共享内存(Shared Memory)
特点:多个进程可直接访问同一块内存空间,数据传输效率最高。
注意:需要配合信号量/互斥锁实现同步,避免数据冲突。
应用:大数据量交互。
5.信号量(Semaphore)
特点:不用于传输数据,主要用于进程/线程间的同步与互斥(类似锁机制)。
应用:控制共享资源访问,避免竞争条件。
6.套接字(Socket)
特点:最通用的 IPC 机制,可用于本机进程通信,也支持不同机器之间的网络通信。
应用:分布式系统、客户端-服务器模型。
二、无名管道
无名管道的概述
无名管道(pipe)是最早的进程间通信方式之一,主要用于具有亲缘关系的进程(父子进程)之间。
管道的基本概念
管道可以类比为一个“水管”,只有 一个入口(写端) 和 一个出口(读端)。
数据从写端写入,从读端读出,遵循 先进先出(FIFO) 原则。
管道本质上是由 内核创建并维护的一段缓存(内存空间)。
特点
1.亲缘关系限制
无名管道只能用于 父子进程 或 兄弟进程 之间通信。
这是因为
pipe()创建的文件描述符是通过 fork() 继承给子进程的。
2.半双工通信
无名管道是 单向传输,即固定一端写、一端读。
若要实现双向通信,需要建立两条管道。
3.存在于内存的特殊文件
无名管道由内核创建,仅存在于 内存 中,不会出现在文件系统里。
进程通过 read/write 系统调用对其进行读写操作。
4.没有文件名和文件节点
与命名管道(FIFO)不同,无名管道没有路径名,不能通过路径访问,只能通过文件描述符使用。
5.阻塞特性
读阻塞:如果管道中没有数据,读操作会阻塞,直到有数据写入。
写阻塞:管道有缓存区(通常 4KB/64KB),当缓存写满时,写操作会阻塞,直到有数据被读出。
6.先进先出(FIFO)队列结构
管道内部数据遵循 先进先出(First In First Out)规则。
一旦数据被读取,就会从管道中移除,不会重复读取。
使用场景
1. 单进程内读写(自我通信,意义不大)
对应第一张图:
进程 A 自己写入数据,再自己读取数据。
实际开发中这种方式很少用,一般用于测试。
2. 父子进程之间通信
对应第二张图:
父进程和子进程通过
pipe()系统调用共享同一条管道。常见模式:
父进程写,子进程读。
或者子进程写,父进程读。
📌 总结一句话:
无名管道就是一个由内核维护的缓存区,适合父子进程之间通过“写端写、读端读”的方式进行单向通信。
无名管道的相关函数
pipe() —— 创建无名管道
头文件
#include <unistd.h>函数原型
int pipe(int pipefd[2]);参数说明
pipefd[0]→ 管道的 读端口(用于读取数据)。pipefd[1]→ 管道的 写端口(用于写入数据)。
返回值
0:成功创建管道。-1:失败(errno 被设置,常见错误是内存不足或进程文件描述符用尽)。
功能
在内核中创建一条无名管道。
通过
pipefd数组返回两个文件描述符,进程可以用它们进行read()和write()操作。
注意事项
通常先调用
pipe()创建管道,再调用fork()创建子进程。这样父子进程就能通过同一条管道通信。
无名管道的使用实例
父进程写,子进程读
#include <unistd.h>   // pipe, fork, read, write, close
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit
#include <string.h>   // strlenint main() {int fd[2];   // fd[0] 读端, fd[1] 写端pid_t pid;char buf[100];// 第一步:创建无名管道if (pipe(fd) == -1) {perror("pipe error");exit(1);}// 第二步:创建子进程pid = fork();if (pid < 0) {perror("fork error");exit(1);}if (pid > 0) {  // 父进程:写数据close(fd[0]);  // 父进程不用读端,关闭const char *msg = "Hello from parent!";write(fd[1], msg, strlen(msg) + 1);  // 写入管道close(fd[1]);  // 写完关闭写端} else {  // 子进程:读数据close(fd[1]);  // 子进程不用写端,关闭read(fd[0], buf, sizeof(buf));   // 从管道读数据printf("子进程读到的数据: %s\n", buf);close(fd[0]);  // 读完关闭读端}return 0;
}
现象:

子进程写,父进程读
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>int main() {int fd[2];          // fd[0]读端,fd[1]写端pid_t pid;char buf[100];// 创建无名管道if (pipe(fd) == -1) {perror("pipe error");exit(1);}// 创建子进程pid = fork();if (pid < 0) {perror("fork error");exit(1);}if (pid == 0) {// 子进程:写数据close(fd[0]);   // 关闭读端char *msg = "Hello, father! This is child.\n";write(fd[1], msg, strlen(msg)+1); // 写数据close(fd[1]);   // 写完关闭写端exit(0);} else {// 父进程:读数据close(fd[1]);   // 关闭写端read(fd[0], buf, sizeof(buf)); // 读取数据printf("父进程读取到的数据: %s\n", buf);close(fd[0]);   // 关闭读端wait(NULL);     // 等待子进程退出}return 0;
}
现象:

注意事项
通常先调用
pipe()创建管道,再调用fork()创建子进程。这样父子进程就能通过同一条管道通信。
父进程和子进程需分别关闭不用的端口(父关读端,子关写端)。
三、命名管道
命名管道的概述
命名管道(Named Pipe / FIFO)
命名管道是一种 特殊的文件类型,和普通文件不同,它的数据传输是 先进先出(FIFO) 的。它在文件系统中以 文件节点的形式存在(即有名字),因此可以通过路径来找到它,并用
open()打开。命名管道允许 没有亲缘关系的进程之间 进行通信(这是与无名管道的重要区别)。
命名管道的特点
1.FIFO 文件不是普通文件
它是特殊文件(FIFO 文件)。
文件节点存在于文件系统中,但管道的数据存储在内存中,不占用磁盘空间。
因此,FIFO 文件大小始终显示为
0。
2.操作方式
不是普通文件,只能通过 文件 I/O 函数(如
open、read、write、close)进行操作。
3.适用范围
可用于 任意两个进程之间 的通信(不仅限于父子进程)。
4.文件名和文件节点
命名管道有文件名(路径名),需要先在文件系统中创建(例如通过
mkfifo)。进程通过该文件名找到管道并进行通信。
5.阻塞特性
读阻塞:如果管道中没有数据,读操作会阻塞,直到有数据写入。
写阻塞:管道有一定的缓存区,如果写满了,写操作会阻塞,直到有空间可用。
6.先进先出(FIFO)
管道内部遵循队列规则,数据按照写入顺序排队,先写入的先读出。
7.打开方式
以
O_RDONLY打开时,默认作为 读端。以
O_WRONLY打开时,默认作为 写端。
命名管道的相关函数
1.头文件 
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
2. 函数原型 
int mkfifo(const char *pathname, mode_t mode);
3. 参数
pathname:要创建的 FIFO 文件的路径名(字符串)。
mode:指定 FIFO 文件的权限(类似
open、chmod,用八进制数表示,比如0666)。高位会受
umask影响。常用:
0666(读写权限),0777(读写执行权限)。
4. 返回值
成功:返回
0。失败:返回
-1,并设置errno(比如EEXIST表示文件已存在)。
5. 示例代码
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main() {const char *fifo_path = "myfifo";// 创建命名管道,权限0666if (mkfifo(fifo_path, 0666) == -1) {perror("mkfifo error");exit(EXIT_FAILURE);}printf("FIFO %s created successfully.\n", fifo_path);return 0;
}
命名管道的使用实例
单进程命名管道读写
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#define FIFO_PATH "myfifo.pipe"int main() {char buf[128] = {0};// 创建命名管道if (mkfifo(FIFO_PATH, 0666) == 0) {printf("命名管道 %s 创建成功\n", FIFO_PATH);}// 打开管道读写端int fd = open(FIFO_PATH, O_RDWR);if (fd == -1) {printf("打开读写端失败!\n");exit(1);}printf("%s 打开读写端成功\n", FIFO_PATH);// 往管道写数据char *msg = "Hello World";if (write(fd, msg, strlen(msg)) > 0) {printf("向 %s 成功写入: %s\n", FIFO_PATH, msg);}// 读出数据int n = read(fd, buf, sizeof(buf));if (n > 0) {printf("读取 %d 字节数据: %s\n", n, buf);} else {printf("读取失败或无数据\n");}close(fd);//删除管道remove(FIFO_PATH);return 0;}现象:
父子进程一次对话
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#define FIFO_PATH "myfifo.pipe"int main() {char buf[128] = {0};// 创建命名管道if (mkfifo(FIFO_PATH, 0666) == 0) {printf("命名管道 %s 创建成功\n", FIFO_PATH);}//创建子进程pid_t pid = fork();if(pid < 0) {printf("子进程创建失败\r\n");}else if(pid > 0) {int fd = open(FIFO_PATH, O_RDWR);if (fd == -1) {printf("父进程打开读写端失败!\n");exit(1);}printf("父进程打开读写端成功\n");fgets(buf, sizeof(buf), stdin);write(fd, buf, strlen(buf));memset(buf, 0, sizeof(buf));usleep(1000);int n = read(fd, buf, sizeof(buf)-1);if (n > 0) {buf[n] = '\0';printf("父进程读到的数据为: %s\n", buf);
}memset(buf, 0, sizeof(buf));}else {int fd = open(FIFO_PATH, O_RDWR);if (fd == -1) {printf("子进程打开读写端失败!\n");exit(1);}printf("子进程打开读写端成功\n");int n = read(fd, buf, sizeof(buf)-1);if (n > 0) {buf[n] = '\0';printf("子进程读到的数据为: %s\n", buf);memset(buf, 0, sizeof(buf));usleep(1000);fgets(buf, sizeof(buf), stdin);write(fd, buf, strlen(buf));memset(buf, 0, sizeof(buf));}return 0;}
}现象:

非亲缘关系进程通信
代码
A进程:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#define FIFO_PATH "process.pipe"int main() {char buf[128] = {0};if (mkfifo(FIFO_PATH, 0666) == 0) {printf("命名管道 %s 创建成功\n", FIFO_PATH);}int fd = open(FIFO_PATH, O_RDWR);if (fd == -1) {printf("A进程打开读写端失败!\n");exit(1);}printf("A父进程打开读写端成功\n");fgets(buf, sizeof(buf), stdin);write(fd, buf, strlen(buf));return 0;
}B进程:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#define FIFO_PATH "process.pipe"int main() {char buf[128] = {0};int fd = open(FIFO_PATH, O_RDWR);if (fd == -1) {printf("B进程打开读写端失败!\n");exit(1);}printf("B父进程打开读写端成功\n");int n = read(fd, buf, sizeof(buf)-1);if (n > 0) {buf[n] = '\0';printf("B进程读到的数据为: %s\n", buf);}}现象:
