Linux - 进程间通信(管道)

文章目录

    • 一、进程间通信的目的
    • 二、进程间通信的本质
    • 三、管道
      • 1、介绍
      • 2、匿名管道
      • 3、命名管道


一、进程间通信的目的

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

二、进程间通信的本质

让不同的进程看到同一份资源。

三、管道

1、介绍

管道(Pipe)是一种基本的进程间通信(IPC,Inter-Process Communication)机制。它允许一个进程的输出直接作为另一个进程的输入,从而实现数据的传递,这种机制在Unix和类Unix系统(如Linux)中非常常见。分为:匿名管道和命名管道。

在这里插入图片描述
在这里插入图片描述

2、匿名管道

(1)为什么叫匿名管道?
该管道不需要路径、不需要名字。
(2)匿名管道的特性

  1. 面向字节流。
  2. 单向数据通信
  3. 用于具有血缘关系的进程(一般用于父子)。
  4. 文件的生命周期随进程,管道也是。
  5. 管道自带同步互斥等机制机制。
  6. 在Linux系统中,匿名管道的大小通常是固定的,并且相对较小。一般来说,匿名管道的缓冲区大小限制在4KB左右,与PIPE_BUF常量(它定义了管道缓冲区的最小原子单位,并影响着进程间通过管道通信的行为)有关。

(3)创建匿名管道
功能:创建一无名管道。
函数原型:

int pipe(int fd[2]);

头文件:

#include <unistd.h>

参数:

fd:输出型参数,文件描述符数组, 其中fd[0]表示读端, fd[1]表示写端。

返回值:

成功返回0,失败返回错误代码。

在这里插入图片描述

使用:
通过匿名管道使父进程向子进程发送信息。

 1 #include <iostream>                                                             2 #include <string>                                                               3 #include <cstdlib>                                                              4 #include <unistd.h>                                                             5 #include <sys/types.h>                                                          6 #include <sys/wait.h>                                                           7                                                                                 8 int main()                                                                      9 {                                                                               10     // 1、创造管道                                                              11     int p[2] = {0};                                                             12     int n = pipe(p);                                                            13     if (n != 0)                                                                 14     {                                                                           15         std::cerr << "管道错误" << std::endl;                                   16         return 1;                                                               17     }                                                                           18                                                                                 19     // 2、创建子进程                                                            20     pid_t id = fork();                                                          21     if(id < 0)                                                                  22     {                                                                           23         std::cerr << "创建子进程错误" << std::endl;                             24     }                                                                           25     //3、进行通信                                                               26     //父进程 - 写                                                               27     else if(id > 0)                                                             28     {                                                                           29         //关闭读端   30         close(p[0]);                                                            31                                                                                 32         std::string s = "i am father";                                          33         write(p[1],s.c_str(),s.size());                                         34                                                                                                                                 35         close(p[1]);                                                            36         pid_t rid = waitpid(0,nullptr,0);                                       37         (void)rid;                                                              38                                                                                 39     }                                                                           40     //子进程 -读                                                                41     else if(id == 0)                                                            42     {                                                                           43         //关闭写端                                                              44         close(p[1]);                                                            45                                                                                 46         //读                                                                    47         char buff[1024];                                                        48         int m = read(p[0],buff,1024);                                           49         buff[m] = 0;                                                            50         std::cout<<buff<<std::endl;                                             51                                                                                 52         close(p[0]);                                                            53         exit(0);                                                                54     }                                                                           55                                                                                 56     return 0;                                                                   57 }            

在这里插入图片描述
站在文件描述符角度-深度理解管道
在这里插入图片描述

站在内核角度-管道本质
在这里插入图片描述
所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想。

管道读写的规则:

  1. 管道为空且正常:rand会阻塞。
  2. 管道为满且正常:write会阻塞。
  3. 管道写端关闭且读端继续:读端读到0,表示读到文件结尾。
  4. 管道写端继续且读端关闭:写入进程会被终止(os发送信号13终止程序)。

(4)使用匿名管道实现简单的进程池
进程池

