Linux之进程间通信(管道)

目录

一、进程间通信

1、进程间通信的概念

2、进程间通信的目的

3、进程间通信的分类

二、管道

1、管道基本介绍

2、匿名管道

3、命名管道


一、进程间通信

1、进程间通信的概念

什么是进程间通信?

我们在学习了进程的相关知识后,知道,进程的运行是具有独立性的,每个进程都有自己独立的PCB,也都有只属于自己的地址空间,进程之间是互不干扰的。

所以说,进程之间要通信的话,难度是非常大的。

进程间通信的本质:操作系统需要直接或者间接给通信双方的进程提供一段 “内存空间”,并且要让不同的进程看到同一份资源(内存空间)。而这个内存空间不应该属于任何一个进程,而应该由操作系统来维护。

所以,进程间通信的最大成本就是:操作系统在设计的原生层面,进程是互相独立的,但是现在需要让它们之间进行通信。那么应该如何让不同的进程看到同一份资源。

2、进程间通信的目的

~ 数据传输:一个进程需要将它的数据发送给另一个进程。
~ 资源共享:多个进程之间共享同样的资源。
~ 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
~ 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

3、进程间通信的分类

~ 管道:1、匿名管道pipe       2、命名管道 

~ System V IPC:1、System V 消息队列     2、System V 共享内存    3、System V 信号量

~ POSIX IPC:1、消息队列     2、共享内存     3、信号量    4、互斥量     5、条件变量    6、读写锁

进程间通信的必要性:如果只是使用单进程,那么也就无法使用并发能力,更加无法实现多进程协同工作。

本篇文章我们来具体讲一讲,管道的相关知识。

二、管道

1、管道基本介绍

什么是管道?

1、管道是Unix中最古老的进程间通信的形式。
2、我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

管道只能进行单向通信,用来传输资源:数据。

我们按下面的步骤来分析:

1、首先,父进程分别以读和写的方式,打开了一个文件。

2、然后,父进程fork创建了一个子进程,我们知道,子进程会继承父进程,指向同一个文件。

3、我们规定,父进程对文件写入,子进程对文件读取。关闭父子进程各自不需要的功能。即:父进程关闭读的文件描述符,子进程关闭写的文件描述符。

最终,通过该文件就实现了单向通信。这就是管道。管道的本质其实就是内存级文件。所以两个进程间通信的数据,不需要刷新到磁盘里。

为什么能够这样做?

文件有属于自己的内核缓冲区,所以父进程和子进程有一份公共的资源:文件系统提供的内核缓冲区,父进程可以向对应的文件的文件缓冲区写入,子进程可以通过文件缓冲区读取,此时就完成了进程间通信。

管道一般用来进行具有血缘关系的进程(父子进程)之间的通信。 

管道分为匿名管道和命名管道。下面我们就来具体讲一讲它们的内容。

2、匿名管道

通过文件名区分文件,但是如果当前进程的文件没有名字,这样的内存级文件称为匿名管道。匿名管道能用来父进程和子进程之间进行进程间通信。

匿名管道文件在磁盘上也没有实体,它只存在于内存中。

函数:pipe,调用成功返回0,调用失败返回-1。

SYNOPSIS#include <unistd.h>int pipe(int pipefd[2]);
DESCRIPTIONpipe() creates a pipe,pipefd[0]  refers  to  the  read end of the pipe.  pipefd[1] refers to the write end of the pipe.
RETURN VALUEOn success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

我们首先创建管道文件,打开读写端:在pipefd数组中,pipefd[0]代表读,pipefd[1]代表写。

#include <iostream>
#include <cassert>
#include <unistd.h>using namespace std;int main()
{// 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n;cout << "pipefd[0]: " << pipefd[0] << endl;cout << "pipefd[1]: " << pipefd[1] << endl;return 0;
}

接着,我们fork创建子进程, 然后,我们关闭父子进程不需要的文件描述符,完成通信框架的建立:

#include <iostream>
#include <cassert>
#include <unistd.h>using namespace std;int main()
{// 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n;pid_t id = fork();assert(id != -1);if(id == 0){//子进程close(pipefd[1]);exit(0);}//父进程close(pipefd[0]);return 0;
}

最后,我们通过父进程给子进程发送消息来检测通信:

#include <iostream>
#include <cassert>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;int main()
{// 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n;// 创建子进程pid_t id = fork();assert(id != -1);if (id == 0){// 子进程close(pipefd[1]);char buffer[1024];while (true){ssize_t mess = read(pipefd[0], buffer, sizeof(buffer));if (mess > 0){buffer[mess] = 0;cout << "father process say: " << buffer << endl;}}exit(0);}// 父进程close(pipefd[0]);string message = "i am father ->";char buff[1024];int count = 0;while (true){snprintf(buff, sizeof(buff), "%s[%d]: %d", message.c_str(), getpid(), count++);write(pipefd[1], buff, sizeof(buff));sleep(1);}pid_t ret = waitpid(id, nullptr, 0);assert(ret > 0);(void)ret;close(pipefd[1]);return 0;
}

管道的特点:

1、管道是用来进行具有血缘关系的进程间的通信,常用于父子进程。

2、管道能够让进程间协同,提供了访问控制。

3、管道是面向字节流的。

4、管道的生命周期随进程,进程退出,管道释放。

5、管道是单向通信,是半双工通信的特殊情况。

6、内核会对管道操作进行互斥与同步。

下面我们针对管道的特点2,来进行演示:

~ 读快写慢

其余代码不变,我们仅让父进程在每次写入后,休眠10秒再写。

我们发现,父进程休眠期间,子进程会等待父进程写入后再读取。(如果管道中没有数据,读端在读,此时默认会直接阻塞当前正在读取的进程)

~ 读慢写快

我们让父进程不停地写入。

子进程休眠5秒后再读取。

拿着管道读端不读,写端一直在写:写端往管道里写,而管道是有大小的,不断往写端写,会被写满。 

管道是固定大小的缓冲区,当管道被写满,就不能再写了。此时写端会阻塞。而休眠结束后,子进程就会开始读取,这时父进程才能继续写入。

~ 关闭写入端

父进程写入端关闭:写完第一次后,父进程休眠6秒,然后结束循环,关闭写入端。

 父进程作为写入的一方,关闭了写入端,子进程作为读取的一方,read会返回0,表示读到了文件的结尾。这时我们让子进程结束循环,并退出。

~ 关闭读端

管道是单向的:读端关闭,再写入就没有意义了,这时OS会终止写端,会给写进程发送信号,终止写端。

