Linux 进程间通信之匿名管道

💓博主CSDN主页:麻辣韭菜💓

⏩专栏分类:Linux知识分享⏪

🚚代码仓库:Linux代码练习🚚

🌹关注我🫵带你学习更多Linux知识
  🔝 


目录

前言 

一. 进程间通信介绍

1.进程间通信目的

2.进程间通信发展

3.进程间通信分类 

二.管道 

用fork来共享管道原理

匿名管道

 进程池



前言 

从进程控制篇章,我们知道了进程是具有独立性,既然各进程具有独立性,它们之间是互不联系的,那它们是怎么通过一种方式取得联系?为什么要有进程间通信?进程间通信本质是什么?

一. 进程间通信介绍

1.进程间通信目的

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

2.进程间通信发展

  • 管道
  • System V进程间通信
  • POSIX进程间通信

3.进程间通信分类 

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

二.管道 

管道是Linux原生能提供的,管道有两种,匿名和命名。 

进程间通信的前提,是需要让不同的进程看到同一块“内存”(特定的组织结构)

所以你所谓的进程看到同一块“内存” 其实是不隶属于任何一个进程,应该更强调共享。

那如何让两个进程看到同一块“内存”?

fork来共享管道原理

         

 

 在实现之前我们需要了解一个接口函数 pipe

创建管道需要使用pipe函数。pipe函数会返回两个文件描述符,分别代表着管道的两端。这两个文件描述符可以用于在父进程和子进程之间传输数据。

pipefd[0]:读下标

pipefd[1]:   写下标 

