1. 匿名管道的限制

匿名管道存在以下核心限制:

  • 仅限亲缘关系进程:只能用于父子进程等有血缘关系的进程间通信(如通过 fork() 创建的子进程)。
  • 单向通信:数据只能单向流动(一端写,另一端读),双向通信需创建两个管道。
  • 临时性:存在于内存中,进程结束后自动销毁。
  • 缓冲区有限:大小固定(通常为一个内存页,如4KB),易写满阻塞。

引入命名管道的原因
为解决匿名管道的局限性,命名管道允许任意进程(无论是否有亲缘关系)通过文件系统路径访问,实现跨进程通信。


2. 什么是命名管道

命名管道(Named Pipe/FIFO)是一种特殊的文件类型,特点包括:

  • 文件系统可见:通过路径名(如 /tmp/myfifo)标识,任何进程可访问。
  • 遵循FIFO原则:数据按写入顺序读取,严格保持先进先出。
  • 突破亲缘限制:不相关进程可通过路径名打开同一管道通信。
  • 双向通信支持:部分场景下支持读写双向操作(需显式设计)。

示例:命名管道在文件系统中显示为特殊文件(权限位带 p,如 prw-r--r--)。


3. 如何创建命名管道

方法一:命令行创建

mkfifo <路径名>   # 例如:mkfifo /tmp/my_pipe

生成一个具名管道文件,权限默认受 umask 影响。

示例:

方法二:程序内创建

使用 mkfifo() 函数:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);  // 成功返回0,失败返回-1
  • 参数
    • pathname:管道路径(如 /tmp/my_pipe)。
    • mode:权限标志(如 0666 表示所有用户可读写)。
  • 后续操作
    • 需用 open() 打开管道(读模式 O_RDONLY 或写模式 O_WRONLY)。
    • 默认阻塞行为:读端打开时写端阻塞,反之亦然;可通过 O_NONBLOCK 设为非阻塞。

删除管道

  • 命令行:rm <路径名> 或 unlink <路径名>
  • 程序内:unlink(pathname)

4. 匿名管道和命名管道的区别

特性匿名管道命名管道证据来源
创建方式pipe(fd) 一步创建并打开mkfifo() 创建 + open() 打开
进程关系要求必须具有亲缘关系(如父子进程)任意进程均可访问
持久性随进程结束销毁文件系统持久,需手动删除
通信方向仅单向可支持双向通信
性能略快(无文件系统操作)稍慢(涉及磁盘索引节点)
使用场景短期亲缘进程通信长期/跨进程通信(如C/S架构)

关键补充

  • 语义一致性:打开后两者操作方式相同(如 read()/write())。
  • 网络支持:命名管道可跨机器通信,匿名管道仅限本地。
  • 阻塞行为:两者均受缓冲区影响,但命名管道可通过 O_NONBLOCK 灵活控制阻塞。

5. 命名管道的打开规则

