Linux进程间通信(IPC)机制之一:管道(Pipes)详解

                                               🎬慕斯主页:修仙—别有洞天

                                              ♈️今日夜电波:Nonsense—Sabrina Carpenter

                                                                0:50━━━━━━️💟──────── 2:43
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


 

目录

进程间通信介绍

进程间通信目的

进程间通信分类

什么是管道?

管道详解

匿名管道

匿名管道的创建

匿名管道的特性与情况

命名管道

指令级

代码级

🌰 


进程间通信介绍

进程间通信目的

数据传输:一个进程需要将它的数据发送给另一个进程

资源共享:多个进程之间共享同样的资源。

通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信分类


管道

匿名管道、pipe、命名管道

System V IPC

System V 消息队列、System V 共享内存、System V 信号量

POSIX IPC

消息队列、共享内存、信号量、互斥量、条件变量、读写锁

        以下是一些主要的进程间通信方式:

  • 管道(Pipes):包括无名管道和命名管道。无名管道是半双工的,数据只能单向流动,通常用于有亲缘关系的进程间通信,如父子进程之间。命名管道则允许无亲缘关系的进程间通信。
  • 消息队列(Message Queues):消息队列是由消息的链表组成,存放在内核中并由消息队列标识符标识。它允许进程之间发送格式化的消息。
  • 信号量(Semaphores):信号量是一个计数器,可以用来控制多个进程对共享资源的访问,实现进程间的同步。
  • 共享内存(Shared Memory):共享内存允许多个进程访问同一块内存区域,从而快速地共享数据。
  • 套接字(Sockets):套接字支持不同主机上的两个进程进行通信,常用于网络编程中。它实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信。


什么是管道?

        管道是一种在Linux中用于进程间通信的机制,它可以将一个进程的输出作为另一个进程的输入

        管道的概念来源于日常生活中的水管,就像水管可以将水从一个地方输送到另一个地方一样,管道在Linux系统中用于传递数据流。具体来说,管道可以分为两类:

  • 无名管道(匿名管道):这是最初UNIX系统中使用的管道形式,通常用于有亲缘关系的进程间通信,如父子进程。无名管道是通过系统调用pipe()创建的,并且它们是半双工的,意味着数据只能在一个方向上流动。
  • 命名管道(也称为FIFO):与无名管道不同,命名管道可以在不相关的进程之间进行通信。它们通过文件系统创建,并具有路径名,因此可以被任何知道该路径名的进程访问。

        管道的大小是固定的,并且在创建时就已经确定。在Linux中,管道的大小是可以调整的,但是这通常需要重新编译内核或使用特定的系统调用来改变。

        总结:我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

管道详解

匿名管道

        通常一个进程管理文件是通过PCB控制对应的struct files_struct 结构体,然后在struct files_struct 结构体中会存在一个struct files *fd_array[]存放着该进程打开文件的struct files,接着再通过对应的struct files指向对应的缓冲区等等,大致过程如下:

        而当我们通过fork()创建子进程后,新创建的子进程会根据父进程的模板拷贝相关的内核数据结构,其中会经过类似“浅拷贝”的过程,使得子进程会指向内存中已经存在的且被父进程指向的文件,这就创建了两个进程指向同一份文件的效果

        而实际上,我们的同一个进程如果要对一个文件进行读写,一个struct file是不够的,这是因为我们在读或者写的位置可能是不同的,我们需要控制对应的读写,其中会包含对应的变量来保存读或者写的位置。因此是需要创建两个对应的 struct file来控制读写,只不过他们会指向同一个inode、同一个方法集、同一个缓冲区。你也会发现,当父子进程对屏幕用printf打印时,可能会出现数据错乱的情况,这是因为子进程发生了“浅拷贝”,双方的printf都是打印到同一个文件。那么当我们对一个文件进行读写,并且创建子进程的时候,子进程与父进程都会通过同样的两个 struct file指向同一个文件,当我们将他们间一个的读 struct file关闭,另一个的写 struct file关闭,这不就形成了一个进程连接到另一个进程的一个数据流吗?这就是管道的原理。当然, struct file中会有一个类似“引用计数”的功能来控制是否关闭对应的struct file。如下图:

 

匿名管道的创建

        对此,理解了上面的过程后,我们继续理解接下来的创建管道的操作,系统中提供了对应的接口:

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

        结合上面的知识,以父子进程间管道通信为例子,详细的创建过程如下:

        我们通过fork()后,子进程会拷贝和父进程的内核数据结构,那么所有的子进程实际上的内核数据结构实际上都是差不多的。既然我们可以进行父子进程间的管道通信,我们当然也可以进行子与子之间、子与孙、父与孙之间的管道通信!如下为一个使用管道让父子进程间通信的例子:

