命名管道通信
文章目录
命名管道(Named Pipe / FIFO)
命名管道的概念
匿名管道具有如下特点:
- 用于 进程间通信(IPC, Inter-Process Communication)
- 只能在 具有血缘关系的进程之间使用
(例如:父进程与子进程)
产生这一限制的原因是:
匿名管道的文件描述符需要通过 进程继承(fork)机制 才能被多个进程共享。
因此,匿名管道的使用场景通常是:
Parent Process
│
fork()
│
Child Process
父子进程继承同一组文件描述符,从而访问同一个管道。
然而,在实际系统开发中,我们经常需要解决一个问题:
如何让两个互不相关的进程进行通信?
也就是说:
- 两个进程之间 *不存在父子关系
- 甚至可能由 两个不同的程序启动
为了解决这一问题,Linux 提供了另一种 IPC 机制:
命名管道(Named Pipe / FIFO)
命名管道本质上仍然是 管道机制的一种实现形式,但其特点是:
- 管道具有 *文件系统中的路径名
- 任何进程只要能够访问该路径,都可以通过该管道通信
因此,命名管道可以实现:
Process A <----> Named Pipe <----> Process B
即:
允许两个完全独立的进程进行通信。
命名管道的实现原理:基于文件对象共享机制
为了理解 命名管道(Named Pipe / FIFO) 的实现机制,需要先回顾 Linux 中文件打开时的内部结构。
当一个进程打开文件时,操作系统会在内核中建立一系列数据结构,用于管理该文件的访问。
典型结构如下:
用户进程
│
▼
文件描述符表 (File Descriptor Table)
│
▼
struct file (文件对象)
│
▼
inode (磁盘文件元数据)
│
▼
磁盘文件
其中:
- 文件描述符表(File Descriptor Table) 位于进程控制块中,用于记录当前进程打开的文件。
- struct file 表示一个内核中的 文件对象(Open File Object),包含:
- 文件操作方法
- 文件偏移量
- 文件状态标志
- 缓冲区信息
- inode 表示磁盘文件的元数据。
单进程打开文件的过程
假设当前存在一个普通文件:
named_pipe
该文件存在于磁盘文件系统中,并具有:
- inode
- 文件路径
- 文件权限
当进程调用系统调用:
open(“named_pipe”, O_RDONLY);
操作系统会执行以下步骤:
- 在文件系统中查找该路径对应的 inode
- 在内核中创建一个 struct file 对象
- 初始化该文件对象的属性
- 在进程的 文件描述符表 中分配一个条目
- 返回对应的文件描述符
例如:
进程A 文件描述符表
fd0 → stdin
fd1 → stdout
fd2 → stderr
fd3 → struct file (named_pipe)
两个进程打开同一文件的情况
现在考虑一个新的问题:
假设存在两个 彼此没有任何关系的进程:
Process A
Process B
两者都执行:
open(“named_pipe”, O_RDWR);
问题是:
操作系统是否需要为第二个进程重新创建一个新的
struct file对象?
操作系统的处理方式
在这种情况下,操作系统通常 不会重复创建完全相同的文件对象。
原因包括:
资源管理原则
操作系统在设计上遵循:
尽可能减少重复资源的创建与维护。
如果两个进程打开同一个文件:
- 文件属性相同
- inode 相同
- 文件操作方式一致
则可以共享底层资源。
文件对象的共享机制
因此,在很多情况下:
Process A fd3 ──┐
│
▼
struct file
│
▼
inode
│
▼
磁盘文件
▲
│
Process B fd4 ──┘
即:
- 两个进程的 文件描述符不同
- 但它们可以指向 同一个 struct file 或同一底层文件对象
这样可以避免: - 重复维护文件状态
- 重复分配缓冲区
- 额外内存开销
文件共享带来的效果
由于两个进程访问的是 同一份底层文件资源,因此会出现如下现象:
如果一个进程对文件进行写入:
write(fd, data, size);
另一个进程就可以读取到这些数据:
read(fd, buffer, size);
例如:
Process A → 写入文件
Process B → 读取文件
在用户层表现为:
echo “data” >> file
cat file
即可读取到刚写入的内容。
需要注意:
在某些情况下,可能暂时无法看到更新的数据,其原因可能是:
- 数据仍然位于 用户缓冲区
- 尚未 刷新到磁盘(flush)
但从逻辑上讲:
两个进程访问的是同一文件资源,因此数据是共享的。
与命名管道的关系
理解上述机制后,就可以理解 命名管道的设计思想。
命名管道本质上也是一种 文件对象:
p (FIFO 文件)
其特点包括:
- 在文件系统中具有 路径名
- 具有 inode
- 可以被多个进程打开
例如:
mkfifo named_pipe
创建后:
ls -l
会看到:
prw-r–r-- 1 user user 0 named_pipe
其中:
p
表示 FIFO 文件类型。
命名管道通信原理
在 Linux 中,命名管道(Named Pipe / FIFO) 是一种重要的进程间通信(IPC)机制。
与普通文件不同,命名管道在逻辑上表现为文件,但其数据传输过程不经过磁盘 IO,而是在内核缓冲区中完成数据交换。
其通信过程如下:
- 一个进程向管道写入数据
- 数据被写入内核缓冲区
- 另一个进程从管道读取数据
因此,本质上它仍然是一种管道通信机制。
当两个进程分别打开该 FIFO 文件时:
Process A open(named_pipe) → 写端
Process B open(named_pipe) → 读端
操作系统在内核中维护:
- 管道缓冲区
- 读写指针
- 同步机制
通信过程如下:
Process A (write)
│
▼
内核管道缓冲区
│
▼
Process B (read)
与普通文件不同的是:
FIFO 的数据 不会写入磁盘,而是直接存储在 内核缓冲区 中。
因此:
- 文件大小始终为 0
- 数据仅在进程之间流动
命名管道与匿名管道的区别
在 Linux 中,管道主要分为两种:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 匿名管道(Anonymous Pipe) | 没有文件名 | 仅限父子进程 |
| 命名管道(Named Pipe / FIFO) | 有文件路径和名称 | 任意进程 |
匿名管道
匿名管道通过 文件描述符继承实现通信:
- 父进程创建 pipe
- fork 创建子进程
- 子进程继承父进程的文件描述符
- 两个进程共享同一个 内核对象
该内核对象通常表示为:
struct file
该结构体在内核中具有唯一内存地址,因此可以作为资源标识。
但是:
- 匿名管道没有文件名
- 只能依赖继承关系共享资源
命名管道
命名管道如何做到让不同的进程先看到了同一份资源:
让不同的进程打开指定名称(路径+文件名)的同一份文件
命名管道在文件系统中以特殊文件形式存在,例如:
/tmp/my_fifo
由于 Linux 文件系统中路径 + 文件名具有唯一性,因此:
多个进程只要:
open("/tmp/my_fifo")
就可以访问同一个管道对象。
因此命名管道的资源共享方式是:
通过文件路径定位同一个内核对象
而不是依赖继承关系。
命名管道的创建
在 Linux 中,可以使用系统调用:
mkfifo()
其函数原型:
int mkfifo(const char *pathname, mode_t mode);
参数说明:
| 参数 | 含义 |
|---|---|
| pathname | 管道文件路径 |
| mode | 文件权限 |
例如:
mkfifo("/tmp/my_fifo", 0666);
表示创建一个权限为:
rw-rw-rw-
的 FIFO 文件。
如果创建成功:
返回 0
失败:
返回 -1
并设置 errno
命名管道文件的删除
管道文件在通信结束后通常需要删除,可以使用系统调用:
unlink()
函数原型:
int unlink(const char *pathname);
示例:
unlink("/tmp/my_fifo");
其作用是:
- 删除文件系统中的管道文件
- 不影响已经打开的文件描述符
命名管道通信流程
一个典型的 Client–Server 模型如下:
Server 端
职责:
- 创建 FIFO
- 打开 FIFO
- 从 FIFO 读取数据
- 删除 FIFO
示例:
mkfifo("/tmp/my_fifo", 0666)
fd = open("/tmp/my_fifo", O_RDONLY)
read(fd, ...)
close(fd)
unlink("/tmp/my_fifo")
Client 端
职责:
- 打开 FIFO
- 向 FIFO 写入数据
示例:
fd = open("/tmp/my_fifo", O_WRONLY)
write(fd, ...)
close(fd)
进程间通信的基本前提
在 Linux 中,进程间通信(IPC) 的基本前提是:
不同进程必须能够访问同一份系统资源。
在命名管道(FIFO)通信模型中,实现该前提的方法是:
- 在文件系统中创建一个 FIFO 文件
- 不同进程通过 相同的路径名 打开该文件
- 进程通过读写该文件实现通信
因此,本质上是:
不同进程 → 打开同一管道文件 → 共享同一内核对象
通信过程表现为文件读写操作。
Server 端实现逻辑
Server 端主要负责接收数据,其实现流程如下:
- 打开命名管道(只读方式)
- 循环读取数据
- 处理读取结果
- 关闭文件描述符
伪代码结构如下:
while(true)
{
ssize_t s = read(fd, buffer, sizeof(buffer)-1);
if(s > 0)
{
buffer[s] = '\0';
cout << "client say: " << buffer << endl;
}
else if(s == 0)
{
// 写端关闭
break;
}
else
{
// 读取错误
perror("read error");
break;
}
}
读取返回值语义read() 的返回值具有如下含义:
| 返回值 | 含义 |
|---|---|
| >0 | 成功读取字节数 |
| =0 | 写端关闭(EOF) |
| <0 | 读取错误 |
因此,当返回值为 0 时,说明客户端已经关闭写端,服务器可以安全退出。
Client 端实现逻辑
Client 端主要负责发送数据。
基本流程:
- 打开 FIFO 文件(写模式)
- 从标准输入读取数据
- 将数据写入管道
- 循环发送消息
伪代码:
while(true)
{
printf("please say: ");
fgets(buffer, sizeof(buffer), stdin);
write(fd, buffer, strlen(buffer));
}
通信流程:
Client → write → FIFO → read → Server
FIFO 打开时的阻塞行为
命名管道具有一个重要特性:
FIFO 在默认情况下具有 阻塞打开(blocking open)行为。
具体规则:
| 操作 | 行为 |
|---|---|
| 只读打开 FIFO | 若没有写端打开,则阻塞 |
| 只写打开 FIFO | 若没有读端打开,则阻塞 |
因此在实验中会出现:
- Server 打开 FIFO
- 程序停在
open()处 - 直到 Client 打开 FIFO
- 两端同时继续执行
流程如下:
Server: open(O_RDONLY) → 阻塞
Client: open(O_WRONLY) → 成功
Server: open 返回
这就是命名管道的同步机制。
通信终止机制
当 Client 进程退出时:
- 写端文件描述符关闭
- FIFO 写端消失
- Server 再次
read()时返回 0
因此 Server 可以检测:
read() == 0
表示:
客户端已退出
服务器即可安全结束通信。
命名管道通信完整流程
整体流程如下:
创建 FIFO
mkfifo("/tmp/my_fifo", 0666)
Server 启动
open("/tmp/my_fifo", O_RDONLY)
Client 启动
open("/tmp/my_fifo", O_WRONLY)
数据通信
Client write → FIFO → Server read
客户端退出
close(write_fd)
服务器检测 EOF
read() == 0
服务器退出并删除 FIFO
unlink("/tmp/my_fifo")
命名管道通信特点
| 特性 | 匿名管道 | 命名管道 |
|---|---|---|
| 是否有文件名 | 否 | 是 |
| 是否依赖父子关系 | 是 | 否 |
| 是否存在于文件系统 | 否 | 是 |
| 使用范围 | 父子进程 | 任意进程 |
| 因此命名管道的优势是: |
可以在无亲缘关系的进程之间通信
命名管道本质
从内核实现角度来看:
命名管道可以理解为:
一个通过文件系统路径暴露出来的内核管道对象。
即:
文件系统路径
│
▼
FIFO inode
│
▼
内核管道缓冲区
│
▼
进程间通信
因此命名管道既具有:
- 文件的外部形式
- 管道的通信语义
通过上述分析,可以得到以下结论:
- Linux 进程通过 文件描述符表 管理已打开文件。
- 文件打开后,内核会创建 struct file 文件对象。
- 多个进程可以 共享同一底层文件资源。
- 命名管道(FIFO)利用这一机制,实现:
无血缘关系进程之间的通信 - FIFO 的数据存储在:
内核管道缓冲区
而不是磁盘文件中。
通过命名管道通信,俩个进程通信本质上变成了:
Client ----write----> FIFO ----read----> Server
即:
写文件 → 读文件
但数据不会落盘,而是在 内核缓冲区中流动。
因此它仍属于:
基于内核缓冲区的 IPC 机制
命名管道通信的核心思想:
- 在文件系统中创建 FIFO 文件
- 不同进程通过 路径名 打开同一个 FIFO
- 一端写入数据
- 另一端读取数据
其本质仍然是:
管道通信 + 文件系统命名机制
但实际上数据仍然在 内核缓冲区中传输,不会落盘。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)