Linux 进程间通信之命名管道(FIFO):跨进程通信的实用方案

Linux 进程间通信之命名管道(FIFO):跨进程通信的实用方案
在这里插入图片描述

🔥草莓熊Lotso:个人主页
❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:

在这里插入图片描述

文章目录


前言:

在 Linux IPC(进程间通信)体系中,匿名管道解决了亲缘进程(父子、兄弟进程)间的通信问题,但存在一个核心限制 —— 仅支持具有共同祖先的进程通信。而命名管道(FIFO)作为匿名管道的扩展,通过文件系统中的 “命名” 标识,打破了亲缘关系的束缚,实现了无关联进程间的双向数据传输。本文将从命名管道的核心概念、创建方式、通信原理和实战案例出发,带你吃透这一实用的跨进程通信技术。

一. 命名管道核心概念:什么是 FIFO?

1.1 命名管道的定义

命名管道(Named Pipe),又称 FIFO(First In First Out),是一种特殊的文件系统对象(类型为p),其核心本质与匿名管道一致 —— 内核中的一块缓冲区,但通过文件路径作为标识,让任意进程都能通过该路径访问管道,实现跨进程通信。
与匿名管道相比,命名管道的核心差异的是 “命名”:

  • 匿名管道:无文件路径,仅通过pipe()创建的文件描述符在亲缘进程间共享;
  • 命名管道:有明确的文件路径(如/tmp/myfifo),任意进程可通过open()打开该路径,实现通信。

1.2 命名管道的核心特性

  • 跨进程通信:支持无亲缘关系的进程(如两个独立的应用程序)通信,突破匿名管道的亲缘限制;
  • 半双工通信:数据单向流动,如需双向通信需创建两个命名管道;
  • 基于文件操作:遵循 Linux “一切皆文件” 思想,通过open()/read()/write()/close()等标准文件接口操作;
  • 生命周期随内核:命名管道创建后,即使创建进程退出,管道文件仍存在于文件系统中,需手动删除(unlink()或rm命令);

同步与互斥:内核自动保证管道操作的同步(如读阻塞、写阻塞)和互斥(同一时间仅允许一个进程写)。

在这里插入图片描述


在这里插入图片描述

1.3 命名管道和匿名管道的区别与联系

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,⼀但这些工作完成之后,它们具有相同的语义。
  • 为了更清晰地理解命名管道的定位,整理两者的核心差异更详细的对比如下表所示
特性匿名管道命名管道 (FIFO)
定义一种半双工通信通道,通常用于具有亲缘关系的进程之间(如父子进程)。一种特殊的文件(存在于文件系统中),允许无亲缘关系的进程间通信。
创建方式通过 pipe() 系统调用创建,返回两个文件描述符(读端和写端)。通过 mkfifo()mknod() 创建,在文件系统中具有路径名。
标识无名,仅通过文件描述符引用。有名,通过文件系统中的路径名标识。
进程关系仅适用于有共同祖先(如父子进程)的进程。适用于任意进程,无论是否有亲缘关系。
通信方向单向(半双工),数据只能从一个方向流动。通常也是单向(半双工),但可通过打开两个管道实现双向通信。
持久性随进程存在而存在,所有相关进程关闭管道后自动销毁。随文件系统存在,可显式删除(unlink),即使没有进程打开也不会消失。
打开方式无需显式打开,创建时直接获得文件描述符。必须像普通文件一样用 open() 打开,使用路径名。
数据模型字节流,无消息边界。字节流,无消息边界。
阻塞行为默认阻塞:读空管道或写满管道会使进程阻塞(可设置非阻塞)。默认阻塞:读空 FIFO 或写满 FIFO 会使进程阻塞(可设置非阻塞)。
典型应用场景父子进程间的简单通信,如命令管道 `` 在 shell 中的使用。
系统限制容量通常有限(如 64KB),取决于系统实现。容量通常有限,类似匿名管道,但受文件系统影响。
对比维度匿名管道(pipe)命名管道(FIFO)
通信范围仅亲缘进程(父子、兄弟)任意进程(无亲缘关系)
创建方式pipe()系统调用mkfifo()函数或mkfifo命令
标识方式文件描述符(fd[0]、fd[1])文件系统路径(如./myfifo)
生命周期随进程(所有进程关闭描述符后释放)随内核(需手动unlink()删除)
打开方式无需open(),创建即打开需通过open()打开路径
核心用途亲缘进程间简单通信无关联进程间通信(如C/S模型)

