【Linux】进程间通信及管道详细介绍(上)

前言
本节我们开始学习进程间通信相关的知识,并详细探讨一下管道,学习匿名管道和命名管道的原理和代码实现等相关操作…

目录

    • 1. 进程间通信背景
      • 1.1 进程通信的目的:
    • 2 管道的引入:
      • 2.1 匿名管道:
        • 2.1.1 匿名管道的原理:
        • 2.1.2 匿名管道的创建:
        • 2.1.3 总结管道的特点:
        • 2.1.4 管道读写规则:
        • 2.1.6 控制多个子进程(进程池):
    • 3 命名管道
      • 3.1 命名管道的创建
      • 3.2 匿名管道与命名管道的区别
      • 3.3 两个进程之间的通信
      • 3.4 命名管道的特点:

1. 进程间通信背景

我们知道进程是具独立性的。但是,相互之间还是需要进行一些信息交互,简称为 IPC (Inter - Process Communication)

1.1 进程通信的目的:

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

需要多进程进行协同处理一件事情(并发处理)。单纯的数据传输,一个进程想把数据发给另一个进程。多进程之间共享同样的资源。一个进程想让另一个进程做其他的事情,进程控制。

进程间通信的发展历史:

  • 管道 :最古老的进程间通信的形式
  • System V进程间通信 (用的非常少了,设计的非常重,更多的用来本地通信)
  • POSIX进程间通信 (设计的很轻,可以本地,可以做成网络,因为里面有套接字)

注意:我们学习的是 POSIX进程间通信

在这里插入图片描述

2 管道的引入:

刚学Linux时,就接触过
竖划线| 的 操作.

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

在这里插入图片描述

  • 1.进程间通信的前提条件:

  • 两个进程看到同一份资源才具备通信的条件
    1.通信之前,让不同的进程看到同一份资源(文件、内存块…)
    2.进程间通信,就是让两个进程如何看到同一份资源
    3.资源的不同,决定了,不同种类的通信方式!

  • 2.如何才能让两个进程看到同一份资源?:

  • 让两个进程同时看到磁盘上的同一份文件:

这种方法,要考虑CPU 和外设之间得到读写速度,效率太低了 因为通信是一个相对常规的操作,将数据刷到外设,再从外设上读取,效率太低了,

  • 让两个进程打开同一个文件:

重点注意:
管道只能单向通信。

管道是基于文件实现的,管道就是文件,两个进程之间通过文件来实现的。
进程间的通信,大部分都是基于内存级别的,不会刷新到磁盘里面,都是临时数据
进程退出,文件描述符会被关掉,但文件不一定会被关闭。

进程通信的核心思想:让两个进程获取到同一份资源

2.1 匿名管道:

2.1.1 匿名管道的原理:

在这里插入图片描述

  • 如何做到让不同的进程,看到同一份资源的呢?

fork让子进程继承—能够让具有血缘关系的进程进行进程间通信(同一文件Struct file)—常用于父子进程。。

  • struct file中有个引用计数,是指对该文件的引用数量,用于跟踪文件被多少个进程或内核对象所引用。
  • 父进程指向什么文件,子进程也指向什么文件。
  • 这也就是为什么,创建fork子进程之后,我们让父子printf打印的时候,父子进程都会向同一个显示器打印,因为它们俩都指向了同一个文件
  • Linux中可以通过特定的系统调用来判断文件是普通文件还是管道文件。
  • 自己读写数据时,就在这个文件对应的内存缓冲区里面来完成数据交互,我们把这个文件我们称之为管道

Linux系统设计的时候就设计成,如果是普通文件就往磁盘上写,如果是管道文件也往缓冲区里写,但是就
不再往磁盘上刷新了。如果是管道,就把它和对应的磁盘去掉关联。

  • Linux下一切皆文件,管道也是文件~
2.1.2 匿名管道的创建:

匿名管道主要用于父子进程之间的通信,用pipe接口来创建管道:

