Linux系统:进程间通信-匿名与命名管道

news2025/6/10 8:02:45

本节重点

  • 匿名管道的概念与原理
  • 匿名管道的创建
  • 命名管道的概念与原理
  • 命名管道的创建
  • 两者的差异与联系
  • 命名管道实现EchoServer

一、管道

管道(Pipe)是一种进程间通信(IPC, Inter-Process Communication)机制,用于在不同进程之间传递数据。它是一种抽象的数据通道,允许一个进程的输出直接作为另一个进程的输入,类似于现实中的管道将水流从一个地方输送到另一个地方。管道是操作系统提供的一种高效、轻量级的通信方式,广泛应用于命令行工具、父子进程协作等场景。

在Linux中我们知道,当一个指令运行起来时就是一个进程。who命令用来显示当前登录系统的用户信息,而wc是一个统计工具其中-l选项表示统计行数。当两个指令分别运行时就是两个进程,我们可以通过 | (管道)将who指令运行的结果传递给wc,此时wc就可以帮我们统计当前登录系统的用户个数。

who | wc -l

可以用下图来表示这种关系:

 

二、匿名管道

匿名管道(Anonymous Pipe)是一种进程间通信(IPC, Inter-Process Communication)机制,用于在具有亲缘关系的进程(如父子进程或兄弟进程)之间传递数据。它是一种单向的、半双工的通信通道,数据只能在一个方向上流动(若需双向通信,需创建两个管道)。匿名管道没有名字标识,因此仅适用于具有亲缘关系的进程,无法用于无关进程间的通信。

半双工与全双工:

半双工通信允许数据在两个方向上传输,但同一时刻只能单向传输。即通信双方可以轮流发送和接收数据,但不能同时进行。

全双工通信允许数据在两个方向上同时传输。即通信双方可以同时发送和接收数据。

特性半双工全双工
传输方向同一时刻只能单向传输双向同时传输
效率较低(需切换方向)较高(无需切换方向)
延迟较高(方向切换可能引入延迟)较低(无方向切换)
资源占用较低(单方向占用资源)较高(双方向占用资源)
应用场景对讲机、老式电台、早期网络电话、现代网络、USB接口

2.1 管道的创建

在Linux系统中我们可以系统调用pipe来创建管道文件,返回值是两个文件描述符分别表示文件的读端与写端。

函数原型:

#include <unistd.h>
int pipe(int fd[2]);

参数解析:

  • fd 是一个长度为2的整数数组,用来存储管道文件的读端与写端的文件描述符
  • fd[ 0 ]:管道的读端(用于读取数据)。
  • fd[ 1 ]:管道的写端(用于写入数据)。

返回值:成功时返回 0,失败时返回 -1 并设置 errno。

数据传输的特点:

  • 写操作:进程通过write(pipefd[1], data, len)将数据拷贝到内核缓冲区。若缓冲区满,写进程阻塞(默认阻塞模式)。
  • 读操作:进程通过read(pipefd[0], buffer, size)从内核缓冲区拷贝数据到用户空间。若缓冲区空,读进程阻塞(默认阻塞模式)

管道的关闭:

  •  读端先关闭:此时写入无意义,操作系统会向写进程发送SIGPIPE信号终止进程。
  • 写段先关闭:此时读进程会读到EOF,用于指示文件或数据流已到达末尾。

