【Linux】从零开始认识进程间通信 —— 管道

在这里插入图片描述
送给大家一句话:
人要成长,必有原因,背后的努力与积累一定数倍于普通人。所以,关键还在于自己。 – 杨绛


从零开始认识进程间通信

  • 1 为什么要进程间通信
  • 2 进程如何通信
  • 3 进程通信的常见方式
  • 4 管道
    • 4.1 什么是管道
    • 4.2 管道通信的系统调用
    • 4.3 小试牛刀
  • 5 总结
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 为什么要进程间通信

以前我们学习的过程中,进程是具有独立性的。但有些时候需要多个进程进行协同,这时候就需要进程间的通信来保证信息的互通。

就比如学校就分有教务处 , 学生处,教研组,班主任等部分。如果学校想要组织一场考试,就通知教务处安排好考场和监考员,告诉教研组老师需要出卷子,等教务处与教研组完成对应工作再告知学生处和班主任,然后通知学生进行考试,班主任和学生处做好考试监督工作。

这里面就少不了沟通交流,传递信息。进程工作也是这样:进程的协同工作需要一个前提提交——通信。通信就是传递数据,控制相关信息

2 进程如何通信

首先 , 我们知道进程是具有独立性的,一个进程的状态不会影响其他进程的运行。而独立性的本质是进程 = 内核数据结构 + 代码和数据 ,内核数据结构PCB是独立的,代码和数据是独立的 , 进程自然就是独立的。

所以,进程间通信的成本的成本稍微高一些,因为进程本身是独立的,两个进程天然是无法进行数据共享的!

可是子进程建立的时候不是会拷贝(继承)一份父进程的数据吗,这不是进行通信吗???
这就要我们明确区分两个概念:能通信可以一直通信是不一样的。子进程继承的父进程数据是只读的,而且只进行一次。而一直通信是时不时就“打个电话”。

所以进程间通信的前提是:先让不同的进程看到同一份(操作系统)资源(“一段内存”)。

在这里插入图片描述

  1. 首先,一定是某一个进程先需要通信,让OS创建一个共享资源
  2. 那么OS必须通过对应的系统调用来创建共享资源
  3. OS创建的共享资源的不同 , 系统调用接口的不同----就导致进程间通信会有不同的种类

3 进程通信的常见方式

一般通信有以下种类:

  1. 管道
    • 匿名管道pipe
    • 命名管道
  2. System V IPC 标准 (早期的本地通信)
    • System V 消息队列
    • System V 共享内存
    • System V 信号量
  3. POSIX IPC 标准(现代版本)
    • 消息队列
    • 共享内存
    • 信号量
    • 互斥量
    • 条件变量
    • 读写锁

今天来讲解管道

早期的时候,程序员们面对通信的需求时,不想再单独设计一个通信模块,直接复用内核级代码,就产生了管道!!!管道分为命名管道和匿名管道。

4 管道

4.1 什么是管道

【Linux】 拿下 系统 基础文件操作!!!
【Linux】开始了解重定向

这两篇文章了我们讲解了文件的底层,知道每一个进程都有对应的文件管理结构体,文件管理结构体中有管理已经打开文件的数组。数组下标为文件描述符,指向文件结构体,而文件结构体又会指向文件真正属性inode。
在这里插入图片描述
当我们以不同方式打开文件时,只需要在内存中加载一份数据(通过引用计数来管理),以读写方式打开,便会有两个对应的文件结构体。他们共同使用一份代数据,那自然就使用同一个内核级缓冲区。

那么为了要通信,不用在写一个新的模块,直接建立一个子进程来通信多简单。子进程会以父进程为模版进行写时拷贝。
在这里插入图片描述
进行拷贝的只有进程对应的结构体,因为进程具有独立性,而文件系统我们可没提过什么对立性,所以文件管理数组进行浅拷贝,同样指向原先的文件结构体。

这时也就理解为什么父子进程会向同一块显示器终端打印数据了。

也理解为什么进程会默认打开012三个标准输入输出:因为所有进程都是bash的子进程,而bash打开了这三个文件,所以自然就打开了!!!

