当前位置: 首页 > news >正文

Linux:进程通信、管道通信

目录

一、认识进程通信

二、管道通信

1.匿名管道

2.命名管道


一、认识进程通信

在了解进程之间的通信之前,我们知道进程具有独立性,不会干扰彼此的运行,但是进程是可以互相发送消息的,这种通信不会影响进程的独立。

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

进程之间通信的本质,就是让两个进程同时操作同一份资源。这份资源只能由操作系统提供,不能由进程之间的任何一方提供,否则,就会破坏进程的独立性。

  • 进程通信的分类
管道
        匿名管道pipe
        命名管道
System V IPC
        System V 消息队列
        System V 共享内存
        System V 信号量
POSIX IPC
        消息队列
        共享内存
        信号量
        互斥量
        条件变量
        读写锁

二、管道通信

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

1.匿名管道

匿名管道通常用于具有亲缘关系的进程之间,比如父子进程之间。

管道通信是半双工模式通信,即只能单方向通信。

匿名管道文件是内存级文件,即用即销毁,在磁盘中没有其对应的存储。

  • 从文件描述符角度理解匿名管道

一个进程的PCB关联着一张文件描述符表,0、1、2默认是被三个标准流占用的,如果一个进程以'r'的方式打开一个文件,又以'w'的方式打开同一个文件,在文件描述符表中是会占用两个文件描述符的。

 

在父进程fork创建子进程,发生浅拷贝,各自的fd指向的内容是相同的。

 

 

由于匿名管道是单向通信,因此通信时,只能有一个进程在读管道,也只能有一个进程在向管道写数据,所以,父进程和子进程管道通信之前,父子进程要各自关掉一个描述符,(由于此时的文件被多个进程打开,当其中一个进程关文件,则只是将文件的引用计数减一,并不是真的关掉文件)。

  • 父子进程管道通信的流程

 

 

  • 匿名管道通信时的4种情况

1.父进程阻塞:管道内没有数据,并且此时子进程不关闭写端文件fd,则读端(父进程)就要阻塞等待,直到管道中有数据。

2.子进程阻塞:管道大小被写满,此时父进程不关闭读端文件fd,则写端(子进程)就要阻塞等待

3.如果写端不再向管道中写数据,并且此时关闭了写端文件fd,则父进程读完管道中的数据后,最后会读到0,用来表示读到了文件尾。

4.如果读端不再从管道中读数据,并且此时关闭了读端文件fd,而写端文件还在向管道中写数据,则操作系统会直接终止写入的进程(子进程),通过发送信号13 SIGPIPE来杀掉子进程。

  • 管道通信的5种特性

1.由于管道通信时,要么是读端在读,要么是写端在写,不会出现一边读一边写的情况,所以管道自带同步机制。

2.管道通信常见于具有亲缘关系的进程,比如父子进程。

3.管道通信是面向字节流的。

4.父进程或者子进程退出,则管道自动释放,管道文件的生命周期是跟随进程的。

5.管道通信是半双工模式,并且是一种特殊的半双工模式,特殊指在一次通信中,写端只能写,读端只能读。

  • 匿名管道的实例

 

 

int main()
{//1.父进程创建管道int pipefd[2];int n = pipe(pipefd);if(n < 0)return 1;//管道规定,0为读,1为写printf("pipefd[0]:%d,pipefd[1]:%d\n",pipefd[0],pipefd[1]);//2.创建子进程pid_t id = fork();if(id == 0){//子进程close(pipefd[0]);Pro_Wrte(pipefd[1]);exit(0);}    //父进程close(pipefd[1]);Pro_Read(pipefd[0]);wait(NULL);return 0;
}

 

  •  实现一个进程池

主进程有多个子进程。要求实现负载均衡,给每个进程均衡分配任务。

2.命名管道

进程通信本质就是让不同进程在内存级别看到同一份资源,而两个毫不相干的进程,要看到同一份资源,那么这份资源就得是磁盘上面有路径、有文件名的一个文件。

命名管道和匿名管道唯一的区分就是,命名管道对应在磁盘上是有名称的特殊文件,但是作为内存中的管道来通信同样不需要和磁盘做IO。

总结就是,命名管道就是磁盘上的一个文件,只不过是一种特殊文件,这个文件内所发生的变动不需要刷新到磁盘上同步。

  • 创建一个命名管道

 

man mkfifo

 

 

mkfifo是命令行指令,Fifo是命名管道的名字,同样可以发现,文件类型这一位,目录文件是d,普通文件是-,而管道文件就是p开头。

 

 

  • 代码中创建命名管道 

 

man 3 mkfifo

 

 

  •  模拟服务器、客户机不同进程通信