#include <unistd.h>
功能:创建匿名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端,是输出性参数
返回值:成功返回0,失败返回-1 错误代码
2.1.3 总结管道的特点:

在使用系统调用pipe()创建管道时,pipefd[0] ---------》管道读取端的文件描述符,pipefd[1] ---------》管道写入端的文件描述符。

  • 管道是用来进行具有血缘关系的进程做进程间通信的–常用于父子通信
  • 管道具有通过让进程间协调,提供了访问限制。(读不到了会阻塞)
  • 管道提供的是面向流式的通信服务,—面向字节流,协议
  • 管道是基于文件的,文件的生命周期是随进程的,管道的生命周期是随进程的。
  • 管道的pipefd[0]和pipefd[1]的用途是固定的,读取端只能从pipefd[0]读取数据,而写入端只能向pipefd[1]写入数据
    在这里插入图片描述
    在这里插入图片描述
    代码演示:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>int main(int argc, char *argv[]) 
{ int pipefd[2]; if (pipe(pipefd) == -1) perror("make pipe"),exit(1);if (pid == 0) {close(pipefd[0]);write(pipefd[1], "hello", 5);close(pipefd[1]);exit(0);
}close(pipefd[1]);char buf[10] = {0};read(pipefd[0], buf, 10);printf("buf=%s\n", buf);return 0;
}
2.1.4 管道读写规则:

当父进程没有写入数据的时候,子进程在等!所以,父进程写入之后,子进程才能read到数据,子进程打印读取数据要以父进程的节奏为主!

  • 管道内部,当没有数据可读时, read 读端就必须阻塞等待

    — 等待管中有数据,否则无法执行后面的代码。
    ----read调用返回-1

  • 管道内部,如果数据被写满了,写端(write)就必须阻塞等待

    -----等待管中有空间,否则此时写入会覆盖之前的数据。
    ----- write调用返回-1

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性
.

管道特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

  • 管道提供流式服务 一般而言,进程退出,管道释放,所以管道的生命周期随进程

  • 一般而言,内核会对管道操作进行同步与互斥

  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
    在这里插入图片描述

父进程给子进程派发任务:
结合上述所学知识,就可以简单写一个通过通信管道父进程给子进程派发任务执行的代码了。

 #include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <cassert>using namespace std;// 父进程控制子进程typedef void (*functor)();vector<functor> functors; // 方法集合//for debug
unordered_map<uint32_t, string> info;void f1()
{cout << "这是一个处理日志的任务, 执行的进程 ID [" << getpid() << "]"<< "执行时间是[" << time(nullptr) << "]\n"<< endl;
}void f2()
{cout << "这是一个备份数据任务, 执行的进程 ID [" << getpid() << "]"<< "执行时间是[" << time(nullptr) << "]\n"<< endl;
}
void f3()
{cout << "这是一个处理网络连接的任务, 执行的进程 ID [" << getpid() << "]"<< "执行时间是[" << time(nullptr) << "]\n"<< endl;
}void loadFunctor()
{info.insert({functors.size(), "处理日志的任务"});functors.push_back(f1);info.insert({functors.size(), "备份数据任务"});functors.push_back(f2);info.insert({functors.size(), "网络连接的任务"});functors.push_back(f3);
}int main()
{// 0. 加载任务列表loadFunctor();// 1. 创建管道int pipefd[2] = {0};if (pipe(pipefd) != 0){cerr << "pipe error" << endl;return 1;}// 2. 创建子进程pid_t id = fork();if (id < 0){// 创建失败cerr << "fork error" << endl;return 2;}else if (id == 0){// 子进程,read - 读取// 3. 关闭不需要的文件fdclose(pipefd[1]);// 子进程不断根据收到的信息,执行对应的方法// 如果没有人往管道中写,此时子进程就卡在了read这里等待别人分配任务while (true){uint32_t operatorType = 0;// 从fd为pipefd[0]的文件里读sizeof(uint32_t)个字节的内容,写到operatorType中去// 如果有数据就读取,如果没有数据就阻塞等待,等待任务的到来。ssize_t s = read(pipefd[0], &operatorType, sizeof(uint32_t));if (s == 0){cout << "我要退出了..." << endl;break;}assert(s == sizeof(uint32_t));(void)s;// 走到这里一定是一个成功的读取if (operatorType < functors.size()){functors[operatorType]();}else{cerr << "bug? operatorType = " << operatorType << endl;}}close(pipefd[0]);exit(0);}else if (id > 0){srand((long long)time(nullptr));// 父进程,write - 操作// 3. 关闭不需要的文件fdclose(pipefd[0]);// 4. 指派任务int num = functors.size();int cnt = 10;while (cnt--){// 5. 形成任务码uint32_t commandCode = rand() % num;cout << "父进程指派任务完成,任务是:" << info[commandCode] << "任务的编号是: " << cnt << endl;// 向指定的进程下达执行任务的操作write(pipefd[1], &commandCode, sizeof(uint32_t));sleep(1);}close(pipefd[1]);pid_t res = waitpid(id, nullptr, 0);if (res) cout << "wait success" << endl;}return 0;
}
2.1.6 控制多个子进程(进程池):