进程池(Process Pool)是一种并发编程的模型,用于管理和复用多个进程,以提高系统的效率和性能。以下是关于进程池的详细解释:
一、定义与组成
定义:进程池是资源进程和管理进程组成的技术应用,通过预先创建好的空闲进程,管理进程会把工作分发到空闲进程来处理。
组成:进程池主要由资源进程和管理进程组成。资源进程是实际执行任务的进程,而管理进程则负责创建、管理和调度这些资源进程。
二、工作原理
任务分配:管理进程负责将任务分配给空闲的资源进程。这通常通过某种任务队列来实现,资源进程从队列中获取任务并执行。
进程交互:管理进程和资源进程之间需要交互,以传递任务、状态和结果等信息。这种交互通常通过IPC(进程间通信)、信号、管道等机制来实现。
资源回收:当资源进程完成任务后,管理进程会回收这些进程,以便它们可以被重新用于执行其他任务。
三、优势与应用
减少性能开销:进程池通过复用进程来减少因频繁创建和销毁进程而带来的性能开销。
提高并发性能:在处理大量并发任务时,进程池可以显著提高系统的并发性能。
易于管理:进程池提供了一种集中管理多个进程的方式,使得系统的可维护性和可扩展性得到提高。

代码实现:

对进程进行描述
Channel.hpp

#pragma once
#include <unistd.h>//先描述 -每一个进程
class Channel
{
public:Channel(int fd,pid_t id):_fd(fd),_id(id){}//执行任务void Send(int num){write(_fd,&num,sizeof(num));}int _fd;pid_t _id;
};

任务
Task.hpp

#pragma once
#include <iostream>
#include <unordered_map>
#include <functional>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>using task_t = std::function<void()>;void func1()
{std::cout << "执行任务1" << std::endl;
}void func2()
{std::cout << "执行任务2" << std::endl;
}
void func3()
{std::cout << "执行任务3" << std::endl;
}
void func4()
{std::cout << "执行任务4" << std::endl;
}//任务
class Task
{
public://初始化Task(){srand(time(nullptr));mp[0] = func1;mp[1] = func2;mp[2] = func3;mp[3] = func4;}//获取任务号int SelectTask(){return rand() % mp.size();}//添加任务void AddTask(task_t t){mp[mp.size()] = t;}//执行任务void Excute(int number){if(mp.find(number) == mp.end())return;mp[number]();}private://编号与任务的隐式哈希std::unordered_map<int, task_t> mp;
};//全局
Task tk;//实行的方法
void Worker()
{while (true){int num;int n = read(0,&num,sizeof(num));if(n > 0){tk.Excute(num);}else if(n == 0){break;}}
}

进程池
ProcessPool.hpp

#pragma once
#include <string>
#include <vector>
#include <cstdlib>
#include <sys/wait.h>
#include "Task.hpp"
#include "Channel.hpp"//包装器
using work_t = std::function<void()>;//错误码
enum
{OK = 0,UsageError,PipeError,ForkError
};//进程池
class ProcessPool
{
public:ProcessPool(int num, work_t work = Worker): _num(num),_work(work){}// 初始化管道和子进程int InitProcessPool(){for (int i = 0; i < _num; i++){// 先创建管道int p[2] = {0};int n = pipe(p);if (n != 0){return PipeError;}// 创建子进程int id = fork();if (id == 0) // 子进程{// 将不用的端口关闭close(p[1]);// 将子进程继承下来的写端都关闭了for (auto &e : channels){close(e._fd);}// 重定向到0端口dup2(p[0], 0);// 执行任务_work();exit(0);}else if (id > 0) // 父进程{// 关掉不用的读端close(p[0]);// 对子进程管理channels.emplace_back(Channel(p[1], id));}else{return ForkError;}}return OK;}// 执行任务void DispatchTask(){// 轮询int count = 10;int index = 0;while (count--){// 获取任务int n = tk.SelectTask();std::cout << "n: " << n << std::endl;// 派发任务Channel channel = channels[index];std::cout << "index:" << index << " fd: " << channel._fd << " id: " << channel._id << std::endl;channel.Send(n);index++;index %= _num;sleep(2);}}// 结束任务void CleanProcessPool(){for (auto &e : channels){close(e._fd);waitpid(e._id,nullptr,0);}}void Debug(){std::cout << "num: " << _num << std::endl;for (auto &e : channels){std::cout << "fd: " << e._fd << "id: " << e._id << std::endl;}}private:int _num;                      // 子进程个数work_t _work;                  // 使用方法std::vector<Channel> channels; // 管理子进程和管道
};

