Linux 命名管道(FIFO)跨进程通信
在 Linux 进程间通信(IPC)中,管道是最基础的方式之一。但常用的匿名管道有一个局限——只能用于有血缘关系的进程(如父子进程)。命名管道(FIFO) 则通过文件系统作为媒介,让任意两个进程(无论是否有血缘关系)都能实现数据交互。
一、命名管道是什么?
1. 本质区别
命名管道和匿名管道的核心通信逻辑一致,都是基于内核缓冲区的字节流半双工通信。核心区别在于:命名管道有一个可见的文件系统入口(一个 FIFO 文件),而匿名管道没有。
简单来说,匿名管道是'隐式的、血缘专属',命名管道是'显式的、通用的'。只要知道 FIFO 文件的路径,任意进程都能与之通信。
2. 核心特点
- 跨进程通信:无血缘关系的进程可通过 FIFO 路径通信;
- 半双工通信:数据单向流动,双向通信需创建两个 FIFO;
- 基于文件标识:FIFO 存在于文件系统中,通过打开文件获取描述符;
- 阻塞特性:默认读端阻塞直到写端打开,反之亦然(可设 O_NONBLOCK 非阻塞);
- 生命周期:FIFO 文件不会随进程退出自动删除,需手动清理。

3. 对比匿名管道
| 维度 | 匿名管道 (pipe) | 命名管道 (FIFO) |
|---|---|---|
| 通信范围 | 有血缘关系进程 | 任意进程 |
| 标识方式 | 无文件,靠 FD 继承 | 有文件系统入口 |
| 创建方式 | pipe() 系统调用 | mkfifo() 或命令 |
| 生命周期 | 进程退出销毁 | 文件删除才销毁 |
二、创建方式
1. 命令行创建
使用 mkfifo 命令快速测试:
# 创建名为 myfifo 的管道
mkfifo myfifo
# 查看类型标识为 p
ls -l myfifo
创建的管道文件最前面的标识是 p,验证了它不是普通文件,写入时文件大小不变。
2. 代码创建
通过 mkfifo() 系统调用在代码中创建,需指定路径和权限:
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
// 参数:pathname-管道路径;mode-权限
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() {
int ret = mkfifo(FIFO_PATH, 0644);
if (ret == -1) {
if (errno != EEXIST) {
perror("mkfifo error");
return 1;
}
printf("命名管道已存在\n");
} else {
printf("命名管道创建成功\n");
}
return 0;
}
3. 打开规则
命名管道的 open() 行为与普通文件不同,关键在于读写端的同步:
- O_RDONLY:只读(读端),默认阻塞,直到有写端打开;
- O_WRONLY:只写(写端),默认阻塞,直到有读端打开;
- O_RDWR:读写同时打开,不阻塞(可实现自我循环,但建议避免)。
注意:实际开发中,建议读端用
O_RDONLY,写端用O_WRONLY,避免混用导致逻辑混乱。
三、实操实现
本例包含服务端(监听接收)和客户端(发送消息)。
1. 前置准备
comm.h
#pragma once
#include <string>
const std::string fifoname = "fifo";
Makefile
all: client server
client: client.cpp
g++ -o $@ $^ -std=c++11
server: server.cpp
g++ -o $@ $^ -std=c++11
.PHONY: clean
clean:
rm -f client server
2. 服务端程序 (server.cpp)
#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);
unlink(fifoname.c_str());
return 0;
}
3. 客户端程序 (client.cpp)
#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() {
// 打开管道文件(写端)
int wfd = open(fifoname.c_str(), O_WRONLY);
if (wfd < 0) {
perror("open");
return 1;
}
std::string outstring;
while (true) {
std::cout << "Please Enter@ ";
std::cin >> outstring;
write(wfd, outstring.c_str(), outstring.size());
}
close(wfd);
return 0;
}
4. 编译运行
在终端分别启动服务端和客户端即可看到交互效果。
四、应用场景
- 后台服务与前台程序通信:守护进程接收指令;
- 独立程序数据交互:如数据库与缓存服务传递数据;
- Shell 脚本通信:比匿名管道更灵活;
- 简单 C/S 模型:客户端写入请求,服务器读取处理。
五、常见坑点
- 阻塞问题:未设置 O_NONBLOCK 导致进程卡死;
- SIGPIPE 信号:读端关闭后写端继续写入会崩溃,需处理信号;
- 文件残留:未 unlink() 导致下次创建报错;
- 路径不一致:两端路径不同无法通信;
- 权限问题:权限过低导致其他进程无法打开;
- 数据边界:字节流无边界,需注意读取完整性。
六、总结
命名管道(FIFO)是 Linux 跨进程通信的基础,核心是通过文件系统标识实现任意进程间的字节流通信。它继承了匿名管道的简单性,又解决了血缘限制。掌握其阻塞特性、信号处理和文件清理机制,是学习 IPC 的关键一步。后续若需更高效率或复杂功能,可进一步探索消息队列或共享内存。