子进程要主动close(0 / 1 /2)不影响父进程继续使用显示器文件!只有引用计数(类似硬链接数)归零才会清理数据

今天我们进行进程间通信的前提——先让不同的进程看到同一份(操作系统)资源,不就解决了吗!!!

文件的内存缓冲区不就是两个进程共享的一份资源吗!而所谓的管道文件就是这个文件缓冲区!

但是呢,管道只允许进行单向通信(父->子 或 子->父),因为管道如果允许父子进程都可以写,就会导致数据紊乱!进行通信的时候,每个进程关闭不需要的文件描述符,然后通过缓冲区来单向通信。一个进程把信息写入缓冲区,另一个进程从缓冲区读取数据,不需要刷新到硬盘,直接从内存进行操作!

在这里插入图片描述

有个问题:父子既然要关闭不需要的fd那为什么曾经还要打开呢?可以不关闭吗?
如果父进程只打开读写的fd,那么子进程也就只能继承读写的fd,这就坏事了,总得有人写入吧!那为什么不直接以读写方式打开一个fd呢?这样肯定不可以,子进程继承后也具有读写,也坏事了!
所以不关闭是为了让子进程可以继承下去,到时候关闭不需要的就可以了!当然也可以不关闭,只要你不乱使用,所以为了排除风险,建议直接关闭

4.2 管道通信的系统调用

了解了管道是什么,我们就来看看关于管道的系统调用是什么吧?
通过手册我们可以看到:

PIPE(2)                                                             Linux Programmer's Manual                                                            PIPE(2)NAMEpipe, pipe2 - create pipeSYNOPSIS#include <unistd.h>/* On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64; see NOTES */struct fd_pair {long fd[2];};struct fd_pair pipe();/* On all other architectures */int pipe(int pipefd[2]);#define _GNU_SOURCE             /* See feature_test_macros(7) */#include <fcntl.h>              /* Obtain O_* constant definitions */#include <unistd.h>int pipe2(int pipefd[2], int flags);

新的pipe(int pipefd[2]) 是今天的主角(Ubuntu提供了新的选择pipe2),其实底层就是open

pipefd[2] 这是一个输出型参数,把以读方式打开的文件描述符rfd和以写方式打开的文件描述符wfd记录下来!

open不同的是,这个系统调用不需要文件路径和文件名,所以才叫匿名管道!

那么如果我们想要双向通信呢???
干脆建两个管道不就行了!

那为什么要进行单向通信呢?
因为这个管道的单向通信简单,对代码的复用率很高!

4.3 小试牛刀

接下来我们就来写一个demo,来试试管道接口。

首先我们来搭建一个框架:

  1. 建立一个管道,得到对应的文件描述符
  2. 创建子进程,关闭对应文件
  3. 我们进行子进程写入,父进程读取
  4. 等待子进程退出,避免僵尸进程出现!
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<sys/wait.h>
#include<cerrno>using namespace std;void SubProcessWrite(int wfd)
{
}
void FatherProcessRead(int rfd)
{
}
int main()
{//创建管道int pipefd[2];//[0] -> r | [1] -> wint n = pipe(pipefd);if(n != 0){perror("创建管道错误!\n");}cout << "pipefd[0] : " << pipefd[0] << " pipefd[1] :" << pipefd[1] << endl;//创建子进程//关闭对应文件pid_t id = fork();if(id == 0){//子进程 -- writeclose(pipefd[0]);SubProcessWrite(pipefd[1]);//使用完都关闭close(pipefd[1]);exit(0);}//父进程 -- readclose(pipefd[1]);FatherProcessRead(pipefd[0]);//使用完都关闭close(pipefd[0]);pid_t rid = waitpid(id ,nullptr , 0 );if(rid > 0){cout << "wait child process done!" << endl;}return 0;
}