Main.cc

#include"ProcessPool.hpp"int main(int argc,char *argv[])
{if(argc == 1){std::cout<<"ProcessPool -num"<<std::endl;return UsageError;}ProcessPool p(std::stoi(argv[1]));p.InitProcessPool();p.DispatchTask();p.CleanProcessPool();return 0;
}

3、命名管道

  1. 匿名管道只能使具有血缘关系的进程进行通信,而命名管道可以使同一台主机的不同进程进行通信,和匿名管道都是使用文件内核级缓冲区,不对磁盘进行刷新。
  2. 命名管道是一个特殊的文件,用于在不同进程之间进行数据交换和通信。
  3. 读写规则和匿名管道一样。

(1)为什么叫命名管道
具有真是路径+文件名(真实存在的文件)

(2)命名管道特点

  1. 具有实体文件。
  2. 双向通信能力:与只支持单向通信的匿名管道(仅存在于父子进程间)不同,命名管道允许两个进程进行双向通信。然而,这通常需要通过创建两个命名管道来实现,每个管道负责一个方向的数据传输。
  3. 命名管道保证数据按照写入的顺序被读取,即先进先出(FIFO)的原则。这确保了数据的完整性和一致性。
  4. 由于命名管道在文件系统中表现为文件,因此可以使用标准的文件权限机制来控制对管道的访问。这包括读权限、写权限和执行权限等。
  5. 在Linux系统中,匿名管道的大小通常是固定的,并且相对较小。一般来说,匿名管道的缓冲区大小限制在4KB左右,与PIPE_BUF常量(它定义了管道缓冲区的最小原子单位,并影响着进程间通过管道通信的行为)有关。
  6. 生命周期随系统存在。

(3)命名管道的创建
命名管道可以从命令行上创建,命令行方法是使用下面这个命令

mkfifo filename

命名管道也可以从程序里创建,相关函数有mkfifo
头文件

#include <sys/types.h>  
#include <sys/stat.h>  

函数原型:

int mkfifo(const char *filename,mode_t mode);

参数:

filename : 管道文件名。
mode: 管道文件权限。

返回值:

成功时,mkfifo返回0。
失败时,返回-1,并设置errno以指示错误类型。

命名管道删除的函数
头文件:

#include <unistd.h> 

函数原型:

int unlink(const char *pathname);

参数:

pathname:指向要删除的文件的路径名的指针。

返回值

成功时返回 0。
失败时返回 -1,并设置 errno 以指示错误类型。

(4)命名管道文件打开规则

  1. 如果当前打开操作是为读而打开FIFO时,阻塞直到有相应进程为写而打开该FIFO。
  2. 如果当前打开操作是为写而打开FIFO时,阻塞直到有相应进程为读而打开该FIFO。
  3. 总结:需要两个进程以不同操作打开FIFO时,才会真正打开FIFO。

(5)用命名管道实现server&client通信

默认client创建管道文件并进行写操作。
server获取管道文件并进行读操作。

使用代码实现:
公共文件:
两个程序的公共代码

#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//公共文件名
const char* filename = "./fifo";//读取数据大小
const int size = 1024;//公共权限
const mode_t permissions = 0666;//写
const int o_wronly =  O_WRONLY;//读
const int o_rdonly = O_RDONLY;//打开文件
int Open(int flag)
{int fd = open(filename,flag);if(fd < 0){std::cerr<<"打开文件失败"<<std::endl;}return fd;
}//关闭文件
void Close(int fd)
{if(fd > 0)close(fd);
}

Client.hpp