#include <iostream>
#include <cassert>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstdlib>

#define MAX 1024

using namespace std;

int main()
{
    // 第1步,建立管道
    int pipefd[2]={0};
    int n=pipe(pipefd);
    assert(n==0);
    (void)n; // 防止编译器告警,意料之中,用assert,意料之外,用if

    // 第2步,创建子进程
    pid_t id=fork();
    if(id<0)
    {
        perror("fork");
        return 1;
    }
    // 子写,父读
    // 第3步,父子关闭不需要的fd,形成单向通信的管道
    if (id == 0)
    {
        //子关读
        close(pipefd[0]);
        int cnt = 0;
        while(true)
            {
                //利用write写
                char message[MAX];
                snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);
                cnt++;
                write(pipefd[1], message, strlen(message));
                sleep(1);
            }

        cout << "child close w piont" << endl;
        exit(0);
    }

    // 父进程,关闭写
    close(pipefd[1]);
    char buffer[MAX];
    while(true)
        {
            // sleep(2000);
            ssize_t n = read(pipefd[0], buffer, sizeof(buffer)-1);
            if(n > 0)
            {
                buffer[n] = 0; // '\0', 当做字符串
                cout << getpid() << ", " << "child say: " << buffer << " to me!" << endl;
            }
            else if(n == 0)
            {
                cout << "child quit, me too !" << endl;
                break;
            }
            cout << "father return val(n): " << n << endl;
            sleep(1);

        }
    cout << "read point close"<< endl;
    close(pipefd[0]);

    return 0;
}

匿名管道的特性与情况
// a. 管道的4种情况
//    1. 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
//    2. 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)
//    3. 写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾
//    4. 读端关闭,写端一直写入,OS会直接杀掉写端进程,通过想目标进程发送SIGPIPE(13)信号,终止目标进程 
// b. 管道的5种特性
//    1. 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用与父子,仅限于此
//    2. 匿名管道,默认给读写端要提供同步机制 --- 了解现象就行
//    3. 面向字节流的 --- 了解现象就行
//    4. 管道的生命周期是随进程的
//    5. 管道是单向通信的,半双工通信的一种特殊情况

命名管道

        通过前面的知识点,我们知道匿名管道可以让有“血缘关系”的进程进行通信,那如果我们要让没有血缘关系、毫不相干的进程进行通信呢?这个时候就需要使用到命名管道了。对于命名管道,我们可以使用指令来创建,也可以使用代码来创建。下面分别介绍两种方式:

指令级

        系统中的指令手册如下:

        如上图所示,我们通过命名管道让两个毫不相干的进程进行了通信。需要注意的是:我们创建的命名管道虽然是一个文件,他是存在在磁盘上的。但是,他是没有大小的!

        进程间相互通信的本质实际上是让不同的进程看到同一份资源,而命名管道的原理则是:因为路径是具有唯一性的,那么我们可以使用路径加文件名,来唯一的让不同的进程看到同一份资源。当我们让不同的进程看到了同一份资源,对应的进程struct file就会向匿名管道一样指向该文件的方法集、缓冲区等等但是文件是不会对于缓冲区进行刷盘操作的,因为磁盘中并没有存储信息。大致的图示如下:

代码级

        手册如下:

        函数原型:        

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

        参数说明

  • pathname:指定要创建的FIFO文件的路径名。
  • mode:设置新创建的FIFO文件的权限模式,默认是0666。

        返回值

  • 功时返回0,失败时返回-1,并设置errno以指示错误类型。

        使用场景

  • 当需要在进程间传递数据时,可以使用mkfifo创建命名管道,然后通过读写该管道来实现通信。
  • 命名管道可以用于不同进程、不同主机甚至不同操作系统之间的通信。

        注意事项

  • 在创建命名管道之前,需要确保路径中的目录已经存在,否则可能需要先创建这些目录。
  • 在使用命名管道进行通信时,需要注意同步和互斥的问题,以避免数据的混乱和竞争条件。
  • 由于命名管道存在于文件系统中,因此也需要考虑文件系统的权限和安全性问题。
        🌰 

        server.cc

#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILENAME "fifo"

bool MakeFifo()
{
    int n = mkfifo(FILENAME, 0666);
    if(n < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        return false;
    }

    std::cout << "mkfifo success... read" << std::endl;
    return true;
}

