前言
在 Linux IPC(进程间通信)体系中,匿名管道解决了亲缘进程间的通信问题,但存在核心限制——仅支持具有共同祖先的进程通信。而命名管道(FIFO)作为匿名管道的扩展,通过文件系统中的'命名'标识,打破了亲缘关系的束缚,实现了无关联进程间的双向数据传输。
Linux 命名管道(FIFO)通过文件系统路径实现无亲缘进程通信,具备半双工、基于文件操作特性。创建可用 mkfifo 命令或函数,打开需遵循读写端同步阻塞规则。实战包含文件拷贝与 C/S 模型,相比匿名管道突破亲缘限制,适用于日志收集等场景,注意手动清理管道文件及权限配置。

在 Linux IPC(进程间通信)体系中,匿名管道解决了亲缘进程间的通信问题,但存在核心限制——仅支持具有共同祖先的进程通信。而命名管道(FIFO)作为匿名管道的扩展,通过文件系统中的'命名'标识,打破了亲缘关系的束缚,实现了无关联进程间的双向数据传输。
命名管道(Named Pipe),又称 FIFO(First In First Out),是一种特殊的文件系统对象(类型为 p),其核心本质与匿名管道一致——内核中的一块缓冲区,但通过文件路径作为标识,让任意进程都能通过该路径访问管道,实现跨进程通信。
与匿名管道相比,命名管道的核心差异是'命名':
pipe() 创建的文件描述符在亲缘进程间共享;/tmp/myfifo),任意进程可通过 open() 打开该路径,实现通信。open()/read()/write()/close() 等标准文件接口操作;| 特性 | 匿名管道 | 命名管道 (FIFO) |
|---|---|---|
| 定义 | 一种半双工通信通道,通常用于具有亲缘关系的进程之间(如父子进程)。 | 一种特殊的文件(存在于文件系统中),允许无亲缘关系的进程间通信。 |
| 创建方式 | 通过 pipe() 系统调用创建,返回两个文件描述符(读端和写端)。 | 通过 mkfifo() 或 mknod() 创建,在文件系统中具有路径名。 |
| 标识 | 无名,仅通过文件描述符引用。 | 有名,通过文件系统中的路径名标识。 |
| 进程关系 | 仅适用于有共同祖先(如父子进程)的进程。 | 适用于任意进程,无论是否有亲缘关系。 |
| 通信方向 | 单向(半双工),数据只能从一个方向流动。 | 通常也是单向(半双工),但可通过打开两个管道实现双向通信。 |
| 持久性 | 随进程存在而存在,所有相关进程关闭管道后自动销毁。 | 随文件系统存在,可显式删除(unlink),即使没有进程打开也不会消失。 |
| 打开方式 | 无需显式打开,创建时直接获得文件描述符。 | 必须像普通文件一样用 open() 打开,使用路径名。 |
| 数据模型 | 字节流,无消息边界。 | 字节流,无消息边界。 |
| 阻塞行为 | 默认阻塞:读空管道或写满管道会使进程阻塞(可设置非阻塞)。 | 默认阻塞:读空 FIFO 或写满 FIFO 会使进程阻塞(可设置非阻塞)。 |
| 典型应用场景 | 父子进程间的简单通信,如命令管道 ` | ` 在 shell 中的使用。 |
联系:
PIPE_BUF 限制内,写入操作具有原子性(多个进程同时写时数据不会交错)。命名管道有两种创建方式:命令行创建和代码创建,本质都是在文件系统中生成一个 FIFO 类型的文件。
直接通过 mkfifo 命令创建命名管道,语法简单,适合快速测试:
# 创建名为 myfifo 的命名管道
mkfifo myfifo
# 查看管道文件(类型标识为 p)
ls -l myfifo
创建的管道文件最前面的标识是 p 开头,验证了它不是一个普通文件。当终止读端时,相当于读端关闭,若写端还在写,操作系统会杀死写端进程。
通过 mkfifo() 系统调用在代码中创建命名管道,需指定管道路径和权限。
#include <sys/stat.h>
#include <sys/types.h>
// 参数:pathname-管道路径;mode-权限(如 0644)
int mkfifo(const char* pathname, mode_t mode);
代码示例(创建命名管道)
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#define FIFO_PATH "./myfifo"
int main() {
// 创建命名管道,权限 0644(所有者读 + 写,其他读)
int ret = mkfifo(FIFO_PATH, 0644);
if (ret == -1) {
// 若管道已存在,errno 为 EEXIST,可忽略该错误
if (errno != EEXIST) {
perror("mkfifo error");
return 1;
}
printf("命名管道已存在\n");
} else {
printf("命名管道创建成功\n");
}
return 0;
}
命名管道的打开(open())行为与普通文件不同,核心是'读端与写端的同步'——仅当管道的读端和写端都被打开后,通信才能正常进行。
| 打开方式 | 行为描述 |
|---|---|
读方式打开(O_RDONLY) | - 若管道无写端打开:阻塞,直到有进程以写方式打开该管道; - 若指定 O_NONBLOCK:不阻塞,直接返回成功 |
写方式打开(O_WRONLY) | - 若管道无读端打开:阻塞,直到有进程以读方式打开该管道; - 若指定 O_NONBLOCK:不阻塞,返回失败(errno=ENXIO) |
读写方式打开(O_RDWR) | 不阻塞,直接打开(同时具备读和写权限,可实现单向通信的'自我循环') |
注意:实际开发中,建议读端以
O_RDONLY打开,写端以O_WRONLY打开,避免使用O_RDWR(可能导致通信逻辑混乱)。
下面通过两个独立的程序(writer.c 写端、reader.c 读端)演示命名管道的跨进程通信,实现'写端输入数据,读端接收并打印'的功能。
通过两个程序配合,实现文件拷贝功能:
file_writer.c:读取本地文件,将内容写入命名管道;file_reader.c:从命名管道读取内容,写入目标文件。#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFO_PATH "./tp"
#define BUF_SIZE 1024
// 错误处理宏
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while(0)
int main() {
// 1. 创建命名管道
int ret = mkfifo(FIFO_PATH, 0644);
if (ret == -1 && errno != EEXIST) {
ERR_EXIT("mkfifo error");
}
// 2. 打开本地文件(待拷贝的源文件 abc)
int infd = open("abc", O_RDONLY);
if (infd == -1) {
ERR_EXIT("open source file error");
}
// 3. 以写方式打开命名管道(阻塞,直到读端打开)
int outfd = open(FIFO_PATH, O_WRONLY);
if (outfd == -1) {
ERR_EXIT("open fifo error");
}
// 4. 读取源文件内容,写入管道
char buf[BUF_SIZE];
int n;
while ((n = read(infd, buf, BUF_SIZE)) > 0) {
// 写入管道(保证数据完整写入)
if (write(outfd, buf, n) != n) {
ERR_EXIT("write to fifo error");
}
}
// 5. 关闭文件描述符
close(infd);
close(outfd);
printf("文件内容写入管道完成\n");
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FIFO_PATH "./tp"
#define BUF_SIZE 1024
#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while(0)
int main() {
// 1. 以读方式打开命名管道(阻塞,直到写端打开)
int infd = open(FIFO_PATH, O_RDONLY);
if (infd == -1) {
ERR_EXIT("open fifo error");
}
// 2. 创建目标文件(拷贝后的文件 abc.bak)
int outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (outfd == -1) {
ERR_EXIT("open target file error");
}
// 3. 从管道读取内容,写入目标文件
char buf[BUF_SIZE];
int n;
while ((n = read(infd, buf, BUF_SIZE)) > 0) {
if (write(outfd, buf, n) != n) {
ERR_EXIT("write to target file error");
}
}
// 4. 关闭文件描述符,删除命名管道
close(infd);
close(outfd);
unlink(FIFO_PATH); // 手动删除管道文件
printf("文件拷贝完成\n");
return 0;
}
# 编译两个程序
gcc file_writer.c -o writer
gcc file_reader.c -o reader
# 终端 1:运行读端(阻塞等待写端)
./reader
# 终端 2:运行写端(开始拷贝)
./writer
运行结果:
abc 文件内容,写入命名管道 tp;tp 读取内容,写入 abc.bak;实现一个简单的 C/S 模型:
server.c:作为服务端,监听管道,接收客户端消息并打印;client.c:作为客户端,向管道发送消息,实现双向交互。需配置 Makefile 及公共头文件 comm.h 以简化构建流程。
#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "comm.h"
int main() {
// 1. 创建管道文件
umask(0); // 重置文件创建掩码,确保权限生效
int n = mkfifo(fifoname.c_str(), 0666);
if (n < 0) {
perror("mkfifo");
return 1;
}
// 2. 打开管道文件(阻塞等待客户端连接)
int rfd = open(fifoname.c_str(), O_RDONLY);
if (rfd < 0) {
perror("open");
return 2;
}
char inbuffer[1024];
// 3. 进行通信,循环接收客户端消息
while (true) {
ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);
if (n > 0) {
inbuffer[n] = 0;
std::cout << "client say# " << inbuffer << std::endl;
} else if (n == 0) {
// 写端关闭了
break;
} else {
perror("read");
break;
}
}
// 4. 关闭文件描述符
close(rfd);
// 5. 删除管道文件
unlink(fifoname.c_str());
return 0;
}
#include <iostream>
#include <string>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.h"
int main() {
// 1. 以写方式打开管道(阻塞,直到服务端打开读端)
int wfd = open(fifoname.c_str(), O_WRONLY);
if (wfd < 0) {
perror("open");
return 1;
}
// 2. 循环输入并发送消息给服务端
std::string outstring;
while (true) {
std::cout << "Please Enter@ ";
std::cin >> outstring;
write(wfd, outstring.c_str(), outstring.size());
// 不需要额外写 \0
}
// 3. 关闭文件描述符
close(wfd);
return 0;
}
# 编译程序
make
# 终端 1:启动服务端(阻塞等待客户端)
./server
# 终端 2:启动客户端(输入消息交互)
./client
注意:必须先打开服务端再打开客户端,否则读端阻塞会导致写端无法继续。
unlink() 删除;O_RDWR 打开管道实现单进程读写,但会破坏半双工特性,容易导致数据混乱;open() 时添加 O_NONBLOCK 标志;≤ PIPE_BUF(默认 4096 字节)时,内核保证写入原子性;超过则不保证,需在应用层处理分包;open() 失败。总结:命名管道(FIFO)是匿名管道的重要扩展,其核心价值在于突破了亲缘进程的限制,通过文件系统路径实现任意进程间的通信,且沿用了 Linux 标准的文件操作接口,上手成本低。
mkfifo() 或 mkfifo 命令创建,open() 时需遵循读/写端同步规则;read()/write() 即可完成数据传输;
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online