一、为读而打开 FIFO(O_RDONLY

  1. O_NONBLOCK 未设置(默认阻塞)
    • 行为:调用 open() 会阻塞当前进程,直到有另一个进程为写而打开同一 FIFO
    • 原理:内核需确保存在数据生产者,否则读操作无意义。

"open以只读方式打开FIFO时,要阻塞到某个进程为写而打开此FIFO" 。
"若没有指定O_NONBLOCK,只读 open 要阻塞到某个其他进程为写而打开此 FIFO" 。

  1. O_NONBLOCK 设置(非阻塞)
    • 行为open() 立即成功返回(返回文件描述符),无论是否有写端打开
    • 后续注意:此时若管道无数据,read() 可能返回 0(EOF)或 EAGAIN 错误(见下文读写规则)。

"先以只读方式打开,如果没有进程已经为写而打开一个FIFO,只读 open() 成功,并且 open() 不阻塞" 。
"若指定了O_NONBLOCK,则只读 open 立即返回" 。


二、为写而打开 FIFO(O_WRONLY

  1. O_NONBLOCK 未设置(默认阻塞)
    • 行为:调用 open() 会阻塞当前进程,直到有另一个进程为读而打开同一 FIFO
    • 原理:内核需确保存在数据消费者,否则写操作可能无限等待。

"open以只写方式打开FIFO时,要阻塞到某个进程为读而打开此FIFO" 。
"只写 open 要阻塞到某个其他进程为读而打开它" 。

  1. O_NONBLOCK 设置(非阻塞)
    • 行为:若无读端已打开open() 立即失败,返回 -1 并设置错误码 ENXIO(表示设备不存在)。
    • 行为:若已有读端打开,则 open() 成功。

"先以只写方式打开,如果没有进程已经为读而打开一个FIFO,只写 open() 将出错返回 -1" 。
"若指定了O_NONBLOCK,则只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO" 。


三、关键补充与深度解析

  1. O_RDWR(读写模式)的特殊性

    • 行为:以 O_RDWR 模式打开时 永不阻塞,因进程自身已同时打开读写端 。
    • 风险:可能导致自我死锁(如写满后读阻塞),实践中极少使用。
  2. 读写操作的阻塞行为(与 open 独立)

    操作O_NONBLOCK 未设置O_NONBLOCK 设置
    read() 空管道阻塞直到有数据写入立即返回 EAGAIN(或空数据)
    write() 满管道阻塞直到有空间部分写入或返回 EAGAIN
  3. 管道断裂与信号处理

    • 写端关闭:读端 read() 返回 0(EOF),不阻塞 。
    • 读端关闭:写端 write() 触发 SIGPIPE 信号(默认终止进程),错误码 EPIPE 。
  4. 原子性与 PIPE_BUF

    • 规则:写入 ≤ PIPE_BUF 字节的数据保证原子性(不与其他进程交织)。
    • 典型值:Linux 中 PIPE_BUF 为 4096 字节(一页大小)。

 四、内核实现原理(选读)

  1. 阻塞的本质
    • 进程休眠在 FIFO inode 的等待队列中,由另一端打开或数据变动时唤醒 。
    • 示例:
      // Linux 内核片段(读打开阻塞逻辑)
      if (PIPE_READERS(*inode)++ == 0) wait_for_partner(inode, &PIPE_WCOUNTER(*inode)); // 等待写端
      
  1. 非阻塞的冲突处理
    • 写打开时若无读端,内核直接返回 ENXIO 而非加入等待队列 :

"若命名管道读端尚未打开,而 O_NONBLOCK=1,写端打开失败并释放资源" 。


总结与建议

场景打开模式O_NONBLOCK结果
读打开,无写端存在O_RDONLY未设置阻塞
读打开,无写端存在O_RDONLY设置立即成功
写打开,无读端存在O_WRONLY未设置阻塞
写打开,无读端存在O_WRONLY设置立即失败(ENXIO)
读写打开O_RDWR任意立即成功(不依赖外部进程)

工程建议

  1. 生产-消费模型:推荐读端阻塞打开(确保写端就绪),写端非阻塞打开(快速失败+重试逻辑)。
  2. 超时控制:若需阻塞但避免无限等待,结合 select()/poll() 设置超时。
  3. 错误处理:始终检查 open() 返回值和 errno,尤其非阻塞模式。

6. 代码示例

下面为了更好理解命名管道,我们直接来一段代码,使用命名管道让两个无血缘关系的进程进行通信——一个进程写一个进程读。

这里client.cc和server.cc代表两个没有血缘关系的进程,在前面学习进程时我们知道,.cc文件跑起来就是一个进程,所以这里不多赘述。而我们命名管道的创建,以及打开管道文件进行操作的代码则封装在comm.hpp中。Makefile则是我们配置的自动化工具。

下面我们就来在comm.hpp中将代码封装起来

首先需要将命名管道创建,最后结束通信后还需要将管道回收,因为命名管道不会随进程的生命周期,所以需要我们手动回收

代码如下:

class NamedFifo
{
public:NamedFifo(const std::string &path, const std::string &name): _path(path), _name(name){_filename = _path + "/" + _name;// 创建命名管道int n = mkfifo(_filename.c_str(), 0666);if(n < 0){std::cerr << "mkfifo failed" << std::endl;}else{std::cout << "mkfifo success" << std::endl;}}~NamedFifo(){// 回收命名管道int n = unlink(_filename.c_str());if(n < 0){std::cerr << "remove fifo failed" << std::endl;}else{std::cout << "remove fifo success" << std::endl;}}private:std::string _path;std::string _name;std::string _filename;
};

由于我们要实现一个进程写,一个进程读的单向通信,所以我们先规定,让客户端client.cc进程来写,服务端server.cc进程来读,那么读写操作我们还需要再封装一个类,因为我们只要创建一个管道就行了。

如果都封装在一个类中,那么客户端和服务端都需要实例化出一个对象,才能对管道读写通信,但这样就会创建两个命名管道了,因为只要构造函数就会创建命名管道,而我们不需要两个命名管道,我们只需要创建一个命名管道,然后服务端和客户端分别以读写的方式打开这个管道文件就可以进行通信了,所以我们可以再封装一个类来实现对打开的命名管道进行操作。

代码如下:

class Fileoper
{
public:Fileoper(const std::string &path, const std::string &name): _path(path), _name(name), _fd(-1){_filename = _path + "/" + _name;}void OpenForRead(){_fd = open(_filename.c_str(), O_RDONLY);if(_fd < 0){std::cerr << "open fifo failed" << std::endl;}else{std::cout << "open fifo success" << std::endl;}}void OpenForWrite(){_fd = open(_filename.c_str(), O_WRONLY);if(_fd < 0){std::cerr << "open fifo failed" << std::endl;}else{std::cout << "open fifo success" << std::endl;}}~Fileoper() {}private:std::string _path;std::string _name;std::string _filename;int _fd;
};

由于我们需要打开指定路径的管道文件,所以成员变量仍然需要和NamedFifo类一样,但是我们打开管道文件后,需要通过返回的文件描述符后续管理规管道文件,所以我们还需要一个成员变量_fd,来接收open返回的文件描述符。客户端需要从管道写入,服务端需要从管道读取,所以客户端以只写的方式打开管道文件,而服务端以只读的方式打开管道文件。但是打开之后我们客户端和服务端还需要对管道进行读写操作,所以我们还需要分别实现一个写函数和一个读函数

代码如下:

    void Write(){std::string message;while(true){std::cout << "Please Enter#";std::getline(std::cin, message);write(_fd, message.c_str(), message.size());}}void Read(){while(true){char buffer[1024];ssize_t n = read(_fd, buffer, sizeof(buffer)-1);if(n > 0){buffer[n] = 0;std::cout << "Client say#" << buffer << std::endl;}else if(n == 0){std::cout << "Client quit! me too!" << std::endl;break;}else{std::cerr << "read error" << std::endl;break;}}}

当然,通信结束之后我们需要关闭文件描述符

    void Close(){close(_fd);}

测试

我们先定义两个宏

#define PATH "."
#define FILENAME "fifo"

我们想要在当前路径下创建一个fifo的管道文件

服务端:

#include "comm.hpp"int main()
{// 创建管道NamedFifo f(PATH, FILENAME);// 文件操作Fileoper reader(PATH, FILENAME);reader.OpenForRead();reader.Read();reader.Close();return 0;
}

客户端:

#include "comm.hpp"int main()
{Fileoper Writer(PATH, FILENAME);Writer.OpenForWrite();Writer.Write();Writer.Close();   return 0;
}

运行测试:

可以看到成功实现了两个没有血缘关系的进程的单向通信

源码:

comm.hpp:

#pragma once#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>#define PATH "."
#define FILENAME "fifo"class NamedFifo
{
public:NamedFifo(const std::string &path, const std::string &name): _path(path), _name(name){_filename = _path + "/" + _name;// 创建命名管道int n = mkfifo(_filename.c_str(), 0666);if (n < 0){std::cerr << "mkfifo failed" << std::endl;}else{std::cout << "mkfifo success" << std::endl;}}~NamedFifo(){// 回收命名管道int n = unlink(_filename.c_str());if (n < 0){std::cerr << "remove fifo failed" << std::endl;}else{std::cout << "remove fifo success" << std::endl;}}private:std::string _path;std::string _name;std::string _filename;
};class Fileoper
{
public:Fileoper(const std::string &path, const std::string &name): _path(path), _name(name), _fd(-1){_filename = _path + "/" + _name;}void OpenForRead(){_fd = open(_filename.c_str(), O_RDONLY);if(_fd < 0){std::cerr << "open fifo failed" << std::endl;}else{std::cout << "open fifo success" << std::endl;}}void OpenForWrite(){_fd = open(_filename.c_str(), O_WRONLY);if(_fd < 0){std::cerr << "open fifo failed" << std::endl;}else{std::cout << "open fifo success" << std::endl;}}void Write(){std::string message;while(true){std::cout << "Please Enter#";std::getline(std::cin, message);write(_fd, message.c_str(), message.size());}}void Read(){while(true){char buffer[1024];ssize_t n = read(_fd, buffer, sizeof(buffer)-1);if(n > 0){buffer[n] = 0;std::cout << "Client say#" << buffer << std::endl;}else if(n == 0){std::cout << "Client quit! me too!" << std::endl;break;}else{std::cerr << "read error" << std::endl;break;}}}void Close(){close(_fd);}~Fileoper() {}private:std::string _path;std::string _name;std::string _filename;int _fd;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/92137.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/92137.shtml
英文地址,请注明出处:http://en.pswp.cn/web/92137.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python Day24 多线程编程:核心机制、同步方法与实践案例

一、线程事件对象&#xff08;threading.Event&#xff09;threading.Event 用于实现线程间的通信&#xff0c;可让一个线程通知其他线程终止任务&#xff0c;核心是通过 “事件触发” 机制协调线程行为。核心方法&#xff1a;创建事件对象&#xff1a;event threading.Event(…

007 前端( JavaScript HTML DOM+Echarts)

一.html dom运用查找html元素的三种方式通过 id 找到 HTML 元素通过标签名找到 HTML 元素通过类名找到 HTML 元素1.通过 id 找到 HTML 元素<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>msf的网页</title> <…

实习文档背诵

实习内容:1.定时任务与数据补全:基于 XXL-JOB 实现分布式定时任务调度&#xff0c;补全近半年历史操作日志数据&#xff0c;有效解决因网络异常导致的数据缺失问题。业务场景&#xff1b;集团的4a日志半年内没有同步&#xff0c;这边需要把日志数据同步到集团上首先先评估每天的…

分布式CAP定理

CAP 定理在一个分布式系统中&#xff0c;以下三个特性不可能同时完全满足&#xff0c;最多只能满足其中两个&#xff1a;C&#xff08;Consistency&#xff0c;一致性&#xff09;&#xff1a;所有节点在同一时间看到的数据是完全一致的&#xff08;即更新操作成功并返回后&…

PHP-Casbin:现代化 PHP 应用的权限管理引擎

在当今复杂的Web应用中&#xff0c;精细化的权限管理是保障系统安全的关键环节。PHP-Casbin 作为Casbin生态的PHP实现&#xff0c;凭借其灵活的模型支持和强大的扩展能力&#xff0c;已成为PHP开发者实现访问控制的首选工具。 超越传统权限模型 PHP-Casbin 基于PERM&#xff…

FastDeploy2.0:环境变量的说明

一、执行# 设置日志目录 export FD_LOG_DIR/workspace/models/log# 指定使用的 GPU 设备 export CUDA_VISIBLE_DEVICES0,1,2,3# 创建日志目录&#xff08;如果不存在&#xff09; mkdir -p "$FD_LOG_DIR"# 定义日志文件路径 LOG_FILE"$FD_LOG_DIR/fastdeploy_se…

C语言:指针(1-2)

5. 指针运算指针的基本运算有三种&#xff0c;分别是&#xff1a;指针-整数指针-指针指针的关系运算5.1 指针运算在上面&#xff0c;我们知道&#xff0c;数组在内存中是连续存放的&#xff0c;只要知道第一个元素的地址&#xff0c;顺藤摸瓜就能找到后面的所有元素。那么&…

【多模态】DPO学习笔记

DPO学习笔记1 原理1.0 名词1.1 preference model1.2 RLHF1.3 从RLHF到DPOA.解的最优形式B. DPO下参数估计C. DPO下梯度更新D. DPO训练的稳定性2 源代码2.1 数据集构成2.2 计算log prob2.3 DPO loss1 原理 1.0 名词 preference model&#xff1a;对人类偏好进行建模&#xff0…

2025最新、UI媲美豆包、DeepSeek等AI大厂的AIGC系统 - IMYAI源码部署教程

IMYAI 系统部署与使用手册 一、系统演示 &#x1f539; 快速体验 前端演示地址&#xff1a;https://super.imyaigc.com后台演示地址&#xff1a;https://super.imyaigc.com/settings &#x1f539; 技术架构 前端&#xff1a;Vite Vue3 NaiveUI TailwindCSS Plyr后端&…

【关于Java的反射】

在 Java 编程中&#xff0c;反射&#xff08;Reflection&#xff09; 是一个非常强大的工具&#xff0c;它允许你在运行时动态地获取类的信息、创建对象、调用方法和访问字段。虽然反射功能强大&#xff0c;但它也有一些局限性和性能开销&#xff0c;因此需要谨慎使用。一、什么…

Gitee推出“移动软件工厂“解决方案 解决嵌入式与涉密场景研发困局

Gitee推出"移动软件工厂"解决方案 破解嵌入式与涉密场景研发困局 随着数字化转型浪潮的推进&#xff0c;软件开发正面临着前所未有的复杂环境挑战。特别是在嵌入式系统、FPGA开发以及涉密信息系统等特殊场景下&#xff0c;研发团队往往需要在高安全要求与有限网络环境…

低功耗16*8位四线串行8*4按键阵矩LED驱动专用电路

概述&#xff1a;PC0340是占空比可调的LED显示控制驱动电路。由16根段输出、8根位输出、数字接口、数据锁存器、显示存储器、键扫描电路及相关控制电路组成了一个高可靠性的单片机外围LED驱动电路。串行数据通过4线串行接口输入到PC0340&#xff0c;采用LQFP44L的封装形式。本产…

通过自定义注解加aop切面实现权限控制

前言&#xff1a;自定义注解&#xff0c;通过aop切面前置通知&#xff0c;对请求接口进行权限控制1&#xff0c;创建枚举类package org.springblade.sample.annotationCommon;import lombok.AllArgsConstructor; import lombok.Getter;import java.util.Arrays; import java.ut…

IDS知识点

在网络安全工程师、系统运维工程师等岗位的面试中&#xff0c;​​IDS&#xff08;Intrusion Detection System&#xff0c;入侵检测系统&#xff09;​​ 是高频考点&#xff0c;尤其是对网络安全防护、安全监控类岗位。以下是IDS的核心考点和必须掌握的知识点&#xff0c;按优…

Adobe Analytics 数据分析平台|全渠道客户行为分析与体验优化

Adobe Analytics 是业界领先的数据分析平台&#xff0c;帮助企业实时追踪客户行为&#xff0c;整合多渠道数据&#xff0c;通过强大的分析与可视化工具深入分析客户旅程&#xff0c;优化数字体验。结合 Adobe Experience Cloud&#xff0c;Adobe Analytics 成为推动数字化增长和…

【轮播图】H5端轮播图、横向滑动、划屏效果实现方案——Vue3+CSS position/CSS scroller

文章目录定位实现滑屏效果前置知识CSS: touch-action属性CSS: transform属性触摸事件forEach回调占位符准备阶段实现移动效果实现跟手效果触摸结束优化完整代码滚动实现滑屏效果前置知识CSS: scroll-snap-type属性准备阶段实现滑动效果实现吸附效果滚动条隐藏存在问题完整代码s…

忘记了WordPress管理员密码的找回方法

WordPress管理员密码找回方法 如果您忘记了WordPress管理员密码&#xff0c;可以通过以下几种方法找回或重置&#xff1a; 方法1&#xff1a;通过电子邮件重置(最简单) 访问您的WordPress登录页面(通常是wodepress.com/wp-admin或wodepress.com/wp-login.php) 点击”忘记密…

RAFT:让语言模型更聪明地用文档答题

RAFT&#xff1a;让语言模型更聪明地用文档答题 作者注&#xff1a; 本文旨在面向零基础读者介绍 UC Berkeley 提出的 RAFT&#xff08;Retrieval-Augmented Fine-Tuning&#xff09;方法。它是一种训练语言模型的新方式&#xff0c;让模型更好地利用“外部知识”——比如文档、…

【紧急预警】NVIDIA Triton推理服务器漏洞链可导致RCE!

2025 年 8 月 4 日消息&#xff0c;NVIDIA 旗下的 Triton 推理服务器&#xff08;一款支持 Windows 和 Linux 系统、用于大规模运行 AI 模型的开源平台&#xff09;被曝出一系列安全漏洞。这些漏洞一旦被利用&#xff0c;攻击者有可能完全接管存在漏洞的服务器。 Wiz 安全公司…

基于深度学习的医学图像分析:使用PixelCNN实现医学图像生成

前言 医学图像分析是计算机视觉领域中的一个重要应用&#xff0c;特别是在医学图像生成任务中&#xff0c;深度学习技术已经取得了显著的进展。医学图像生成是指通过深度学习模型生成医学图像&#xff0c;这对于医学研究、疾病模拟和图像增强等任务具有重要意义。近年来&#x…