int main()
{
Start:
    int rfd = open(FILENAME, O_RDONLY);
    if(rfd < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        if(MakeFifo()) goto Start;
        else return 1;
    }
    std::cout << "open fifo success..." << std::endl;

    char buffer[1024];
    while(true)
    {
        ssize_t s = read(rfd, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "Client say# " << buffer << std::endl;
        }
        else if(s == 0)
        {
            std::cout << "client quit, server quit too!" << std::endl;
            break;
        }
    }

    close(rfd);
    std::cout << "close fifo success..." << std::endl;

    return 0;
}

        client.cc

#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILENAME "fifo"

int main()
{
    int wfd = open(FILENAME, O_WRONLY);
    if (wfd < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "open fifo success... write" << std::endl;

    std::string message;
    while (true)
    {
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);

        ssize_t s = write(wfd, message.c_str(), message.size());
        if (s < 0)
        {
            std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
            break;
        }
    }

    close(wfd);
    std::cout << "close fifo success..." << std::endl;

    return 0;
}

        Makefile

.PHONY:all
all:server client

server:server.cc
	g++ -o $@ $^ -std=c++11
client:client.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f server client fifo


                          感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于session每次请求都会改变的问题

这几天在部署一个前后端分离的项目&#xff0c;使用docker进行部署&#xff0c;在本地测试没有一点问题没有&#xff0c;前脚刚把后端部署到服务器&#xff0c;后脚测试就出现了问题&#xff01;查看控制台报错提示跨域错误&#xff1f;但是对于静态资源请求&#xff0c;包括登…

数据结构.线性表

1.静态分配 #include<iostream> using namespace std; const int N 10; typedef struct {int data[N];int length;}SqList; void InitList(SqList &L) {for (int i 0; i < N; i){L.data[i] 0;}L.length 0; }int main() {SqList L;InitList(L);return 0; }2.动…

2024年AI全景预测

欢迎来到 2024 年人工智能和技术的可能性之旅。 在这里&#xff0c;每一个预测都是一个潜在的窗口&#xff0c;通向充满创新、变革、更重要的是类似于 1950 年代工业革命的未来。 20 世纪 50 年代见证了数字计算的兴起&#xff0c;重塑了行业和社会规范。 如今&#xff0c;人工…

运行adprep /forestprep扩展Active Directory架构

运行 adprep /forestprep 是为了扩展Active Directory架构&#xff0c;以便为整个林添加新版本Windows Server所支持的新类、属性和其他目录对象。在升级到更高版本的Windows Server并提升林功能级别之前&#xff0c;通常需要执行此操作。 以下是详细步骤&#xff1a; 确认环境…

flask框架制作前端网页作为GUI

一、语法和原理 &#xff08;一&#xff09;、文件目录结构 需要注意的问题&#xff1a;启动文件命名必须是app.py。 一个典型的Flask应用通常包含以下几个基本文件和文件夹&#xff1a; app.py&#xff1a;应用的入口文件&#xff0c;包含了应用的初始化和配置。 requirem…

【C++入门到精通】特殊类的设计 |只能在堆 ( 栈 ) 上创建对象的类 |禁止拷贝和继承的类 [ C++入门 ]

阅读导航 引言一、特殊类 --- 不能被拷贝的类1. C98方式&#xff1a;2. C11方式&#xff1a; 二、特殊类 --- 只能在堆上创建对象的类三、特殊类 --- 只能在栈上创建对象的类四、特殊类 --- 不能被继承的类1. C98方式2. C11方法 总结温馨提示 引言 在面向对象编程中&#xff0…

Nginx与keepalived实现集群

提醒一下&#xff1a;下面实例讲解是在mac虚拟机里的Ubuntu系统演示的&#xff1b; Nginx与keepalived实现集群实现的效果 两台服务器都安装Nginx与keepalived&#xff1a; master服务器的ip(192.168.200.2) backup服务器的ip(192.168.200.4) 将 master服务器Nginx与keepalive…

【Mybatis的一二级缓存】

缓存是什么&#xff1f; 缓存其实就是存储在内存中的临时数据&#xff0c;这里的数据量会比较小&#xff0c;一般来说&#xff0c;服务器的内存也是有限的&#xff0c;不可能将所有的数据都放到服务器的内存里面&#xff0c;所以&#xff0c; 只会把关键数据放到缓存中&#x…

C# 命名管道NamedPipeServerStream使用

