Linux-进程间通信(进程间通信介绍、匿名管道原理及代码使用、命名管道原理及代码使用)

一、进程通信介绍

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

进程=内核数据结构+代码和数据

每个进程都拥有自己独立的PCB与数据,由于进程与进程间是相互独立的,互相看不到也不想看到对方的数据,所以进程间通信的前提是让不同进程看到同一份资源。进程间通信一定是某个进程需要通信,让操作系统创建一个共享资源,而用户不能直接操控操作系统,所以操作系统需要提供许多的系统调用,系统调用不同就会创建出不同的共享资源,那么进程间的通信方式也会不同

1.3进程间通信的方式

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

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

POSIX IPC

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

二、匿名管道

2.1匿名管道原理

当一个进程以读和写的方式分别打开一个文件,由于打开方式的不同,就会创建两个struct file,每个文件都有自己的属性、数据、更重要的是存在内核级的文件缓冲区,两个struct file除了打开方式字段不同,其余信息都是相同的,所以操作系统不会将文件的信息在拷贝一份,两个struct file指向的文件信息是一份。当该进程创建子进程,子进程会继承父进程的文件描述符表,此时父子进程可以对同一个文件进行读取操作,该文件中的缓冲区就相当于一个共享资源,这个缓冲区也成为道,两个进程间就可以进行通信了

当我们想让父进程写入数据,子进程读取数据时,我们只需要关闭父进程的读文件,关闭子进程的写文件即可,这样就可以做到子进程向管道中写数据,父进程向文件中读数据了

【注意】:

  1. 管道只能进行通单向信,及只能一个进程进行读一个进程写,不能让两个进程既可以读也可以写,所以我们需要关闭对应不需要功能的文件描述符(不关闭也是可以的,但是建议关掉,防止误写误读)
  2. 写端写入的数据会保存在内核级文件缓冲区中,直到被读走

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

int fd[ ]为输出型参数,会带回以读和写方式打开的文件的fd,由于我们知道文件的fd不知道文件的名字,所以称为匿名管道