代码示例:父子进程通过匿名管道实现进程间通信

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
    int fd[2] = {0};
    int ret = pipe(fd);
    if (ret == -1)
    {
        printf("pipe error!\n");
    }
    pid_t id = fork();
    if (id > 0)
    {
        // 父进程读
        close(fd[1]);
        while (1)
        {
            char read_buf[1024];
            int ret = read(fd[0], read_buf, sizeof(read_buf));
            if (ret > 0)
            {
                read_buf[ret] = '0';
                printf("child say: %s", read_buf);
            }
            else if (ret == 0)
            {
                // 写端退出:
                break;
            }
            else
            {
                printf("read error!\n");
            }
        }
        //退出前记得回收子进程
        waitpid(id,NULL,0);
    }
    else if (id == 0)
    {
        // 子进程写
        close(fd[0]);

        int cnt=5;
        while (cnt)
        {
            char buffer[1024];
            read(0, buffer, sizeof(buffer));
            int ret = write(fd[1], buffer, sizeof(buffer));
            cnt--;
        }
        exit(1);
    }
    else
    {
        // 子进程创建失败:
        printf("fork error!\n");
    }
    return 0;
}

代码说明:

当父进程调用pipe系统调用时,内核会创建一个管道文件(或开辟一段环形缓冲区)并暴露读端与写端。进程通过分配文件描述符来控制管道的读写操作,我们通过一个整数数组来记录两个文件描述符,通常下标为0的元素记录读端的文件描述符,下标为1的元素记录写端的文件描述符。

之后我们fork创建子进程时操作系统会拷贝一份文件描述符表继承给子进程,此时父子进程就可以看到同一个管道文件。之后父子进程根据读写工作的分配关闭不需要的文件描述符后就可以进行进程间通信了。

结束进程间通信时可以关闭写进程,此时读进程就会读到EOF也就是文件末尾,此时read的返回值为0,我们可以根据read的返回值判断写端是否关闭来结束读端。最后不要忘记父进程等待子进程的返回结果。

 2.2 原理与特性

2.2.1 工作原理

匿名管道本质是内核维护的环形缓冲区(或文件),用于临时存储待传输的数据。缓冲区大小由系统决定(如Linux默认通常为64KB),数据以先进先出(FIFO)方式处理。与以往的缓冲区不同的是这类缓冲区由内核提供且不会刷盘(将数据刷新到磁盘)。

2.2.2 基础特性

单向通信

默认是半双工(单向),若需双向通信需创建两个管道(如pipe1pipe2)。

示例:

  • 进程A通过pipe1写,进程B通过pipe1读(A→B)。
  • 进程B通过pipe2写,进程A通过pipe2读(B→A)。

阻塞行为

  • 默认阻塞:读写操作在缓冲区满/空时阻塞。
  • 非阻塞模式:通过fcntl(fd, F_SETFL, O_NONBLOCK)设置后,若缓冲区满/空,读写操作立即返回错误(EAGAINEWOULDBLOCK)。

亲缘关系限制

仅适用于父子进程或兄弟进程(通过fork()派生)。无关进程无法直接访问匿名管道(因无名字标识符)。

数据拷贝开销

数据需在用户空间和内核空间之间拷贝两次(写→内核缓冲区,内核缓冲区→读),可能影响性能。

缓冲区容量限制

若写进程速率远高于读进程,缓冲区可能填满,导致写进程阻塞。

三、命名管道

命名管道(Named Pipe),也称为FIFO(First In First Out,先进先出),是一种进程间通信(IPC,Inter-Process Communication)机制,允许不相关的进程通过文件系统中的一个特殊文件(即命名管道文件)进行数据交换。与匿名管道(Anonymous Pipe)不同,命名管道具有一个明确的名称,因此可以在不同进程之间、甚至不同用户或不同主机之间(通过网络命名管道实现)进行通信。

3.1 命名管道的创建

在Linux系统中我们可以通过系统调用mkfifo来创建和设置命名管道,具体的函数原型以及参数解析如下:

函数原型:

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