联系:

  1. 通信机制:两者都是操作系统提供的进程间通信(IPC)方式,基于内核缓冲区实现数据传输。
  2. 数据特性:都提供可靠的字节流服务,数据写入和读取的顺序一致,无消息边界。
  3. 行为相似:默认情况下,读写操作具有相似的阻塞语义(读空阻塞、写满阻塞),并支持非阻塞标志。
  4. 单向性:本质上都是单向通信管道(半双工),若要双向通信需创建两个管道。
  5. 原子性:在 PIPE_BUF 限制内,写入操作具有原子性(多个进程同时写时数据不会交错)。

二. 命名管道的创建方式

命名管道有两种创建方式:命令行创建和代码创建,本质都是在文件系统中生成一个 FIFO 类型的文件。

2.1 命令行创建(mkfifo 命令)

直接通过mkfifo命令创建命名管道,语法简单,适合快速测试:

# 创建名为myfifo的命名管道mkfifo myfifo # 查看管道文件(类型标识为p)ls-l myfifo 
在这里插入图片描述


我们可以发现,这里创建的管道文件(最前面的标识是p开头),就算我们是不停的在往里面进行写操作,但是它的文件大小是一直不变的,验证了我们上面图中所说的它不是一个普通文件。另外当我们直接终止读端的时候,这个时候的情况就相当于读端关闭,写端还在一起写,所以操作系统直接杀死了我们的进程。

2.2 代码创建(mkfifo 函数)

通过mkfifo()系统调用在代码中创建命名管道,需指定管道路径和权限,原型如下:

#include<sys/stat.h>#include<sys/types.h>// 参数:pathname-管道路径;mode-权限(如0644)intmkfifo(constchar*pathname, mode_t mode);

代码示例(创建命名管道)

#include<stdio.h>#include<sys/stat.h>#include<sys/types.h>#include<errno.h>#defineFIFO_PATH"./myfifo"intmain(){// 创建命名管道,权限0644(所有者读+写,其他读)int ret =mkfifo(FIFO_PATH,0644);if(ret ==-1){// 若管道已存在,errno为EEXIST,可忽略该错误if(errno != EEXIST){perror("mkfifo error");return1;}printf("命名管道已存在\n");}else{printf("命名管道创建成功\n");}return0;}

三. 命名管道的打开规则(关键!)

命名管道的打开(open())行为与普通文件不同,核心是 “读端与写端的同步”—— 仅当管道的读端和写端都被打开后,通信才能正常进行,具体规则如下:

打开方式行为描述
读方式打开(O_RDONLY- 若管道无写端打开:阻塞,直到有进程以写方式打开该管道;
- 若指定 O_NONBLOCK:不阻塞,直接返回成功
写方式打开(O_WRONLY- 若管道无读端打开:阻塞,直到有进程以读方式打开该管道;
- 若指定 O_NONBLOCK:不阻塞,返回失败(errno=ENXIO)
读写方式打开(O_RDWR不阻塞,直接打开(同时具备读和写权限,可实现单向通信的 “自我循环”)
注意:实际开发中,建议读端以O_RDONLY打开,写端以O_WRONLY打开,避免使用O_RDWR(可能导致通信逻辑混乱)。

四. 命名管道实战案例

下面通过两个独立的程序(writer.c写端、reader.c读端)演示命名管道的跨进程通信,实现 “写端输入数据,读端接收并打印” 的功能。

4.1 案例 1:命名管道实现文件拷贝

通过两个程序配合,实现文件拷贝功能:

  • file_writer.c:读取本地文件,将内容写入命名管道;
  • file_reader.c:从命名管道读取内容,写入目标文件。

4.1.1 写端程序(file_writer.c)

#include<stdio.h>#include<unistd.h>#include<sys/stat.h>#include<sys/types.h>#include<fcntl.h>#include<errno.h>#include<string.h>#defineFIFO_PATH"./tp"#defineBUF_SIZE1024// 错误处理宏#defineERR_EXIT(m)do{perror(m);exit(EXIT_FAILURE);}while(0)intmain(){// 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");return0;}

4.1.2 读端程序(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>#defineFIFO_PATH"./tp"#defineBUF_SIZE1024#defineERR_EXIT(m)do{perror(m);exit(EXIT_FAILURE);}while(0)intmain(){// 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");return0;}

4.1.3 编译与运行

# 编译两个程序 gcc file_writer.c -o writer gcc file_reader.c -o reader # 终端1:运行读端(阻塞等待写端) ./reader # 终端2:运行写端(开始拷贝) ./writer 

运行结果:

  • 写端读取abc文件内容,写入命名管道tp
  • 读端从tp读取内容,写入abc.bak
  • 拷贝完成后,读端删除管道文件,两个程序退出。

4.2 案例 2:命名管道实现 Server-Client 通信

实现一个简单的 C/S 模型:

  • server.c:作为服务端,监听管道,接收客户端消息并打印;
  • client.c:作为客户端,向管道发送消息,实现双向交互。

4.2.1 前置准备(Makefile && comm.h)

  • Makefile
在这里插入图片描述
  • comm.h
在这里插入图片描述

4.2.2 服务端程序(server.c)

#include<iostream>#include<cstdio>#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<fcntl.h>#include"comm.h"intmain(){// 1. 创建管道文件umask(0);// 重置文件创建掩码,确保权限生效int n =mkfifo(fifoname.c_str(),0666);if(n <0){perror("mkfifo");return1;}// 2. 打开管道文件(阻塞等待客户端连接)int rfd =open(fifoname.c_str(), O_RDONLY);if(rfd <0){perror("open");return2;}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;}elseif(n ==0){// 写端关闭了break;}else{perror("read");break;}}// 4. 关闭文件描述符close(rfd);// 5. 删除管道文件unlink(fifoname.c_str());return0;}

4.2.3 客户端程序(client.c)

#include<iostream>#include<string>#include<cstdio>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include"comm.h"intmain(){// 1. 以写方式打开管道(阻塞,直到服务端打开读端)int wfd =open(fifoname.c_str(), O_WRONLY);if(wfd<0){perror("open");return1;}// 2. 循环输入并发送消息给服务端 std::string outstring;while(true){ std::cout <<"Please Enter@ "; std::cin >> outstring;write(wfd, outstring.c_str(), outstring.size());// 要不要写\0? 不需要!}// 3. 关闭文件描述符close(wfd);return0;}

4.2.4 编译与运行

# 编译程序make# 终端1:启动服务端(阻塞等待客户端) ./server # 终端2:启动客户端(输入消息交互) ./client 
  • 必须先打开服务端再打开客服端,大家可以自己去尝试一下。
在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

我们可以加一点代码更好的去验证一下观察上面的现象啥的

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

五. 命名管道使用避坑指南和总结

  • 必须手动删除管道文件:命名管道创建后会残留于文件系统,若不删除,下次创建会报错(errno=EEXIST),建议在通信结束后用unlink()删除;
  • 避免单进程同时读写:虽然可通过O_RDWR打开管道实现单进程读写,但会破坏半双工特性,容易导致数据混乱;
  • 处理阻塞场景:读端未打开时写端会阻塞,写端未打开时读端会阻塞,若需非阻塞操作,可在open()时添加O_NONBLOCK标志;
  • 数据完整性保证:当写入数据量≤PIPE_BUF(默认 4096 字节)时,内核保证写入原子性;超过则不保证,需在应用层处理分包;
  • 权限设置合理:创建管道时权限需开放给通信进程(如 0664 允许同组进程访问),避免因权限不足导致open()失败。

总结:命名管道(FIFO)是匿名管道的重要扩展,其核心价值在于突破了亲缘进程的限制,通过文件系统路径实现任意进程间的通信,且沿用了 Linux 标准的文件操作接口,上手成本低。

  • 命名管道是带文件路径的内核缓冲区,支持跨进程通信
  • 需通过mkfifo()mkfifo命令创建,open()时需遵循读 / 写端同步规则;
  • 实战中可实现文件拷贝、C/S 通信等场景,配合read()/write()即可完成数据传输;
  • 与匿名管道相比,命名管道的核心优势是通信范围无限制,生命周期随内核。
在这里插入图片描述


在这里插入图片描述

结尾:

🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点: 👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用 💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑 🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解 技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标! 

结语:本文从概念、创建、打开规则到实战案例,全面覆盖了命名管道的核心知识点。命名管道适合对通信效率要求不极致、需跨进程交互的场景(如日志收集、简单命令交互)。如果需要更高效率的跨进程通信(如高频数据传输),可后续学习共享内存;若需消息结构化传输,可了解消息队列。创作不易,觉得有帮助的话,欢迎点赞、收藏、关注三连~ 后续会持续更新 Linux IPC 系列(共享内存、消息队列、信号量),带你从底层吃透进程间通信技术。

✨把这些内容吃透超牛的!放松下吧✨ʕ˘ᴥ˘ʔづきらど

Read more

Mysql超详细安装配置教程(保姆级)

MySQL 一、下载 MySQL (一)下载地址 官网下载社区版 MySQL,推荐选择 MySQL 8.0.44 社区版(稳定版,兼容性强),下载地址:MySQL Community Downloads (二)下载步骤 1. Select Operating System 选择 Microsoft Windows; 2. 下载选项选择: * 推荐:Windows (x86, 64-bit), ZIP Archive(免安装压缩包,灵活配置),文件大小约 231.7M,点击 Download; * 备选:MySQL Installer for Windows(图形化安装程序,

By Ne0inhk
基于 Rust 与 DeepSeek 构建高性能 Text-to-SQL 数据库代理服务

基于 Rust 与 DeepSeek 构建高性能 Text-to-SQL 数据库代理服务

前言 在当前数据库交互范式演进的过程中,将自然语言(Natural Language, NL)直接转化为结构化查询语言(Structured Query Language, SQL)已成为提升数据可访问性的关键技术路径。本文将深度剖析如何利用系统级编程语言 Rust 的高性能特性,结合 PostgreSQL Wire Protocol(数据库传输协议)与 DeepSeek 大语言模型的推理能力,构建一个透明的数据库代理层。该代理服务能够拦截客户端请求,智能识别自然语言指令,并在毫秒级时间内将其转换为可执行的高效 SQL 语句,最终在真实的 PostgreSQL 数据库中执行并返回结果。 一、 核心架构与技术选型 本项目不仅仅是一个简单的转换脚本,而是一个完整的网络服务中间件。其核心技术栈选择经过了严谨的考量: 1. Rust 语言:作为内存安全且无垃圾回收(GC)的语言,Rust 在处理网络协议解析、二进制数据流操作以及高并发连接管理方面展现出卓越的性能。其所有权系统确保了在多线程环境下的数据安全性。 2. PostgreSQL Wire

By Ne0inhk
告别复杂查询性能噩梦:一文读懂连接条件下推优化

告别复杂查询性能噩梦:一文读懂连接条件下推优化

摘要:金仓数据库(KingbaseES)的「基于代价的连接条件下推」技术解决了复杂SQL查询在生产环境中的性能瓶颈问题。该技术通过智能决策框架,先进行安全性检查确保语义等价,再基于代价模型评估下推收益,将连接条件智能下推到子查询中提前过滤数据。测试显示,简单场景性能提升600倍,复杂嵌套查询提升超4500倍,执行时间从秒级降至毫秒级。这项技术结合了语义安全和代价评估,有效应对现代复杂SQL的性能挑战,体现了国产数据库在深度优化方面的技术实力。 告别复杂查询性能噩梦:一文读懂连接条件下推优化 你是否遇到过这样的场景:一个在测试环境运行飞快的复杂SQL,一到生产环境就“卡死”?检查执行计划后,发现罪魁祸首往往是一个生成了巨大中间结果集的子查询,导致后续操作全部陷入性能泥潭。 针对这一经典性能瓶颈,连接条件下推 是一项关键的数据库优化技术。本文将以金仓数据库(KingbaseES)的实现为例,深入解析其原理,并通过多个代码场景展示其如何将查询性能提升数个数量级。 一、 性能瓶颈的根源:失效的谓词过滤 在金融、政务等复杂业务系统中,出于逻辑清晰和维护方便的考虑,开发人员常会编写多

By Ne0inhk
构建下一代 AIOps 监控系统:基于 Go 语言与 DeepSeek 大模型的深度实践

构建下一代 AIOps 监控系统:基于 Go 语言与 DeepSeek 大模型的深度实践

前言 在云计算与微服务架构日益复杂的当下,传统的基于静态阈值的服务器监控系统正面临严峻挑战。海量的告警噪音与滞后的故障定位能力,促使运维体系向 AIOps(人工智能运维)转型。本文将详细阐述如何利用高性能的 Go 语言结合 DeepSeek 大语言模型,从零构建一个具备智能分析能力的服务器监控探针。我们将深入探讨 Linux 内核信息采集机制、Go 语言并发编程模式以及大模型 API 的工程化集成。 第一章:基础设施环境构建与系统初始化 构建高效监控系统的基石在于一个稳定且配置得当的运行环境。本次实践基于 Ubuntu LTS(长期支持版)系列,涵盖 20.04 至 24.04 版本,这些版本提供了稳定的内核支持与广泛的软件包兼容性。 1.1 系统更新与依赖管理 在部署任何生产级软件之前,维持操作系统的最新状态是保障安全与稳定性的首要原则。通过包管理器 apt,系统能够从官方源获取最新的安全补丁与软件版本。 执行更新操作不仅仅是简单的软件升级,其背后涉及更新本地包索引数据库(apt update)以及根据依赖关系图谱进行二进制文件的替换(

By Ne0inhk