#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <string>
#include <unistd.h>
#define N 2void Write(int fd)
{std::string str = "hello, I am child process";pid_t pid = getpid();int number = 0;char buf[1024];while (1){sleep(1);buf[0] = '\0';snprintf(buf, sizeof(buf), "%s-%d- %d\n", str.c_str(), number++, pid);write(fd, buf, strlen(buf));// std::cout <<number << std::endl;// if(number > 5)// break;}
}void Read(int fd)
{char buf[1024];int cnt = 0;while (1){memset(buf, 0, sizeof(buf));size_t n = read(fd, buf, sizeof(buf));if (n > 0){std::cout << "father get a message:[" << getpid() << "]#" << buf << std::endl;}else if (n == 0){printf("father read file done\n");break;}else{std::cout << "father read error" << std::endl;break;}cnt++;if (cnt > 5)break;}
}int main()
{int pipefd[N];int n = pipe(pipefd);if (n < 0){std::cout << "pipe error" << std::endl;return 1;}pid_t pid = fork();if (pid < 0){std::cout << "fork error" << std::endl;}else if (pid == 0){// child processclose(pipefd[0]);Write(pipefd[1]);close(pipefd[1]);exit(0);}else{// parent processRead(pipefd[0]);close(pipefd[0]);// wait child processstd::cout << "father close read fd: " << pipefd[0] << std::endl;sleep(5); // wait child process exitpid_t status = 0;pid_t child_pid = waitpid(pid, &status, 0);if (child_pid < 0){std::cout << "waitpid error" << std::endl;return 2;}std::cout << "wait child success: " << child_pid << " exit code: " << ((status >> 8) & 0xFF)<< " exit signal: " << (status & 0x7F) << std::endl;}sleep(3); // wait father process exitreturn 0;
}

运行代码

 管道的特征:

1.具有血缘关系的进程才能进行进程间通信

2.管道只能单向通信 

3.父子进程是会进程协同的,同步与互斥——保护管道内数据。

4.管道是面向字节流的 ps:这个我们后面网络在讲

5.管道是基于文件的,而文件的生命周期是随进程的。

下面我们就来挖一挖细节,基于第3点特征衍生出来的管道内的4种情况

读端正常,管道内容为空,读端就要堵塞

读端正常,管道内容写满,读端就要堵塞

读端正常,写段关闭,读端就会读到0,表明读到了文件的结尾,不会阻塞

写段正常写入,读端关闭,OS就会杀掉正在写入的进程。 

子进程写代码是有sleep1秒的 而父进程是没有sleep1秒 ,从视频我们可以得出父进程在等待子进程写入到管道中,上一次数据被读到,说明管道的内容空了,而子进程休眠1秒钟这期间对应父进程阻塞1秒钟。

第二种情况 我们让写段写快一点,读段慢一点休眠5秒钟 写段不休眠 

读端正常,管道内容写满,读端就要堵塞

第三种情况 我们写代码的number等于5时直接break;

读端正常,写段关闭,读端就会读到0,表明读到了文件的结尾,不会阻塞

第四情况 我们让读端变量cnt == 5时,读端退出。

从第4个结论来说确实OS会杀掉进程,资源有限,都没有人读了,写入后还要写时拷贝,浪费资源。 

匿名管道

 

从上面我们看到3个sleep的父进程是bash 那这样我们可以知道它们是有血缘关系的,

我们在shell打命令行,执行后,然后shell解释我们的命令看到两个|直接创建两个管道,然后再程序替换 然后3个sleep根据重定向原理重定向到管道中。

所以我们以前在命令行执行的管道 | 就是传说之中的匿名管道!!!

 进程池

 根据前面程序控制,和本节的管道知识,我们可以用fork创建多个子进程,父进程写入,子进程读取,根据读取的内容,子进程完成一些相应的事情。这些子进程就好比池子里的水,我们要用的时候直接就可以拿来用。

代码实现

#include "task.hpp"
#include <string>
#include <unistd.h>
#include <cstdlib>#define ProcessNum 5 // 进程个数// 先描述
class channle
{
public:channle(int cmdfd, int slaverid, const std::string &processname): _cmdfd(cmdfd), _slaverid(slaverid), _processname(processname){}public:int _cmdfd;               // 发送任务的文件描述符pid_t _slaverid;          // 子进程的PIDstd::string _processname; // 子进程的名字
};
对于一个进程池来说,进程多了,我们肯定是要管理起来的,所以定义一个对象方面我们管理,对象定义出来了后,我们就要创建管道和子进程。

void InitProcessPool(std::vector<channel> *channels)
{std::vector<int> oldfds;for (int i = 0; i < ProcessNum; i++){// 创建管道int pipefd[2];int n = pipe(pipefd);if (n < 0){perror("pipe");exit(1);}// 创建子进程pid_t id = fork();if (id < 0){perror("fork");exit(2);}else if (id == 0){ // 子进程for (auto fd : oldfds) //关闭之前继承下来的写端{close(fd);}close(pipefd[1]);   // 子进程读,关闭写端。dup2(pipefd[0], 0); // 管道的读端替换成标准输入0close(pipefd[0]);slaver();exit(0);}else{// 父进程close(pipefd[0]); // 父进程写,关闭读端。// 添加channle字段std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name)); // 利用零时对象初始化oldfds.push_back(pipefd[1]);                       // 子进程会继承父进程的写端 方便我们在fork之后关闭写端}}
}

我们再写个Debug测试一下。

void Debug(const std::vector<channel> &channels)
{for (auto &it : channels){std::cout << it._cmdfd << ' ' << it._slaverid << ' ' << it._processname << std::endl;}
}

int main()
{std::vector<channel> channels;InitProcessPool(channels);Debug(channels);return 0;
}

 

5个子进程创建完毕。那么下一步就是通过cmdfd这个文件描述符父进程写入,子进程读取 

我们可以用dup2,我们从键盘读入输入的内容,从管道读取。这样做的好处就是slaver这个函数不用传参

 else if (id == 0){                     // 子进程close(pipefd[1]); // 子进程读,关闭写端。dup2(pipefd[0],0) //管道的读端替换成标准输入0slaver();exit(0);}

slaver这个函数就是获取任务的函数,怎么获取系统调用read获取,我们通过dup2原本是从标准输入读取,现在从管道里读取。 然后执行相应的任务

void slaver()
{int cmdcode = 0;while (true){int n = read(0, &cmdcode, sizeof(int));if (n == sizeof(int)){std::cout << "slaver say@ get a cmdcode: " << getpid() << " : cmdcode:" << cmdcode << std::endl;if (cmdcode > = 0 && cmdcode < task.size())task.[cmdcode]();}if (n == 0)break;}
}
#pragma once
#include <vector>
#include <iostream>typedef void (*task_t)();void task1()
{std::cout << "lol 刷新日志" << std::endl;
}
void task2()
{std::cout << "lol 更新野区,刷新出来野怪" << std::endl;
}
void task3()
{std::cout << "lol 检测软件是否更新,如果需要,就提示用户" << std::endl;
}
void task4()
{std::cout << "lol 用户释放技能,更新用户的血量和蓝量" << std::endl;
}void LoadTask(std::vector<task_t> *tasks)
{tasks->push_back(task1);tasks->push_back(task2);tasks->push_back(task3);tasks->push_back(task4);
}

 现在还有没有任务,我们可以写一个简单的函数把函数的指针放进vector这个容器中然后根据cmdcode下标访问进行函数调用。

 有了任务列表我们就要派发任务,这里博主选择随机派发任务,当然你下去实现的时候可以选择轮循方式派发任务。

void ctrlSlaver(const std::vector<channel> &channels)
{while (true){std::cout << "Please Enter@ ";// 1. 选择任务int cmdcode = rand() % tasks.size();// 2. 选择进程int processpos = rand() % channels.size();std::cout << "father say: "<< " cmdcode: " << cmdcode << " already sendto " << channels[processpos]._slaverid << " process name: "<< channels[processpos]._processname << std::endl;// 3. 发送任务write(channels[processpos]._cmdfd, &cmdcode, sizeof(cmdcode));// sleep(1);}
}

 

 最后我们还再利用wiatpid这个函数回收子进程

void QuitProcess(const std::vector<channel> &channels)
{for (const auto &c : channels){close(c._cmdfd);waitpid(c._slaverid, nullptr, 0);}
}

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

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

相关文章

【linuxC语言】stat函数

文章目录 前言一、stat函数二、示例代码总结 前言 在Linux系统编程中&#xff0c;stat() 函数是一个非常重要的工具&#xff0c;用于获取文件的元数据信息。无论是在系统管理、文件处理还是应用开发中&#xff0c;都可能会用到 stat() 函数。通过调用 stat() 函数&#xff0c;…

AI视频教程下载:用ChatGPT提示词开发AI应用和GPTs

在这个课程中&#xff0c;你将深入ChatGPT的迷人世界&#xff0c;学习如何利用其能力构建创新和有影响力的工具。你将发现如何创建不仅吸引而且保持用户参与度的应用程序&#xff0c;将流量驱动到你的网站&#xff0c;并开辟新的货币化途径。 **课程的主要特点&#xff1a;** …

Hive优化以及相关参数设置

1.表层面设计优化 1.1 表分区 分区表实际上就是对应一个 HDFS 文件系统上的独立的文件夹&#xff0c;该文件夹下是该分区所有的数据文件。Hive 中的分区就是分目录&#xff0c;把一个大的数据集根据业务需要分割成小的数据集。在查询时通过 WHERE 子句中的表达式选择查询所需要…

抖音小店运营实战班,全新升级 从零到进阶精通 分享月销百万小店核心秘密

课程内容&#xff1a; 1 2024抖音电商发展趋势及抖店运营策略(直播2024 0412).mp4 2 1-1抖音小店入驻流程(直播2024 04 12),mp4 3 1-2个体店铺VS企业店铺有什么区别(直播20240412).mp4 4 1-3抖音小店店铺搭建(直播2024 04 12).mp4 5 2-1-如何避免违禁词(附违禁词大全)(直播…

微软如何打造数字零售力航母系列科普07 - Azure PlayFab:你从未想过的世界上最大的开发工具(平台)

Azure PlayFab&#xff1a;你从未想过的世界上最大的开发工具 微软的James Gwertzman告诉GamesIndustry.biz Academy他帮助开发者成功的使命 制作游戏比以往任何时候都更容易上手。现在有无数的游戏引擎可供选择&#xff0c;其中大多数是免费的&#xff0c;PC空间的店面也同样重…

链表经典面试题上

目录 创作不易&#xff0c;如若对您有帮助&#xff0c;还望三连&#xff0c;谢谢&#xff01;&#xff01;&#xff01; 题目一&#xff1a;203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; 题目二&#xff1a;206. 反转链表 - 力扣&#xff08;LeetCode&#xff…

python学习笔记----安装pycharm(1)

一、安装pycharm 1. 下载并安装pycharm https://www.jetbrains.com/pycharm/download2.汉化pycharm 安装插件并重启IDE完成汉化 二、 第一个python程序

【机器学习】机器学习在教育领域的应用场景探索

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

axios 中文文档 翻译

0.18.0的版本更新有一段时间了&#xff0c;使用起来跟原先基本没有什么变化。但是增加了一些功能&#xff0c;例如错误处理的辨别&#xff0c;于07-06-2018重新翻译和校验了该翻译&#xff0c;更正了一些错别字和表达不准的地方&#xff0c;但是难免仍有错误&#xff0c;欢迎指…

GitLab服务器的搭建

GitLab服务器的搭建 为公司搭建一台代码托管服务器 服务器规格&#xff1a;2vCPUs4GiB20G 操作系统&#xff1a;RockyLinux8.8 下载软件 gitlab官网&#xff1a;http://about.gitlab.com 在官网下载比较麻烦&#xff0c;推荐从《清华大学开源软件镜像站》下载 清华大学开…

38-3 Web应用防火墙 - 安装配置WAF

首先需要安装Centos 7 虚拟机:Centos7超详细安装教程_centos7安装教程-CSDN博客 安装配置WAF 在桌面环境中,右键点击打开终端,首先执行以下步骤: 1)安装必要的工具: 输入命令: sudo su yum install -y wget epel-release 2)第二步,安装依赖工具,输入以下命令: y…

深入理解网络原理1

文章目录 前言一、网络初识1.1 IP地址1.2 端口号1.3 协议1.4 五元组1.5 协议分层 二、TCP/IP五层协议三、封装和分用四、客户端vs服务端4.1 交互模式4.2 常见的客户端服务端模型4.3 TCP和UDP差别 前言 随着时代的发展&#xff0c;越来越需要计算机之间互相通信&#xff0c;共享…

大模型咨询培训叶梓老师:数千大模型,1张GPU搞定——UC Berkeley提出全新微调方法S-LoRA

在大语言模型&#xff08;LLM&#xff09;的部署中&#xff0c;通常采用“预训练-微调”范式。为了适应多样化的任务&#xff0c;参数高效的微调方法如低秩适应&#xff08;LoRA&#xff09;被广泛使用。然而&#xff0c;如何高效地服务这些微调变体仍然是一个未探索的问题。S-…

VBA技术资料MF147:从Excel运行PowerPoint演示文稿

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

redis ZRANGE 使用最详细文档

环境&#xff1a; redis_version:7.2.2 本文参考 redis 官方文档1 语法 ZRANGE key start stop [BYSCORE | BYLEX] [REV] [LIMIT offset count] [WITHSCORES]参数含义key是有序集合的键名start stop在不同语境下&#xff0c;可用值不一样BYSCORE | BYLEX按照分数查询 | 相…

【SQL每日一练】统计复旦用户8月练题情况

文章目录 题目一、分析二、题解1.使用case...when..then2.使用if 题目 现在运营想要了解复旦大学的每个用户在8月份练习的总题目数和回答正确的题目数情况&#xff0c;请取出相应明细数据&#xff0c;对于在8月份没有练习过的用户&#xff0c;答题数结果返回0. 示例代码&am…

Excel 批量获取sheet页名称,并创建超链接指向对应sheet页

参考资料 用GET.WORKBOOK函数实现excel批量生成带超链接目录且自动更新 目录 一. 需求二. 名称管理器 → 自定义获取sheet页名称函数三. 配合Index函数&#xff0c;获取所有的sheet页名称四. 添加超链接&#xff0c;指向对应的sheet页 一. 需求 ⏹有如下Excel表&#xff0c;需…

Easy TCP Analysis上线案例库功能,为用户提供一个TCP抓包分析案例分享学习的平台

​案例库&#xff0c;提供给用户相互分享TCP抓包故障排查案例或是经典学习案例的功能&#xff0c;任何用户都可从案例库查看其它用户分享的案例&#xff0c;每个用户也都可以上传自己的案例&#xff0c;经过平台审核去重即可展示在案例库。 对于学习&#xff0c;最典型的三次握…

【Docker学习】docker start深入研究

docker start也是很简单的命令。但因为有了几个选项&#xff0c;又变得复杂&#xff0c;而且... 命令&#xff1a; docker container start 描述&#xff1a; 启动一个或多个已停止的容器。 用法&#xff1a; docker container start [OPTIONS] CONTAINER [CONTAINER...] 别名&…

【网络编程】网络基础

TCP/IP五层模型 物理层&#xff1a;负责光/电信号的传递方式. 比如现在以太网通用的网线&#xff08;双绞线&#xff09;、早期以太网采用的的同轴电缆&#xff08;现在主要用于有线电视&#xff09;、光纤&#xff0c;现在的 WIFI无线网使用电磁波等都属于物理层的概念。物理层…