为了方便观察,我们让父进程读取,子进程写入。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{int pipefd[2] = { 0 };if (pipe(pipefd) < 0){ perror("pipe");return 1;}pid_t id = fork(); if (id == 0){//childclose(pipefd[0]); const char* msg = "hello father, I am child...";int count = 10;while (count--){write(pipefd[1], msg, strlen(msg));sleep(1);}close(pipefd[1]);exit(0);}//fatherclose(pipefd[1]); close(pipefd[0]); int status = 0;waitpid(id, &status, 0);printf("child get signal:%d\n", status & 0x7F); return 0;
}

操作系统向子进程发送的是SIGPIPE信号将子进程终止。 

3、命名管道

上面我们所讲的匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
而如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文件。

一个进程打开了一个文件后,Linux内核里会有文件的struct file结构。如果还有第二个进程要打开这个文件,那么只要路径相同,那么两个进程打开的文件一定是同一个且是唯一的,那么第二个进程不需要继续创建struct file对象,因为OS会识别到打开的文件被打开了。

此时两个进程就看到了同一份资源,该文件也不需要把数据刷新到磁盘上去,不需要IO。

所以说,命名管道是真实存在于系统路径下的,只是它只有属性,没有内容。它能够使用open,close函数被打开或关闭。

mkfifo:也可以作为命令直接在指定路径下创建命名管道。mkfifo   命名管道名称

NAMEmkfifo - make FIFOs (named pipes)SYNOPSIS#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
RETURN VALUEOn success mkfifo() returns 0.  In the case of an error, -1 is returned (in which case, errno is set appropriately).

下面我们就来使用一下:我们有三个文件,server.cc(读取端) ,client.cc(写入端) 是两个源文件,它们运行后会是两个不同且无关系的进程,我们来进行它们之间的通信。comm.h是一个头文件。

comm.h

#include<iostream>
#include<string>
#include<unistd.h>
#include<cstdio>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>using namespace std;string ipcPath = "./fifo.ipc";
#define MODE 0666
#define SIZE 128

server.cc

#include "comm.hpp"int main()
{// 1.创建命名管道int ret = mkfifo(ipcPath.c_str(), MODE);if (ret == -1){perror("mkfifo");exit(1);}// 2.正常文件操作:通信int fd = open(ipcPath.c_str(), O_RDONLY);if (fd < 0){perror("open");exit(2);}char buffer[SIZE];while (true){ssize_t s = read(fd, buffer, sizeof(buffer) - 1);if (s > 0){cout << "client say :" << buffer << endl;}else if (s == 0){cerr << "read the end of file ->  client quit, server quit too" << endl;break;}else{perror("read");break;}}close(fd);return 0;
}

client.cc

#include "comm.hpp"int main()
{// 1.打开命名管道int fd = open(ipcPath.c_str(), O_WRONLY);if (fd < 0){perror("open");exit(1);}string buffer;while (true){cout << "please enter message ->" << endl;getline(cin, buffer);write(fd, buffer.c_str(), buffer.size());}close(fd);return 0;
}

通信演示:

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

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

相关文章

树的一些经典 Oj题 讲解

关于树的遍历 先序遍历 我们知道 树的遍历有 前序遍历 中序遍历 后序遍历 然后我们如果用递归的方式去解决&#xff0c;对我们来说应该是轻而易举的吧&#xff01;那我们今天要讲用迭代&#xff08;非递归&#xff09;实现 树的相关遍历 首先呢 我们得知道 迭代解法 本质上也…

浅析云服务oss/obs/cos对象存储安全攻防

文章目录 前言云存储服务1.1 初识对象存储1.2 腾讯云COS桶1.3 公开读取风险 对象存储桶风险2.1 Bucket Object遍历2.2 Bucket 名称的爆破2.3 Bucket ACL可读写2.4 任意写与文件覆盖2.5 Bucket 域名的接管 AccessKey凭证泄露3.1 行云管家接管主机3.2 Github泄露AK/SK3.3 客户端程…

Chatgpt+Comfyui绘图源码说明及本地部署文档

其他文档地址&#xff1a; ChatgptComfyui绘图源码运营文档 ChatgptComfyui绘图源码线上部署文档 一、源码说明 1、源码目录说明 app_home&#xff1a;app官网源码chatgpt-java&#xff1a;管理后台服务端源码、用户端的服务端源码chatgpt-pc&#xff1a;电脑网页前端源码cha…

两条链表相同位数相加[中等]

优质博文IT-BLOG-CN 一、题目 给你两个非空的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照逆序的方式存储的&#xff0c;并且每个节点只能存储一位数字。请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。你可以假设除了数字0之外&#xff0c;这…

【征服Redis12】redis的主从复制问题

从现在开始&#xff0c;我们来讨论redis集群的问题&#xff0c;在前面我们介绍了RDB和AOF两种同步机制&#xff0c;那你是否考虑过这两个机制有什么用呢&#xff1f;其中的一个重要作用就是为了集群同步设计的。 Redis是一个高性能的键值存储系统&#xff0c;广泛应用于Web应用…

【React】Redux的使用详解

文章目录 Redux的三大原则Redux官方图react-redux使用 1、创建store管理全局状态​ 2、在项目index.js根节点引用 3、 在需要使用redux的页面或者组件中&#xff0c;通过connect高阶组件映射到该组件的props中 redux中异步操作如何使用redux-thunkcombineReducers函数 Re…

数据结构和算法笔记4:排序算法-归并排序

归并排序算法完全遵循分治模式。直观上其操作如下&#xff1a; 分解&#xff1a;分解待排序的n个元素的序列成各具n/2个元素的两个子序列。解决&#xff1a;使用归并排序递归地排序两个子序列。合并&#xff1a;合并两个已排序的子序列以产生已排序的答案。 我们直接来看例子…

Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel

前言 本文主要讲解&#xff0c;使用不同的 Channel 让 Flutter 和 Android原生 进行通信&#xff0c;由于只是讲解两端通信&#xff0c;所以可视化效果不好&#xff1b; 不过我写了一篇专门讲解 Flutter 嵌入 Android原生View的文章 Flutter 页面嵌入 Android原生 View-CSDN…

小程序使用echarts图表-雷达图

本文介绍下小程序中如何使用echarts 如果是通过npm安装&#xff0c;这样是全部安装的&#xff0c;体积有点大 我这边是使用echarts中的一个组件来实现的&#xff0c;下边是具体流程&#xff0c;实际效果是没有外边的红色边框的&#xff0c;加红色边框的效果是这篇说明 1.echa…

IDEA的database使用

一、数据据库 在使用database之前&#xff0c;首先你的电脑要安装好了数据库并且启动。 MySQL卸载手册 链接&#xff1a;https://pan.baidu.com/doc/share/AVXW5SG6T76puBOWnPegmw-602323264797863 提取码&#xff1a;hlgf MySQL安装图解 链接&#xff1a;https://pan.baidu.…

机器学习笔记——机器学习的分类

1 机器学习是啥 机器学习是人工智能的一个分支&#xff0c;它是一门研究机器获取新知识和新技能&#xff0c;并识别现有知识的学问。 机器学习已广泛应用于数据挖掘、计算机视觉、自然语言处理、生物特征识别、搜索引擎、医学诊断、检测信用卡欺诈、证券市场分析、DNA 序列测…

用Python实现Excel中的Vlookup功能

目录 一、引言 二、准备工作 三、实现Vlookup功能 1、导入pandas库 2、准备数据 3、实现Vlookup功能 4、处理结果 5、保存结果 四、完整代码示例 五、注意事项 六、总结 一、引言 在Excel中&#xff0c;Vlookup是一个非常实用的函数&#xff0c;它可以帮助我们在表…

Web概述

Web 概述&#xff1a;Web是World Wide Web的简称&#xff0c;是一个由许多互联网服务组成的信息空间。它由超文本文档、图像、视频和其他多媒体资源组成&#xff0c;并通过超文本传输协议&#xff08;HTTP&#xff09;进行传输。特点&#xff1a;Web的主要特点是其开放性和可访…

java数据结构与算法刷题-----LeetCode485. 最大连续 1 的个数

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 法一&#xff0c;双指针2. 法二&#xff1a;变量计数 1. 法一…

面对不平衡二元分类问题是否需要使用SMOTE技术?

摘要 在训练分类模型之前平衡数据是解决表格数据中不平衡二元分类任务的流行技术。平衡通常是通过复制少数样本或生成合成少数样本来实现的。虽然众所周知&#xff0c;平衡对每个分类模型的影响不同&#xff0c;但大多数先进的实证研究并未将强大的最先进&#xff08;SOTA&…

Qt6入门教程 8:信号和槽机制(连接方式)

目录 一.一个信号与槽连接的例子 二.第五个参数 1.Qt::AutoConnection 2.Qt::DirectConnection 3.Qt::QueuedConnection 4.Qt::BlockingQueuedConnection 5.Qt::UniqueConnection 三.信号 四.connect函数原型 五.信号与槽的多种用法 六.槽的属性 一.一个信号与槽连接…

R语言【cli】——builtin_theme():内置的CLI主题

Package cli version 3.6.0 Description 此主题始终处于活动状态&#xff0c;并且位于主题堆栈的底部。 Usage builtin_theme(dark getOption("cli.theme_dark", "auto")) Argument 参数【dark】&#xff1a;是否使用黑暗主题。cli.theme_dark选项可用…

3d渲染软件有哪些?3d云渲染推荐

目前市面上的3D渲染软件非常多&#xff0c;不同的建模软件都有自己的渲染方式&#xff0c;根据所处行业的不同和项目需要&#xff0c;设计师可以选择不同的软件帮助展示最终效果。 主流的渲染软件有&#xff1a;VRay和Corona&#xff1a;一般用于室内效果图渲染&#xff0c;与3…

go 语言爬虫库goquery介绍

文章目录 爬虫介绍goquery介绍利用NewDocumentFromReader方法获取主页信息Document介绍通过查询获取文章信息css选择器介绍goquery中的选择器获取主页中的文章链接 爬取总结 爬虫介绍 爬虫&#xff0c;又称网页抓取、网络蜘蛛或网络爬虫&#xff0c;是一种自动浏览互联网并从网…

chapter10-让你拥有“火眼金睛”的 Fiddr4 和其他工具

在前面的课程中&#xff0c;我们通过一个简单的天气预报服务&#xff0c;拓展了如何使用邮件、短信&#xff0c;以及部署在服务器上&#xff0c;完整的开发了一款可以正式使用的小程序。但是有的同学可能也会产生抱怨&#xff1a;这门课不是是爬虫入门吗&#xff1f;为什么讲的…