接下来,写一段代码,模拟不同进程之间的通信,模拟服务器、客户机的通信,这里不考虑网络部分,只假设同一台机器上,两个可执行程序变成两个毫不相干的进程。

模拟服务器端的那个进程要创建命名管道,服务器往往是读端。

模拟客机的那个进程要给这个管道里面写数据。

Makefile

 

.PHONY:all
all:PipeClient PipeServePipeServe:PipeServe.ccg++ -o $@ $^ -std=c++11
PipeClient:PipeClient.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f PipeClient PipeServe

公共文件

#pragma once#ifndef __COMM_HPP__ 
#define __COMM_HPP__#include <iostream>
#include <string>#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#include <cerrno>
#include <cstring>#include <unistd.h>
using namespace std;#define Mode 0666
//目的是为了让客机看见这个管道文件
#define Path "./fifo"//命名管道
class Fifo
{public:Fifo(const string& path):_path(path){umask(0);int n = mkfifo(_path.c_str(),Mode);if(n == 0){cout << "mkfifo success!!!" << endl;}else{cerr << "mkfifo fail:->errno->" << errno<< "->"<<strerror(errno) << endl;}}~Fifo(){//可以用在代码中的删除文件的系统调用int ret = unlink(_path.c_str());if(ret == 0){cout << "mkfifo file:->"<< _path << "remove success!!!" << endl;}else{cout << "mkfifo fifl remove fail:->"<< errno << "->" << strerror(errno)<< endl;}}private:string _path;//文件路径+文件名
};#endif

 

服务作读端:

 

#include "Comm.hpp"#include <unistd.h>
int main()
{Fifo ff(Path);sleep(1);int read_fd = open(Path,O_RDONLY);if(read_fd < 0){cerr << "open fail:->errno->" << errno<< "->"<<strerror(errno) << endl;return 1;}cout << "open success" << endl;char buffer[1024];while(1){ssize_t n = read(read_fd,buffer,sizeof(buffer)-1);if(n < 0){cerr << "read fail:->errno->" << errno<< "->"<<strerror(errno) << endl;break;}else if(n == 0){//读到了文件尾cout << "client quit,server close" << endl;break;}else {buffer[n] = 0;cout << "client say " << buffer << endl;}}close(read_fd);return 0;
}

 

 客机作写端

 

#include "Comm.hpp"int main()
{int write_fd = open(Path,O_WRONLY);if(write_fd < 0){cerr << "open fail:->errno->" << errno<< "->"<<strerror(errno) << endl;return 1;}string in;while(1){cout << "Please cin Your Message" << endl;getline(cin,in);if(in == "quit")break;ssize_t n = write(write_fd,in.c_str(),in.size());if(n < 0){cerr << "write fail:->errno->" << errno<< "->"<<strerror(errno) << endl;break;}}close(write_fd);return 0;
}

 

 最终效果

 


需要我们记住的是,匿名管道常常用于具有亲缘关系的进程之间通信,而命名管道可以帮助两个毫不相干的进程通信。

管道通信的本质就是利用了多个文件描述符指向的多个文件结构体的缓冲区是同一份,由于毫不相干的进程要利用管道通信,就必须要让这两个进程都能“看到”这同一个管道文件因此命名管道常常用于不相干的进程之间通信,而具有亲缘关系的进程只用匿名管道即可通信。

 


http://www.mrgr.cn/news/80741.html

相关文章:

  • 常用的es操作
  • 【Python】argparse模块
  • 循环神经网络(RNN)在时序预测中的应用与优势
  • 【基础】nginx简单配置
  • Spire.PDF for .NET【页面设置】演示:向 PDF 文档添加页码
  • 大数据相关标准——GB/T 42130-2022 智能制造 工业大数据系统功能要求(山东省大数据职称考试)
  • PYQT5程序框架
  • Go-FastDFS文件服务器一镜到底使用Docker安装
  • 【AI图像生成网站Golang】项目架构
  • 基础数据结构---栈
  • linux_x64 下的一般汇编函数与syscall调用约定
  • 安卓换源资源记录
  • 修改ubuntu apt 源及apt 使用
  • HW机试题库(个人总结)
  • Fast-Planner项目复现(Ubuntu 20.04 ROS Noetic)
  • 设计模式2
  • harbor离线安装 配置https 全程记录
  • Flutter环境搭建
  • vue复习
  • zlmediakit搭建直播推流服务
  • ubuntu server 安装
  • vue2,vue3 中 v-for 和v-if的优先级
  • AI自我进化的新篇章:谷歌DeepMind推出苏格拉底式学习,语言游戏解锁无限潜能
  • 搭建分布式Spark集群
  • K8s中 statefulset 和deployment的区别
  • 音频开发中常见的知识体系