参数解析:

  • pathname:指定命名管道的路径名,可以是相对路径或绝对路径,默认在当前路径。
  • mode:用于指定管道的权限,是一个八进制数,表示文件的权限掩码。

 通常在设置管道文件权限之前,我们将文件掩码暂时设为0。

 返回值与错误处理:

  • 成功时返回 0,失败时返回 -1 并设置 errno 以指示错误原因。
  • 常见错误码
    • EACCES:参数 pathname 所指定的目录路径无可执行的权限。
    • EEXIST:参数 pathname 所指定的文件已存在。
    • ENAMETOOLONG:参数 pathname 的路径名称太长。
    • ENOENT:参数 pathname 包含的目录不存在。
    • ENOSPC:文件系统的剩余空间不足。
    • ENOTDIR:参数 pathname 路径中的目录存在但却非真正的目录。
    • EROFS:参数 pathname 指定的文件存在于只读文件系统内。

代码示例:客户端与服务端通过命名管道实现进程间通信

//Common.hpp
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

class Name_Fifo
{
public:
    Name_Fifo(const std::string path, const std::string name)
        : _Name(name), _Path(path)
    {
        umask(0);
        _fifoname = _Path + "/" + _Name;
        int ret = mkfifo(_fifoname.c_str(), 0666);
        if (ret == 0)
        {
            std::cout << "mkfifo success!\n"
                      << std::endl;
        }
        else
        {
            std::cout << "mkfifo fail!\n"
                      << std::endl;
            exit(1);
        }
    }
    ~Name_Fifo()
    {
        unlink(_fifoname.c_str());
    }

private:
    std::string _Path;
    std::string _Name;
    std::string _fifoname;
};

// 对指定命名管道进行的操作
class oper_fifo
{
public:
    oper_fifo(const std::string path, const std::string name)
        : _Path(path), _Name(name), _fd(-1)
    {
        _fifoname = _Path + "/" + _Name;
    }
    ~oper_fifo()
    {
    }
    void OpenForRead()
    {
        _fd = open(_fifoname.c_str(), O_RDONLY);
        if (_fd < 0)
        {
            std::cout << "open fifo failed!" << std::endl;
            exit(2);
        }
        else
        {
            std::cout << "open fifo success!" << std::endl;
        }
    }
    void OpenForWrite()
    {
        _fd = open(_fifoname.c_str(), O_WRONLY);
        if (_fd < 0)
        {
            std::cout << "open fifo failed!" << std::endl;
            exit(2);
        }
        else
        {
            std::cout << "open fifo success!" << std::endl;
        }
    }
    void Read()
    {
        // 具体的读取操作在这里定义
        while (true)
        {
            char rd_buffer[1024];
            int ret = read(_fd, rd_buffer, sizeof(rd_buffer) - 1);
            if (ret == 0)
            {
                // 读到了文件末尾表示写入端退出:
                std::cout << "write exit me too!" << std::endl;
                break;
            }
            else if (ret < 0)
            {
                std::cout << "read failed!" << std::endl;
            }
            else
            {
                rd_buffer[ret] = '0';
                std::cout << "Client say:" << rd_buffer << std::endl;
            }
        }
    }
    void Write()
    {
        // 具体的写入操作在这里定义
        while (1)
        {
            std::cout<<"Please Enter:"<<std::endl;
            std::string messager;
            int cnt = 1;
            pid_t id = getpid();
            std::getline(std::cin, messager);
            messager += (", message number: " + std::to_string(cnt++) + ", [" + std::to_string(id) + "]");
            int ret=write(_fd,messager.c_str(),messager.size());
        }
    }
    void Close()
    {
        if(_fd>0)
        {
            close(_fd);
        }
    }
private:
    std::string _Path;
    std::string _Name;
    std::string _fifoname;
    int _fd; // 命名管道的文件描述符
};
//Server.cc
#include"Common.hpp"