2.3 代码理解(父进程读,子进程写)
#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<cstring>
#include <sys/types.h>
#include<string>
#include<sys/wait.h>
#include <sys/types.h>
#define SIZE 1024void chilewrite(pid_t wfd)
{while(1){std::string message="father I am your child process!";write(wfd,message.c_str(),message.size());sleep(2);}
}void fathread(pid_t rfd)
{char buffer[SIZE];while(1){int n=read(rfd,buffer,sizeof(buffer)-1);if(n>0){buffer[n]='\0';std::cout<<buffer<<std::endl;}else if(n==0){// 如果read的返回值是0,表示写端直接关闭了,我们读到了文件的结尾std::cout<<"写端关闭了"<<std::endl;break;}else{std::cerr<<"read err"<<std::endl;break;}}
}int main()
{//创建管道int pipefd[2];int n=pipe(pipefd);if(n<0){std::cerr<<"errno:"<<errno<<" errstring:"<<strerror(errno)<<std::endl;return 1;}//创建子进程pid_t id=fork();if(id==0){//子进程std::cout<<"子进程准备就绪,准备写入数据了"<<std::endl;close(pipefd[0]);chilewrite(pipefd[1]);close(pipefd[1]);exit(0);}//父进程std::cout<<"父进程准备就绪,准备读取数据了"<<std::endl;close(pipefd[1]);fathread(pipefd[0]);close(pipefd[0]);//进程等待int status=0;pid_t rid=waitpid(id,&status,0);if(rid>0){std::cout << "wait child process done, exit sig: " << (status&0x7f) << std::endl;std::cout << "wait child process done, exit code(ign): " << ((status>>8)&0xFF) << std::endl;}return 0;
}
2.4 管道的四种情况
  1. 如果管道内部是空的并且写端没有关闭,此时读取条件不具备,读端会被阻塞,等待写入数据
  2. 如果管道内部被写满了并且读端没有关闭,此时写入条件不具备,写端会被阻塞,等待读端读取数据
  3. 如果管道一直在读,但是已经关闭了写端,此时读端read返回值会一直读到0,表示读到了文件末尾
  4. 如果管道一直在写,但是已经关闭了读端,此时OS会直接使用13号信号杀死进程,代表进程异常
2.5 管道的五种特征
  1. 匿名管道只能进行有血缘关系的进程之间进行通信,因为匿名管道是依靠子进程继承父进程的文件描述符表实现的(通常用于实现父子进程之间的通信)
  2. 管道内部自带进程的同步机制,多执行流执行代码时具有明显的顺序性,写入管道的数据直到被读取之前会保持在管道缓冲区中(如果缓冲区未满),而读取操作则会等待直到有数据可读,这种机制避免了同时读写导致的数据损坏问题
  3. 管道文件的生命周期是随进程的,当所有打开该文件的进程都退出后,该文件资源也会被释放
  4. 管道文件在进行通信时是面向字节流的,读与写的次数不是一 一匹配的,数据没有明确的分割,一次拿多少数据都行
  5. 管道通信是一种特殊的半双工模式。半双工通信允许数据在两个方向上传输,但不能同时进行。这意味着在任何时候,数据只能在一个方向流动。一旦一方开始发送数据,另一方必须等待接收完毕后才能开始发送。全双工通信允许数据同时在两个方向上进行传输,无需等待。由于半双工模式是可以双向传输数据的,但是管道只能单向通信,所以是特殊的半双工模式
2.6 管道的读写规则
  • 当没有数据可读时

        O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止

        O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

  • 当管道满的时候

        O_NONBLOCK disable: write调用阻塞,直到有进程读走数据

        O_NONBLOCK enable:调用返回-1,errno值为EAGAIN 如果所有管道写端对应的文件描述符被关闭,则read返回0

  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程 退出
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

PIPE_BUF一般至少为512个字节,在Linux下,PIPE_BUF为4096个字节,当写入的的数据量不大于PIPE_BUF时,写入操作是安全的,不会发生写一半数据就被读走的情况,如果写入的数据量大于PIPE_BUF,则可能会发写入的数据提前被读走一部分

三、命名管道

匿名管道只能实现具有血缘关系进程之间的通信,而命名管道可以实现两个毫无相关的进程之间的通信,下面先介绍一下命名管道的原理

3.1命名管道原理

        当一个进程以读的方式打开一个文件,该进程会有自己的进程PCB和文件描述符表,同时会创建一个struct file

        当另一个进程以写的方式也打开这个文件,该进程也会有自己的进程PCB、文件描述符表,并且也会创建一个struct file,但是由于这两个进程打开的文件是一样的,而struct file也就是打开文件的方式不同,所以两个struct file都是指向的同一份文件信息,也指向了同一个内核级文件缓冲区,那么这两个毫无关系的进程就指向了同一段空间,就可以进行通信了。

        怎么确保两个进程打开的是同一个文件呢?---------文件的路径

3.2 mkfifo接口
  • 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
mkfifo filename

  • 命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);#参数:filename为文件名 mode为创建文件的权限
#头文件:#include <sys/types.h>#include <sys/stat.h>#返回值:成功返回0,失败返回-1,并且设置错误码
3.2 代码使用举例

namedpipe.hpp:

#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <cerrno>
#include <unistd.h>
#include <iostream>
#include <fcntl.h>#define Defaultfd -1
#define Creater 1
#define User 2
#define SIZE 128
#define Read O_RDONLY
#define Write O_WRONLYconst std::string path = "./myfifo";
class NamedPipe
{
private:bool OpenNamePipe(int mode){_fd = open(_fifo_path.c_str(), mode);if (_fd < 0)return false;return true;}public:NamedPipe(const std::string path, int who): _fifo_path(path), _id(who), _fd(Defaultfd){if (_id == Creater){int ret = mkfifo(_fifo_path.c_str(), 0666);if (ret != 0){std::perror("mkfifo");}std::cout << "Creater creat namedpipe!" << std::endl;}}bool OpenforRead(){return OpenNamePipe(Read);}bool OpenforWrite(){return OpenNamePipe(Write);}int ReadNamedPipe(std::string *out){char buffer[SIZE];int n = read(_fd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = '\0';*out = buffer;}return n;}int WriteNamedPipe(std::string& in){return write(_fd,in.c_str(),in.size());}~NamedPipe(){if (_id == Creater){int ret = unlink(_fifo_path.c_str());if (ret != 0){std::perror("unlink");}std::cout << "Creater free namedpipe!" << std::endl;}}
private:std::string _fifo_path;int _id;int _fd;
};

client.cpp:

#include "namedpipe.hpp"
int main()
{// 以使用者的身份打开NamedPipe fifo(path, User);if (fifo.OpenforWrite()){std::cout << "Client open fifopipe for write!" << std::endl;while (true){std::cout << "Please enter message" << std::endl;std::string message;std::getline(std::cin, message);fifo.WriteNamedPipe(message);}}return 0;
}

server.cpp:

#include "namedpipe.hpp"
int main()
{// 以创建者的身份打开NamedPipe fifo(path, Creater);if (fifo.OpenforRead()){std::cout << "Server open fifopipe for read!" << std::endl;while (true){std::string message;int n = fifo.ReadNamedPipe(&message);if (n > 0){std::cout << "Client:" << message << std::endl;}else if (n == 0){std::cout << "写端关闭了,读端也要关闭!" << std::endl;break;}else if (n < 0){std::perror("read"); break;}}}return 0;
}

结果展示:

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

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

相关文章

【Android】SharedPreferences阻塞问题深度分析

前言 Android中SharedPreferences已经广为诟病&#xff0c;它虽然是Android SDK中自带的数据存储API&#xff0c;但是因为存在设计上的缺陷&#xff0c;在处理大量数据时很容易导致UI线程阻塞或者ANR&#xff0c;Android官方最终在Jetpack库中提供了DataStore解决方案&#xf…

spring的高阶使用技巧1——ApplicationListener注册监听器的使用

Spring中的监听器&#xff0c;高阶开发工作者应该都耳熟能详。在 Spring 框架中&#xff0c;这个接口允许开发者注册监听器来监听应用程序中发布的事件。Spring的事件处理机制提供了一种观察者模式的实现&#xff0c;允许应用程序组件之间进行松耦合的通信。 更详细的介绍和使…

Kubernetes 声明式语言 YAML

什么是 YAML YAML&#xff08;YAML Ain’t Markup Language&#xff09;是一种可读的数据序列化语言&#xff0c;通常用于配置文件、数据序列化和交换格式。YAML 的设计目标是易读易写&#xff0c;并且能够映射到动态语言中的数据结构 YA加粗样式ML 是 JSON 的超集&#xff0…

HFSS19 官方案例教程W03 - SMA接头与微带分支

SMA接头与微带分支 1►射频接头简介 连接器是电子测量中必不可少的重要部件,无论测试仪表还是DUT,无论线缆还是附件,处处都有形形色色的不同连接器的身影。对于射频工程师而言,经常用到的连接器有N型、BNC型、SMA型、3.5 mm、2.92 mm、2.4 mm、1.85 mm、1 mm这几种 (上…

CSS学习(选择器、盒子模型)

1、CSS了解 CSS&#xff1a;层叠样式表&#xff0c;一种标记语言&#xff0c;用于给HTML结构设置样式。 样式&#xff1a;文字大小、背景颜色等 p标签内不能嵌套标题标签。 px是相对于分辨率而言的&#xff0c; em是相对于浏览器的默认字体&#xff0c; rem是相对于HTML根元…

【MySQL】MVCC的实现原理

【MySQL】MVCC的实现原理 MVCC简介事务的隔离级别读未提交&#xff08;Read Uncommitted&#xff09;概念分析 读已提交&#xff08;Read Committed&#xff09;概念分析结论 可重复读&#xff08;Repeatable Read&#xff09;概念分析结论 串行化&#xff08;Serializable &am…

Java调用接口获得图片输入流InputStream并返回给前端

效果&#xff1a; 代码&#xff1a; export const getPhotoById params > get(${base}/weda/myLecture/poster/template/getPhotoById?id${params.id}&isPreview${params.isPreview},{}); // 获取原始的大图后端 Overridepublic void getPhotoById(PosterTemplate dt…

三、VUE数据代理

一、初识VUE 二、再识VUE-MVVM 三、VUE数据代理 Object.defineProperty() Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性&#xff0c;或修改其现有属性&#xff0c;并返回此对象。 Object.defineProperty() 数据代理 通过一个对象代理另一个对象中属…

【启明智显技术分享】关于工业级HMI芯片Model3C的TEXT RODATA,必须要映射到PSRAM吗?

一、Model3C芯片介绍 Model3C芯片性能 Model3C&#xff08;简称M3C&#xff09;是一款基于 RISC-V 的高性能、国产自主、工业级高清显示与智能控制 MCU&#xff0c;配备强大的 2D 图形加速处理器、PNG/JPEG 解码引擎、丰富的接口&#xff0c;支持工业宽温&#xff0c;具有高…

d16(149-153)-勇敢开始Java,咖啡拯救人生

跳过了p151 四小时的讲题我不敢听&#xff1a;) Stream Stream流&#xff0c;是JDK8后新增的API&#xff0c;可以用于操作集合或者数组的数据 优势&#xff1a;大量结合了Lambda的语法风格&#xff0c;该方式更强大更简单&#xff0c;代码简洁&#xff0c;可读性好 常用方法 …

Anomalib:用于异常检测的深度学习库!

大家好,今天给大家介绍了一个用于无监督异常检测和定位的新型库:anomalib,Github链接:https://github.com/openvinotoolkit/anomalib 简介 考虑到可重复性和模块化,这个开源库提供了文献中的算法和一组工具,以通过即插即用的方法设计自定义异常检测算法。 Anomalib 包…

Python 全栈体系【四阶】(三十七)

第五章 深度学习 八、目标检测 3. 目标检测模型 3.1 R-CNN 系列 3.1.1 R-CNN 3.1.1.1 定义 R-CNN(全称 Regions with CNN features) &#xff0c;是 R-CNN 系列的第一代算法&#xff0c;其实没有过多的使用“深度学习”思想&#xff0c;而是将“深度学习”和传统的“计算…