#include"Comm.hpp"//创建命名管道
class Init
{
public://创建管道文件Init(){//设置掩码umask(0);//创建管道文件int m = mkfifo(filename,permissions);if(m < 0){std::cerr<<"创建管道文件失败"<<std::endl;}else{std::cerr<<"创建管道文件成功"<<std::endl;  }}//删除管道文件~Init(){int m = unlink(filename);if(m < 0){std::cerr<<"删除管道文件失败"<<std::endl;}else{std::cerr<<"删除管道文件成功"<<std::endl;}}
};Init it;class Client
{
public://打开文件 -- bool OpenFifo(){_fd = Open(o_wronly);if(_fd < 0){return false;}return true;}//写文件void WriteFifo(const std::string & in){write(_fd,in.c_str(),in.size());}//关闭文件void CloseFifo(){Close(_fd);}private:int _fd;
};

Client.cc

#include "Client.hpp"int main()
{Client client;if(!client.OpenFifo())return 1;while (true){std::cout << "Please Enter# ";std::string in;std::getline(std::cin, in);client.WriteFifo(in);}client.CloseFifo();return 0;
}

Server.hpp

#include"Comm.hpp"class Server
{
public://打开文件 -- bool  OpenFifo(){_fd = Open(o_rdonly);if(_fd < 0)return false;return true;}//读文件int readFifo(std::string *out){char buffer[size];int n = read(_fd,buffer,sizeof(buffer));std::cout<<n<<std::endl;buffer[n] = 0;*out = std::string(buffer);return n;}//关闭文件void CloseFifo(){Close(_fd);}private:int _fd;
};

Server.cc