int main()
{
    //创建管道:
    Name_Fifo Fifo(".","myfifo");
    
   oper_fifo my_read(".","myfifo");
   my_read.OpenForRead();
   my_read.Read();
   my_read.Close();
    return 0;
//Client.cc
#include"Common.hpp"


int main()
{
    oper_fifo my_write(".","myfifo");
    my_write.OpenForWrite();
    my_write.Write();
    my_write.Close();
    return 0;
}

代码说明:

在我们的代码中,Common.hpp中定义了两个类一个是命名管道Name_Fifo,一个是对命名管道的操作oper_fifo。在服务端我们创建命名管道后进行读操作,此时由于写端还没有将管道文件打开服务端会阻塞于my_read.OpenForRead();代码处。

这时我们运行客户端代码,需要注意的是客户端不再需要进行mkfifo而是直接对创建的命名管道进行写操作。此时当客户端以写方式打开命名管道时并等待用户输入时,读端才会解除阻塞状态。

3.2 原理与特性

3.2.1 核心特性

命名管道在文件系统中以特殊文件形式存在,进程通过路径名访问,无需亲缘关系(如父子进程)。

单个命名管道默认是单向的,但可通过创建两个管道(一个读、一个写)实现双向通信。

读操作:若管道无数据,读取进程会阻塞,直到有数据写入。写操作:若管道缓冲区满,写入进程会阻塞,直到有空间可用。同步:通过内核的等待队列管理阻塞进程,确保数据有序传输。

命名管道独立于创建它的进程,即使进程退出,管道仍存在,直到被显式删除(如Linux的unlink)。

3.2.2 工作原理

创建与初始化

通过mkfifomknod创建,内核在文件系统中生成一个inode,但不占用磁盘空间,仅维护管道的元数据(如缓冲区大小、等待队列)。

数据传输流程

写入端:进程调用write,数据被复制到内核缓冲区。若缓冲区满,写入进程阻塞。

读取端:进程调用read,从内核缓冲区复制数据到用户空间。若缓冲区空,读取进程阻塞。

内核缓冲:数据在内核中临时存储,确保读写操作的原子性。

进程阻塞与唤醒

内核通过等待队列管理阻塞的读写进程。当数据可用或缓冲区有空间时,内核唤醒对应的进程。

关闭与清理

所有读写端关闭后,管道被销毁(Linux)或标记为无效(Windows)。

四、区别与联系 (命名&匿名)

4.1 区别

特性命名管道(Named Pipe)匿名管道(Anonymous Pipe)
命名方式通过文件系统路径命名(如/tmp/pipe\\.\pipe\name无名称,仅通过文件描述符(如fd[0]fd[1])引用
进程关系不相关进程可通过路径名通信仅限亲缘进程(如父子进程、兄弟进程)
持久性独立于进程存在,需显式删除(如unlink随进程退出而自动销毁
网络通信支持(Windows可通过网络路径访问)不支持,仅限本地进程
安全性支持访问控制(如Windows的ACL)无内置安全机制,依赖进程间信任
实现复杂度需创建管道文件(mkfifoCreateNamedPipe简单,通过pipe()系统调用自动创建
数据方向默认半双工(可通过双向管道模拟全双工)半双工,需两个管道实现双向通信
可见性在文件系统中可见(如ls命令可列出)不可见,仅在内核中维护
典型应用场景跨进程、跨网络通信(如日志服务、远程服务)本地进程间短生命周期通信(如父子进程协作)

命名与路径

命名管道通过文件系统路径标识,类似普通文件,但仅用于通信。匿名管道无路径,通过文件描述符传递,通常由pipe()系统调用创建。

进程关系

命名管道允许无亲缘关系的进程通信(如服务进程与客户端)。匿名管道仅限有亲缘关系的进程(如fork()创建的子进程)。

持久性与清理

命名管道需手动删除(如Linux的unlink或Windows的CloseHandle)。匿名管道随进程退出自动销毁,无需额外清理。

 4.2 共同联系

特性命名管道与匿名管道的共同点
内核机制均依赖内核缓冲区存储数据,支持阻塞式I/O
数据传输方式均为字节流(默认,可通过消息模式扩展)
同步机制均通过内核的等待队列管理阻塞的读写进程
进程通信模型均属于进程间通信(IPC)机制
性能开销均涉及用户态与内核态的上下文切换
适用场景均适用于进程间数据交换(匿名适合短生命周期,命名适合长生命周期或跨进程)

4.3 总结

命名管道更灵活,适合跨进程、跨网络通信,但需手动管理生命周期。

匿名管道更简单,适合亲缘进程间短生命周期通信,无需额外清理。
根据场景选择合适的管道类型,可平衡复杂度与功能需求。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用python进行图像处理—图像变换(6)

图像变换是指改变图像的几何形状或空间位置的操作。常见的几何变换包括平移、旋转、缩放、剪切&#xff08;shear&#xff09;以及更复杂的仿射变换和透视变换。这些变换在图像配准、图像校正、创建特效等场景中非常有用。 6.1仿射变换(Affine Transformation) 仿射变换是一种…

使用homeassistant 插件将tasmota 接入到米家

我写一个一个 将本地tasmoat的的设备同通过ha集成到小爱同学的功能&#xff0c;利用了巴法接入小爱的功能&#xff0c;将本地mqtt转发给巴法以实现小爱控制的功能&#xff0c;前提条件。1需要tasmota 设备&#xff0c; 2.在本地搭建了mqtt服务可&#xff0c; 3.搭建了ha 4.在h…

【笔记】结合 Conda任意创建和配置不同 Python 版本的双轨隔离的 Poetry 虚拟环境

如何结合 Conda 任意创建和配置不同 Python 版本的双轨隔离的Poetry 虚拟环境&#xff1f; 在 Python 开发中&#xff0c;为不同项目配置独立且适配的虚拟环境至关重要。结合 Conda 和 Poetry 工具&#xff0c;能高效创建不同 Python 版本的 Poetry 虚拟环境&#xff0c;接下来…

多模态学习路线(2)——DL基础系列

目录 前言 一、归一化 1. Layer Normalization (LN) 2. Batch Normalization (BN) 3. Instance Normalization (IN) 4. Group Normalization (GN) 5. Root Mean Square Normalization&#xff08;RMSNorm&#xff09; 二、激活函数 1. Sigmoid激活函数&#xff08;二分类&…

AWSLambda之设置时区

目标 希望Lambda运行的时区是东八区。 解决 只需要设置lambda的环境变量TZ为东八区时区即可&#xff0c;即Asia/Shanghai。 参考 使用 Lambda 环境变量

RFID推动新能源汽车零部件生产系统管理应用案例

RFID推动新能源汽车零部件生产系统管理应用案例 一、项目背景 新能源汽车零部件场景 在新能源汽车零部件生产领域&#xff0c;电子冷却水泵等关键部件的装配溯源需求日益增长。传统 RFID 溯源方案采用 “网关 RFID 读写头” 模式&#xff0c;存在单点位单独头溯源、网关布线…

[C++错误经验]case语句跳过变量初始化

标题&#xff1a;[C错误经验]case语句跳过变量初始化 水墨不写bug 文章目录 一、错误信息复现二、错误分析三、解决方法 一、错误信息复现 write.cc:80:14: error: jump to case label80 | case 2:| ^ write.cc:76:20: note: crosses initialization…

Unity-ECS详解

今天我们来了解Unity最先进的技术——ECS架构&#xff08;EntityComponentSystem&#xff09;。 Unity官方下有源码&#xff0c;我们下载源码后来学习。 ECS 与OOP&#xff08;Object-Oriented Programming&#xff09;对应&#xff0c;ECS是一种完全不同的编程范式与数据架构…

uni-app学习笔记二十七--设置底部菜单TabBar的样式

官方文档地址&#xff1a;uni.setTabBarItem(OBJECT) | uni-app官网 uni.setTabBarItem(OBJECT) 动态设置 tabBar 某一项的内容&#xff0c;通常写在项目的App.vue的onLaunch方法中&#xff0c;用于项目启动时立即执行 重要参数&#xff1a; indexnumber是tabBar 的哪一项&…

7种分类数据编码技术详解:从原理到实战

在数据分析和机器学习领域&#xff0c;分类数据&#xff08;Categorical Data&#xff09;的处理是一个基础但至关重要的环节。分类数据指的是由有限数量的离散值组成的数据类型&#xff0c;如性别&#xff08;男/女&#xff09;、颜色&#xff08;红/绿/蓝&#xff09;或产品类…

【字节拥抱开源】字节团队开源视频模型 ContentV: 有限算力下的视频生成模型高效训练

本项目提出了ContentV框架&#xff0c;通过三项关键创新高效加速基于DiT的视频生成模型训练&#xff1a; 极简架构设计&#xff0c;最大化复用预训练图像生成模型进行视频合成系统化的多阶段训练策略&#xff0c;利用流匹配技术提升效率经济高效的人类反馈强化学习框架&#x…

本地部署drawDB结合内网穿透技术实现数据库远程管控方案

文章目录 前言1. Windows本地部署DrawDB2. 安装Cpolar内网穿透3. 实现公网访问DrawDB4. 固定DrawDB公网地址 前言 在数字化浪潮席卷全球的背景下&#xff0c;数据治理能力正日益成为构建现代企业核心竞争力的关键因素。无论是全球500强企业的数据中枢系统&#xff0c;还是初创…

可视化预警系统:如何实现生产风险的实时监控?

在生产环境中&#xff0c;风险无处不在&#xff0c;而传统的监控方式往往只能事后补救&#xff0c;难以做到提前预警。但如今&#xff0c;可视化预警系统正在改变这一切&#xff01;它能够实时收集和分析生产数据&#xff0c;通过直观的图表和警报&#xff0c;让管理者第一时间…

多模态大语言模型arxiv论文略读(112)

Assessing Modality Bias in Video Question Answering Benchmarks with Multimodal Large Language Models ➡️ 论文标题&#xff1a;Assessing Modality Bias in Video Question Answering Benchmarks with Multimodal Large Language Models ➡️ 论文作者&#xff1a;Jea…

【向量库】Weaviate概述与架构解析

文章目录 一、什么是weaviate二、High-Level Architecture1. Core Components2. Storage Layer3. 组件交互流程 三、核心组件1. API Layer2. Schema Management3. Vector Indexing3.1. 查询原理3.2. 左侧&#xff1a;Search Process&#xff08;搜索流程&#xff09;3.3. 右侧&…

统计按位或能得到最大值的子集数目

我们先来看题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;请你找出 nums 子集 按位或 可能得到的 最大值 &#xff0c;并返回按位或能得到最大值的 不同非空子集的数目 。 如果数组 a 可以由数组 b 删除一些元素&#xff08;或不删除&#xff09;得到&#xff0c;…

vue3 手动封装城市三级联动

要做的功能 示意图是这样的&#xff0c;因为后端给的数据结构 不足以使用ant-design组件 的联动查询组件 所以只能自己分装 组件 当然 这个数据后端给的不一样的情况下 可能组件内对应的 逻辑方式就不一样 毕竟是 三个 数组 省份 城市 区域 我直接粘贴组件代码了 <temp…

Linux【5】-----编译和烧写Linux系统镜像(RK3568)

参考&#xff1a;讯为 1、文件系统 不同的文件系统组成了&#xff1a;debian、ubuntu、buildroot、qt等系统 每个文件系统的uboot和kernel是一样的 2、源码目录介绍 目录 3、正式编译 编译脚本build.sh 帮助内容如下&#xff1a; Available options: uboot …

Heygem50系显卡合成的视频声音杂音模糊解决方案

如果你在使用50系显卡有杂音的情况&#xff0c;可能还是官方适配问题&#xff0c;可以使用以下方案进行解决&#xff1a; 方案一&#xff1a;剪映替换音色&#xff08;简单适合普通玩家&#xff09; 使用剪映换音色即可&#xff0c;口型还是对上的&#xff0c;没有剪映vip的&…