管道本质也是文件,我们读写同样使用write和read。
我们完善一下代码:

  1. 子进程每隔一秒写入一次数据
  2. 父进程每隔一秒读取一次数据
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<string>
#include<sys/wait.h>
#include<cerrno>using namespace std;string GetOtherMessage()
{static int cnt = 0;string messageid = to_string(cnt);cnt++;pid_t self_id = getpid();string stringid = std::to_string(self_id);string message = "messageid : ";message += messageid;message += "my pid :";message += stringid;return message;
}void SubProcessWrite(int wfd)
{string messages = "father,I am your soon process !";while (true){string info = messages + GetOtherMessage();//写入管道时没有写入'\0',没有必要。在读取时添加write(wfd , info.c_str() , info.size());sleep(1);}}
#define size 1024void FatherProcessRead(int rfd)
{char inbuffer[size];while (true){//注意传入的参数 , 读取 rfd 内的数据到inbuffer中,返回成功读取的个数。ssize_t n = read(rfd , inbuffer , sizeof(inbuffer) - 1);if( n > 0 ) {inbuffer[n] = '\0';//添加'\0'cout << "father get message: " << inbuffer << endl;}}
}
int main()
{//创建管道int pipefd[2];//[0] -> r | [1] -> wint n = pipe(pipefd);if(n != 0){perror("创建管道错误!\n");}cout << "pipefd[0] : " << pipefd[0] << " pipefd[1] :" << pipefd[1] << endl;sleep(1);//创建子进程//关闭对应文件pid_t id = fork();if(id == 0){cout << "子进程关闭不需要的fd , 准备开始发消息" << endl;sleep(1);//子进程 -- writeclose(pipefd[0]);SubProcessWrite(pipefd[1]);//使用完都关闭close(pipefd[1]);exit(0);}cout << "子进程关闭不需要的fd , 准备开始接收消息" << endl;sleep(1);//父进程 -- readclose(pipefd[1]);FatherProcessRead(pipefd[0]);//使用完都关闭close(pipefd[0]);pid_t rid = waitpid(id ,nullptr , 0 );if(rid > 0){cout << "wait child process done!" << endl;}return 0;
}

运行看看:
在这里插入图片描述
这样验证管道通信的可行性。

5 总结

管道的4种情况

  1. 如果管道内部是空的 && write fd没有关闭:
    读取条件不具备,读取进程会被阻塞 – wait 等待条件具备(写入了数据)
  2. 管道别写满 && read fd 不读且没有关闭 :
    管道被写满,写进程会被阻塞,写条件不具备-- wait 等待条件具备(读取走一部分数据才能继续写)
  3. 管道一直在被读 && 写端关闭了wfd:
    读端read的返回值会读到 0 ,表示到了文件结尾!!!所以可以在读取的时候进行一下判断,为0就直接退出读取!
  4. rfd 直接关闭 , 写端wfd一直在写入:
    首先管道只有一对读写端,读端被关闭了,那么管道就不能称之为管道了。OS系统也不会做这样的无用功,直接把broken pipe坏的管道 进行杀掉!会发送对应的13号信号SIGPIPE:
    在这里插入图片描述

我们可以总结出管道的5 种特征:

  1. 匿名管道:只用来进行父子进程之间,因为他们可以看到同一资源
  2. 同步性管道内部自带进程之间的同步机制!同步是多执行流代码的时候,具有明显的顺序性。父子进程的读写一定要同步进行,不然可能会出现并发读取的情况,出现错误!
  3. 文件的生命周期是随进程的:当一个文件没有进程调用的时候,就会释放掉!
  4. 管道在通信的时候,是面向字节流的:write 的次数和read的次数不是一一匹配的!
    我们让子进程疯狂的写,父进程也一直读。子进程每 1 s写一次,写入时也向标准错误里进行打印(为了好观察)。父进程每5s读一次,并打印到显示器:
    在这里插入图片描述
    可以看到,右侧的子进程,左边是父进程。子进程写入好几次的数据,会被父进程一次读取一大批!!!
  5. 管道的通信模式,是一种特殊的半双工模式:与之对应的是全双工模式,即双方交流可以同时说话。半双工是只能一方说话,一方聆听,不能同时说(对讲机模式)。

这里提一个概念,在管道读写是"原子"的,每个"原子"是 4096 bytes。只有小于这个大小,就不会在读写时被其他人影响。如果大于一个原子的大小,就不能保证安全了。

下一篇文章我们进行管道的实战——进程池项目!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

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

相关文章

交叉编译程序,提示 incomplete type “struct sigaction“ is not allowed

问题描述 incomplete type "struct sigaction" is not allowed解决办法 在代码的最顶端添加如下代码即可 #define _XOPEN_SOURCE此定义不是简单的宏定义&#xff0c;是使程序符合系统环境的不可缺少的部分 _XOPEN_SOURCE为了实现XPG&#xff1a;The X/Open Porta…

零一万物Yi-1.5开源,34B/9B/6B多尺寸,34B超Qwen1.5-72B

前言 近年来&#xff0c;大型语言模型&#xff08;LLM&#xff09;在各个领域展现出惊人的能力&#xff0c;为人们的生活和工作带来了巨大的改变。然而&#xff0c;大多数开源 LLM 的性能仍然无法与闭源模型相媲美&#xff0c;这限制了 LLM 在科研和商业领域的进一步应用。为了…

element-plus表格的表单校验如何实现,重点在model和prop

文章目录 vue&#xff1a;3.x element-plus&#xff1a;2.7.3 重点&#xff1a; 1) tableData放到form对象里 2) form-item的prop要写成tableData.序号.属性 <!--table-表单校验--> <template><el-form ref"forms" :model"form"><e…

安装mpi4py与dlio_profiler_py的总结

安装mpi4py mpi4py是一个Python库&#xff0c;它提供了与MPI&#xff08;Message Passing Interface&#xff09;兼容的接口&#xff0c;使得Python程序能够利用MPI实现并行计算。mpi4py 的核心是基于MPI标准的C/C实现&#xff0c;它能够在高性能计算环境下进行高效的并行处理…

软考之信息系统管理知识点(3)

流水线&#xff1a;是指在程序执行时多条指令重叠进行操作的一种准并行处理实现技术。各种部件同时处理是针对不同指令而言的&#xff0c;它们可同时为多条指令的不同部分进行工作&#xff0c;以提高各部件的利用率和指令的平均执行速度。 编译得过程 关系数据库是表的集合 …

【全网最全】2024电工杯数学建模A题前两问完整解答+21页初步参考论文+py代码+保奖思路等(后续会更新成品论文)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的卡片链接&#xff0c;那是获取资料的入口&#xff01; 【全网最全】2024电工杯数学建模A题前两问完整解答21页初步参考论文py代码保奖思路等&#xff08;后续会更新成品论文&#xff09;「首先来看看目前已有…

力扣:92. 反转链表 II(Java)

目录 题目描述&#xff1a;示例 1&#xff1a;示例 2&#xff1a;代码实现&#xff1a; 题目描述&#xff1a; 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的…

网络采集受限?如何解决指纹识别、IP封禁、验证码、账号多登等问题

网页采集是什么 网页采集&#xff0c;也常被称作网络采集、网络数据抓取&#xff0c;是一种通过自动化工具从网站上获取信息的技术。这些技术通过访问网页&#xff0c;解析页面上的内容&#xff0c;并提取出有价值的数据&#xff0c;如文本、图片、链接等。 网页采集通常用于…

Python 调整PDF文件的页面大小

在处理PDF文件时&#xff0c;我们可能会遇到这样的情况&#xff1a;原始PDF文档不符合我们的阅读习惯&#xff0c;或者需要适配不同显示设备等。这时&#xff0c;我们就需要及时调整PDF文档中的页面尺寸&#xff0c;以满足不同应用场景的需求。 利用Python语言的高效性和灵活性…

Linux--网络通信(一)概述

网络通信概述 网络通信本质上是一种进程间通信&#xff0c;是位于网络中不同主机上的进程之间的通信&#xff0c;属于 IPC 的一种&#xff0c; 通常称为 socket IPC。所以网络通信是为了解决在网络环境中&#xff0c;不同主机上的应用程序之间的通信问题。 大概可以分为三个层…

23种设计模式之一————外观模式详细介绍与讲解

外观模式详细讲解 一、概念二、 外观模式结构核心思想及解释模式的UML类图模式角色应用场景模式优点模式缺点 三、实例演示图示代码展示运行结果 一、概念 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它提供了一个统一的接口&#xff0c…

【问题解决】Android Studio Jellyfish新建Kotlin项目后Gradle Sync及Maven下载很慢

创建新项目之后&#xff0c;Gradle Sync和Build都很慢&#xff0c;因为下载Gradle和Maven等工具。 代码默认配置 settings.gradle.kts pluginManagement {repositories {google {content {includeGroupByRegex("com\\.android.*")includeGroupByRegex("com\\.g…

Android Activity 设计详解

文章目录 Android Activity 设计说明1. Activity 的生命周期2. Activity 的启动模式3. Activity 的通信4. Activity 的布局和视图管理5. Activity 的配置变化处理6. Activity 的保存和恢复状态7. Activity 的任务和返回栈 总结 Android Activity 设计说明 在 Android 中&#…

Ansible01-Ansible的概述、实验环境初始化、Inventory

目录 写在前面1. Ansible是什么1.1 简介与来历1.2 Ansible的特点1.3Ansible的架构与工作流程1.3.1 ansible 任务执行模式1.3.2 ansible 执行流程1.4 Ansible的模块 2. Ansible实验初始化2.1 实验环境2.2Ansible的安装2.2.1 Ansible的程序结构 2.3 修改Ansible配置文件2.3.1 配置…

【408精华知识】页、页面、页框、页帧、内存块、物理块、物理页面还傻傻分不清?

在做题过程中&#xff0c;我们经常能看到页、页框、块等概念&#xff0c;初接触时&#xff0c;常感觉傻傻分不清&#xff0c;这篇文章将简洁地介绍它们之间的联系与区别。 这些概念之间的根本区别&#xff0c;在于是物理上的概念还是逻辑上的概念&#xff0c;也即是虚地址还是实…

汇聚荣:新手做拼多多应该注意哪些事项?

新手在拼多多开店&#xff0c;面临的是竞争激烈的市场和复杂的运营规则。要想在这个平台上脱颖而出&#xff0c;必须注意以下几个关键事项。 一、市场调研与定位 深入了解市场需求和竞争对手情况是新手开店的首要步骤。选择有潜力的细分市场&#xff0c;并针对目标消费者群体进…

华为云服务培训

一、存储类服务实践 是什么&#xff1a; 云硬盘( Elastic Volume Service )是一种为 ECS&#xff08;弹性云服务器&#xff09;、BMS&#xff08;裸金属服务器&#xff09; 等计算服务提供持久性存储的服务。 作用&#xff1a; 它通过数据冗余和缓存加速等多项技术&#xf…

Spring系列-03-BeanFactory和Application接口和相关实现

BeanFactory BeanFactory和它的子接口们 BeanFactory 接口的所有子接口, 如下图 BeanFactory(根容器)-掌握 BeanFactory是根容器 The root interface for accessing a Spring bean container. This is the basic client view of a bean container; further interfaces such …

windows 11上自带时间管理-番茄工作法

在 Windows 11 中&#xff0c;你可以使用 专注 功能来最大程度地减少干扰&#xff0c;帮助你保持专注。 专注的工作原理 专注时段打开后&#xff0c;将会出现以下情况&#xff1a; 专注计时器将显示在屏幕上 请勿打扰将打开 任务栏中的应用不会闪烁发出提醒 任务栏中应用的…

SpringCloud系列(23)--手写实现负载轮询算法

前言&#xff1a;在上一篇文章中我们介绍了关于负载轮询算法的原理以及看了源代码&#xff0c;而本章节内容则是着重于我们自己手写一个负载轮询算法 1、分别编写provider-payment8001、provider-payment8002这两个子项目的PaymentController类&#xff0c;增加一个/payment/lb…