#include "Server.hpp"int main()
{Server server;if(!server.OpenFifo())return 1;while (true){std::string out;int n = server.readFifo(&out);if(n == 0)break;std::cout << out << std::endl;}server.CloseFifo();return 0;
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【软考】反规范化技术

论反规范化技术 反规范化有这几种技术&#xff0c;增加冗余列&#xff0c;增加派生列&#xff0c;重组表和分割表。其中冗余列是指同一个字段在另外的表中存储一份&#xff0c;减少连表操作。增加派生列是基于另外一个列或者多个列&#xff0c;计算得到一个新的列&#xff0c;可…

SpringBoot day 1104

ok了家人们这周学习SpringBoot的使用&#xff0c;和深入了解&#xff0c;letgo 一.SpringBoot简介 1.1 设计初衷 目前我们开发的过程当中&#xff0c;一般采用一个单体应用的开发采用 SSM 等框架进行开发&#xff0c;并在 开发的过程当中使用了大量的 xml 等配置文件&#x…

Python | Leetcode Python题解之第528题按权重随机选择

题目&#xff1a; 题解&#xff1a; class Solution:def __init__(self, w: List[int]):self.pre list(accumulate(w))self.total sum(w)def pickIndex(self) -> int:x random.randint(1, self.total)return bisect_left(self.pre, x)

C++ | Leetcode C++题解之第528题按权重随机选择

题目&#xff1a; 题解&#xff1a; class Solution { private:mt19937 gen;uniform_int_distribution<int> dis;vector<int> pre;public:Solution(vector<int>& w): gen(random_device{}()), dis(1, accumulate(w.begin(), w.end(), 0)) {partial_sum(…

弹簧质点系统求Hessian

Verification https://www.matrixcalculus.org/ (1-l0/norm2(p-q))*(p-q)

游游的游戏大礼包

游游的游戏大礼包 import java.util.*; public class Main {public static void main(String[] args) {Scanner in new Scanner(System.in);long n in.nextInt();long m in.nextInt();long a in.nextInt();long b in.nextInt();long ret 0;for(long x 0; x < Math.…

详解ARM汇编条件标志

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 条件标志 在 ARM 指令集中&#xff0c;条件标志是控制指令执行的一种机制&#xff0c;它们用于实现条件分支、比较和其他逻辑操作。 我们平时使用 IDA 调试程…

Navicat Premium安装卸载及使用教程教程

Navicat Premium 17 安装卸载及使用教程教程 0. 卸载 没安装过 Navicat 直接跳过本步骤即可。 正常卸载顺序即可&#xff0c;网上很多教程&#xff0c;这里不演示了 如果怕卸载不干净&#xff0c;最后时候可以执行一下压缩包里面的无限试用 Navicat.bat 即可成功删除Navicat…

Backbone网络详解

Backbone 网络&#xff08;主干网络&#xff09;是深度学习模型中的一个重要组成部分&#xff0c;尤其在计算机视觉任务中。Backbone 网络的主要作用是从输入数据中提取有用的特征&#xff0c;为后续的任务&#xff08;如分类、检测、分割等&#xff09;提供强大的特征表示。常…

Jenkins找不到maven构建项目

有的可能没有出现maven这个选项 解决办法&#xff1a;需要安装Maven项目插件 输入​Maven Integration plugin​

【339】基于springboot的新能源充电系统

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;新能源充电系统的设计与实现 摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解…

Godot Zelda教程练习1

提示&#xff1a;B站链接&#xff1a;Godot Zelda教程练习 资产链接&#xff1a;项目资产 Godot版本&#xff1a;4.3 文章目录 一、新建项目1、创建项目2、设置项目标签3、项目基本设置4、导入资产 二、图块集和自动平铺1、创建TileMapLayer2、创建Terrian Set1、Match Siders(…

【案例】旗帜飘动

开发平台&#xff1a;Unity 6.0 开发工具&#xff1a;Shader Graph 参考视频&#xff1a;Unity Shader Graph 旗帜飘动特效   一、效果图 二、Shader Graph 路线图 三、案例分析 核心思路&#xff1a;顶点偏移计算 与 顶点偏移忽略 3.1 纹理偏移 视觉上让旗帜保持动态飘动&a…

Android亮屏Job的功耗优化方案

摘要: Job运行时会带来持锁的现象,目前灭屏放电Job的锁托管已经有doze和绿盟标准监管,但是亮屏时仍旧存在过长的持锁现象,故为了优化功耗和不影响用户体验下,新增亮屏放电下如果满足冻结和已运行过一次Job,则进行job限制,当非冻结时恢复的策略 1.现象: (gms_schedu…

Java面试经典 150 题.P55. 跳跃游戏(009)

本题来自&#xff1a;力扣-面试经典 150 题 面试经典 150 题 - 学习计划 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台https://leetcode.cn/studyplan/top-interview-150/ 题解&#xff1a; class Solution {public boolean canJump(int[] nums) {int…

源码解析篇 | YOLO11:计算机视觉领域的新突破 !对比YOLOv8如何 ?

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。在2024年9月27日盛大举行的YOLO Vision 2024活动上&#xff0c;Ultralytics公司震撼发布了YOLO系列的最新成员—YOLO11。作为Ultralytics YOLO系列实时目标检测器的最新迭代&#xff0c;YOLO11凭借尖端的准确性、速度和效率…

mac m1 docker本地部署canal 监听mysql的binglog日志

mac m1 docker本地部署canal监听mysql的binglog日志(虚拟机同理) 根据黑马视频部署 1.docker 部署mysql 1.docker拉取mysql 镜像 因为m1是arm架构.需要多加一条信息 正常拉取 docker pull mysql:tagm1拉取 5.7的版本. tag需要自己指定版本 docker pull --platform linux/x…

复现LLM:带你从零训练tokenizer

1. 引言 分词器是每个大语言模型必不可少的组件&#xff0c;但每个大语言模型的分词器几乎都不相同。如果要训练自己的分词器&#xff0c;可以使用huggingface的tokenizers框架&#xff0c;tokenizers包含以下主要组件&#xff1a; Tokenizer: 分词器的核心组件&#xff0c;定…

Nginx防盗链配置

1. 什么是盗链? 盗链是指服务提供商自己不提供服务的内容&#xff0c;通过技术手段绕过其它有利益的最终用户界面&#xff08;如广告&#xff09;&#xff0c;直接在自己的网站上向最终用户提供其它服务提供商的服务内容&#xff0c;骗取最终用户的浏览和点击率。受益者不提供…

DAY53|| 42. 接雨水|84.柱状图中最大的矩形

42. 接雨水(超经典款&#xff09; 42. 接雨水 - 力扣&#xff08;LeetCode&#xff09; 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2…