在这里插入图片描述

命令行中输入的|命令,其实就是一个匿名管道:

3 命名管道

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

3.1 命名管道的创建

在这里插入图片描述

int mkfifo(const char *filename,mode_t mode)
返回值:成功返回0,失败返回-1 错误代码

创建命名管道时候,要指明路径,和umask值,为了防止默认umask的扰乱,我们一开始将`umask``置为0。

管道文件是以p开头的:
在这里插入图片描述

int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}

3.2 匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open(要自己进行打开)
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完 成之后,它们具有相同的语义

命名管道的打开规则

  • 如果当前打开操作是为读而打开FIFO时,
    O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
    O_NONBLOCK enable:立刻返回成功
  • 如果当前打开操作是为写而打开FIFO时,
    O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
    O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

3.3 两个进程之间的通信

  • 匿名管道之间的通信是基于父子进程继承的关系来实现的。
    而让两个毫不相干的进程实现进程通信则是命名管道做的事情.

命名管道:通过一个fifo文件,有路径就具有唯一性,通过路径,就能找到同一资源。

只要打开的是同一个文件在内核里用的就是同一个struct file,那么指向的就是同一个inode,用的就是同一个缓冲区

命名管道是让两个进程之间是看到同一个文件,这个文件做了符号处理,相当于管道文件(通信时,数据不会刷新到磁盘上),操作系统一看到这个文件就知道了,这个文件的数据不用刷新到磁盘上,所以此时就在内存里,就有了管道。

代码演示:

#pragma#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <unistd.h>#define IPC_PATH "./.fifo"using namespace std;

服务端:

#include "comm.h"
// 读取
int main()
{umask(0);// server创建好了,client就不用创建了if (mkfifo(IPC_PATH, 0600) != 0){cerr << "mkfifo error" << endl;return 1;}int pipeFd = open(IPC_PATH, O_RDONLY);if (pipeFd < 0){cerr << "open fifo error" << endl;return 2;}#define NUM 1024// 正常的通信过程char buffer[NUM];while (true){ssize_t s = read(pipeFd, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = '\0';cout << "客户端->服务器#" << buffer << endl;}else if (s == 0){cout << "客户退出了,我也退出了" << endl;break;}else{// do nothingcout << "read: " << strerror(errno) << endl;}}close(pipeFd);cout << "服务端退出了" << endl;// 跑完之后删除管道unlink(IPC_PATH);return 0;
}

客户端:

#include "comm.h"// 写入
int main()
{int pipeFd = open(IPC_PATH, O_WRONLY);if (pipeFd < 0){cerr << "open: " << strerror(errno) << endl;return 1;}#define NUM 1024char line[NUM];// 进行通信while (true){printf("请输入你的消息# ");fflush(stdout);memset(line, 0, sizeof(line));// fgets -> C语言的函数 -> line结尾自动添加\0if (fgets(line, sizeof(line), stdin) != nullptr){line[strlen(line) - 1] = '\0';write(pipeFd, line, strlen(line));}else{break;}}close(pipeFd);cout << "客户端退出了" << endl;return 0;
}

3.4 命名管道的特点:

  • 命名管道,会在磁盘上,但是里面没有内容,不会将内存数据进行刷新到磁盘上
  • 命名管道,手段不一样,是靠系统文件路径的唯一性,来看到同一份文件的

尾声
看到这里,相信大家对这个Linux 有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦

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

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

相关文章

【中项】系统集成项目管理工程师-第5章 软件工程-5.1软件工程定义与5.2软件需求

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

【数据结构】AVL树(图文解析 + 代码实现)

目录 1、AVL树的概念 2、AVL树结点的定义 3、AVL树的插入 4、AVL树的旋转 4.1 左单旋 4.2 右单旋 4.3 右左双旋 4.4 左右双旋 5、AVL树的验证 6、AVL树的性能 前面对map/multimap/set/multiset进行了简单的介绍&#xff0c;会大仙&#xff0c;这几个容器有个共同点是…

【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !

目录 C语言指针精讲1. 什么是指针&#xff1f;1.1 指针的内存模型1.1.1 指针演示输出 1.2 指针运算1.2.1 指针算术运算输出1.2.2 指针与数组的关系输出 1.3 指针类型1.3.1 不同类型的指针示例输出1.3.2 void 指针输出 1.4 指针与内存管理动态内存分配输出 1.5 指针与内存泄漏1.…

vue 两个页面切换, 再回到当前页,还是离开前的数据

1、要保证页面的name 和 建路由的大小写一致 2、页面不用生命周期--activated 调接口刷新

在 Kubernetes 中设置 Pod 优先级及其调度策略详解

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

【论文解读】大模型算法发展

一、简要介绍 论文研究了自深度学习出现以来&#xff0c;预训练语言模型的算法的改进速度。使用Wikitext和Penn Treebank上超过200个语言模型评估的数据集(2012-2023年)&#xff0c;论文发现达到设定性能阈值所需的计算大约每8个月减半一次&#xff0c;95%置信区间约为5到14个月…

雪花算法 集群uid重复问题 uid-generator-spring-boot-starter

1、在生成环境 在某个业务使用该插件生成uid,由于业务整合了 mybatis-plus模块 2、该业务是分部署集群部署以及使用的多线程获取uid&#xff0c;使用中发现唯一建冲突&#xff0c;生成的uid有重复。 然后查看日志发现 workerId 始终为0 怀疑是生成workerId出了问题。 查看跟…

开发日志:windows修复SSL漏洞CVE-2016-2183(3389端口)

漏洞危害&#xff1a; 具有足够资源的中间人攻击者可利用此漏洞,通过“birthday”攻击检测会在固定密码与已知纯文本之间泄露 XOR 的冲突,进而泄露密码文本(例如安全 HTTPS Cookie),并可能导致劫持经认证的会话。 参见《支持SSL 64位块大小的密码套件(SWEET32)-修复方案》 参考…

主流树模型讲解、行列抽样、特征重要性梳理总结

本文旨在总结一下常见树模型的行、列抽样特点以及特征重要性的计算方式&#xff0c;也会带着过一遍算法基本原理&#xff0c;一些细节很容易忘记啊。 主要是分类和回归两类任务&#xff0c;相信能搜索这篇文章的你&#xff0c;应该对树模型有一定的了解。 可以搜索 总结 &…

老鼠后五毒也来凑热闹!网红食品惊现「壁虎头」,胖东来已下架…

上周&#xff0c;老鼠有点忙&#xff0c;比如其连续被曝出&#xff0c;出现在了方便面知名品牌的调料包、知名连锁餐饮品牌的黄焖鸡饭中。‍‍‍‍‍‍‍‍‍‍‍‍‍‍ 在小柴「被「添加」进方便面、黄焖鸡饭&#xff1f;老鼠最近忙疯了……」这篇文章的评论区&#xff0c;柴油…

计算机视觉与面部识别:技术、应用与未来发展

引言 在当今数字化时代&#xff0c;计算机视觉技术迅速发展&#xff0c;成为人工智能领域的一个重要分支。计算机视觉旨在让机器理解和解释视觉信息&#xff0c;模拟人类的视觉系统。它在各行各业中发挥着重要作用&#xff0c;从自动驾驶汽车到智能监控系统&#xff0c;再到医疗…

Python:对常见报错导致的崩溃的处理

Python的注释&#xff1a; mac用cmd/即可 # 注释内容 代码正常运行会报以0退出&#xff0c;如果是1&#xff0c;则表示代码崩溃 age int(input(Age: )) print(age) 如果输入非数字&#xff0c;程序会崩溃&#xff0c;也就是破坏了程序&#xff0c;终止运行 解决方案&#xf…

贪心算法(三) ---cmp_to_key, 力扣452,力扣179

目录 cmp_to_key 比较函数 键函数 cmp_to_key 的作用 使用 cmp_to_key 代码解释 力扣452 ---射气球 题目 分析 代码 力扣179 ---最大数 题目 分析 代码 cmp_to_key 在Python中&#xff0c;cmp_to_key 是一个函数&#xff0c;它将一个比较函数转换成一个键函数…

Problems retrieving the embeddings data form OpenAI API Batch embedding job

题意&#xff1a;从OpenAI API批量嵌入作业中检索嵌入数据时遇到问题 问题背景&#xff1a; I have to embed over 300,000 products description for a multi-classification project. I split the descriptions onto chunks of 34,337 descriptions to be under the Batch e…

Nginx优化、防盗链

目录 Nginx优化 隐藏版本信息 网站缓存 日志切割 超时时间 更改进程数 网页压缩 防盗链 在使用源码软件包安装过Nginx服务&#xff0c;具体步骤看上一篇文章 功能模块位置 在Nginx的解压目录下的auto目录内的options文件可以查看Nginx可以安装的功能模块 [rootlocal…

数据结构初阶-单链表

链表的结构非常多样&#xff0c;以下情况组合起来就有8种&#xff08;2 x 2 x 2&#xff09;链表结构&#xff1a; 而我们主要要熟悉的单链表与双向链表的全称分别为&#xff1a;不带头单向不循环链表&#xff0c;带头双向循环链表&#xff0c;当我们对这两种链表熟悉后&#x…

重生之我们在ES顶端相遇第5章-常用字段类型

思维导图 前置 在第4章&#xff0c;我们提到了 keyword&#xff08;一笔带过&#xff09;。在本章&#xff0c;我们将介绍 ES 的字段类型。全面的带大家了解 ES 各个字段类型的使用场景。 字段类型 ES 支持以下字段类型&#xff08;仅介绍开发中常用&#xff0c;更多内容请自…

AI App Store-AI用户评价-多维度打分对比pk-AI社区

C端用户、创作者、AI达人们在选择众多国内外AI厂商的服务时候往往感到一头雾水&#xff0c;那么多功能接近的AI应用(智能对话类、文档总结类、文生图、AI搜索引擎) 究竟在不同用户需求场景下表现怎么样。大部分人如果有需求都会所有平台都尝试一遍&#xff0c;比如一个博主生成…

C++自定义字典树结构

代码 #include <iostream> using namespace std;class TrieNode { public:char data;TrieNode* children[26];bool isTerminal;TrieNode(char ch){data ch;for (int i 0; i < 26; i){children[i] NULL;}isTerminal false;} }; class Trie { public:TrieNode* ro…

基于区块链技术的中药饮片代煎配送服务与监管平台

业务背景 近年来&#xff0c;随着公众对中医药青睐有加&#xff0c;中药代煎服务作为中医药现代化的重要一环&#xff0c;在全国各地蓬勃兴起。鉴于传统煎煮方式的繁琐耗时&#xff0c;医疗机构纷纷转向与第三方中药饮片企业合作&#xff0c;采用集中代煎模式。这些第三方煎药中…