NamedPipeServerStream 是 .NET Framework 和 .NET Core 中提供的一个类&#xff0c;用于创建和操作命名管道的服务器端。命名管道是一种在同一台计算机上或不同计算机之间进行进程间通信的机制。 命名管道允许两个或多个进程通过共享的管道进行通信。其中一个进程充当服务器&…

RNN预测下一句文本简单示例

根据句子前半句的内容推理出后半部分的内容&#xff0c;这样的任务可以使用循环的方式来实现。 RNN&#xff08;Recurrent Neural Network&#xff0c;循环神经网络&#xff09;是一种用于处理序列数据的强大神经网络模型。与传统的前馈神经网络不同&#xff0c;RNN能够通过其…

独享http代理安全性是更高的吗?

不同于共享代理&#xff0c;独享代理IP为单一用户提供专用的IP&#xff0c;带来了一系列需要考虑的问题。今天我们就一起来看看独享代理IP的优势&#xff0c;到底在哪里。 我们得先来看看什么是代理IP。简单来说&#xff0c;代理服务器充当客户机和互联网之间的中间人。当你使用…

C/C++ - 面向对象编程

面向对象 面向过程编程&#xff1a; 数据和函数分离&#xff1a;在C语言中&#xff0c;数据和函数是分开定义和操作的。数据是通过全局变量或传递给函数的参数来传递的&#xff0c;函数则独立于数据。函数为主导&#xff1a;C语言以函数为主导&#xff0c;程序的执行流程由函数…

复式记账的概念特点和记账规则

目录 一. 复式记账法二. 借贷记账法三. 借贷记账法的记账规则四. 复试记账法应用举例4.1 三栏式账户举例4.2 T型账户记录举例4.3 记账规则验证举例 \quad 一. 复式记账法 \quad 复式记账法是指对于任何一笔经济业务都要用相等的金额&#xff0c;在两个或两个以上的有关账户中进…

GIT使用,看它就够了

一、目的 Git的熟练使用是一个加分项&#xff0c;本文将对常用的Git命令做一个基本介绍&#xff0c;看了本篇文章&#xff0c;再也不会因为不会使用git而被嘲笑了。 二、设置与配置 在第一次调用Git到日常的微调和参考&#xff0c;用得最多的就是config和help命令。 2.1 gi…

4核16G幻兽帕鲁服务器性能测评,真牛

腾讯云幻兽帕鲁服务器4核16G14M配置&#xff0c;14M公网带宽&#xff0c;限制2500GB月流量&#xff0c;系统盘为220GB SSD盘&#xff0c;优惠价格66元1个月&#xff0c;277元3个月&#xff0c;支持4到8个玩家畅玩&#xff0c;地域可选择上海/北京/成都/南京/广州&#xff0c;腾…

第十六章 Spring cloud stream应用

文章目录 前言1、stream设计思想2、编码常用的注解3、编码步骤3.1、添加依赖3.2、修改配置文件3.3、生产3.4、消费3.5、延迟队列3.5.1、修改配置文件3.5.2、生产端3.5.2、消息确认机制 消费端 前言 https://github.com/spring-cloud/spring-cloud-stream-binder-rabbit 官方定…

GPT-SoVITS 本地搭建踩坑

GPT-SoVITS 本地搭建踩坑 前言搭建下载解压VSCode打开安装依赖包修改内容1.重新安装版本2.修改文件内容 运行总结 前言 传言GPT-SoVITS作为当前与BertVits2.3并列的TTS大模型&#xff0c;于是本地搭了一个&#xff0c;简单说一下坑。 搭建 下载 到GitHub点击此处下载 http…

【网站项目】基于SSM的246品牌手机销售信息系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

蓝桥杯——每日一练(简单题)

题目 给定n个十六进制正整数&#xff0c;输出它们对应的八进制数。 解析 一、通过input&#xff08;&#xff09;函数获得需要转化的数字个数 二、for循环的到数字 三、for循环先将16进制转化为10进制&#xff0c;再输出8进制 代码 运行结果

直线拟合(支持任意维空间的直线拟合,附代码)

文章目录 一、问题描述二、推导步骤三、 M A T L A B MATLAB MATLAB代码 一、问题描述 给定一系列的三维空间点 ( x i , y i , z i ) , i 1 , 2 , . . . , n (x_i,y_i,z_i),i1,2,...,n (xi​,yi​,zi​),i1,2,...,n&#xff0c;拟合得到直线的方程。本文的直线拟合方法适用于任…