Linux下管道的实现

1.温故知新

              在上一篇博客我们知道了动态库是怎么样进行链接的,我们知道我们的.o文件,可执行文件都是我们的ELF格式的文件,是ELF文件,里面就有ELF header,程序头表,节,还有节头表,我们链接器编译的时候需要合并节,节头表告诉编译器怎么合并,当加载进内存的时候又会根据程序头表,程序头表告诉节怎么合并成我们的段,我们ELF header里面有我们程序的入口,但是不和我们想象中的先程序我们的main函数,而是先去执行我们的_start,它里面会为我们实现动态库的地址重定向。我们动态库是程序加载的时候才从磁盘加载到内存里的,然后我们规定我们的代码区是不可以更改的,在数据区创建GOT表来实现我们的地址重定向,为了节省内存,我们动态库函数加载也是采用了延时加载的手段,就是先把函数在虚拟地址开辟好,当你调用的时候发现虚拟地址没有映射物理地址,发生中断,陷入内核,内核帮助我们从磁盘加载要用的库函数建立物理和虚拟的映射关系,实现我们的动态库函数调用,至于静态库,链接的时候就已经完成地址的重定向,直接把我们的库搞到可执行里,不需要再搞这个复杂的一套。

        下面,我们介绍一下我们的进程间通信,我们知道进程是具有独立性的,进程之间的任务执行不会相互干扰,但是我们有的时候需要我们进程间通信,为了实现进行间通信我们创建出了管道的概念。下面来详细说明一下。

2.进程间通信

           首先我们来说明:进程间通信本质就是让不同的进程看到同一份资源,并且拿到这份资源,但是由于我们的进程独立性,我们要做到这个并不是很容易。

        

我们进程间通信有很多目的,比如共享资源,比如控制进程等等。

管道是什么呢?管道就是我们进行进程间通信的工具。

管道的定义是:管道是一个基于文件系统的一个内存级的实现进程间单向通信的文件

管道的底层原理是:我们知道父进程创建子进程,子进程会把父进程的PCB和struct file自己拷贝一份,所以子进程的也会指向父进程指向的文件,因为这个原因我们父子进程打印都往一个屏幕上打印,因为我们的屏幕文件被共享了,而我们现在父子进程就可以看见同一个文件的文件缓冲区了,文件缓冲区就是一个管道,但是有一个问题是我们上面打开的文件大部分是普通文件,是普通文件就要把文件缓冲区的内容往磁盘里写,但是我们管道创建本质是为了让我们父进程把它的资源交给子进程,不需要往磁盘做IO刷新,而且我们文件的读写位置只有一个,父进程往管道里100个字节,但是子进程读从100开始读,它是读不到数据的,读写共享是不方便我们读取写入的,不方便通信。所以我们的解决方法是如果是管道,我们把我们的管道文件也拷贝一份给子进程,并且这个文件不往磁盘做IO,这样我们就解决了我们的读写位置重叠,和我们的往磁盘写入的问题。

这个图反映了我们的读写位置是一样的问题

管道的原理就是把我们的file原来不需要拷贝,然后我们多拷贝一份,就解决了我们的读写位置一样的问题,它们的pos就不一样了。

但是我们的文件缓冲区,文件inode和文件的操作表都是一样的。

至于怎么做到pos不共享但是缓冲区共享,还是因为我们复制了一份我们的file

这种文件是不需要打开我们的磁盘文件的,IO磁盘直接被干掉了。

管道的实现是让我们父进程以读写方式打开一个管道,然后子进程会继承,然后父子进程根据需要关闭一个读端和写端,因为我们的管道就是进行单向通信的,天然气管道,暖气管道都是单向的。

所以我们管道也是单向通信即可。

说了这么多,你给我说管道的原理就是让我们不同的进程看到同一个资源,这个管道不和磁盘进行IO,子进程继承的时候会拷贝一份file实现我们的pos的读写分离。那么我们怎么在我们的语言中使用管道进行进程间通信?

下面我们进行实现一下,首先我们的调用是pipe,参数是piprfd,这个是我们的输出型参数。

/ 父进程先创建管道,让子进程去复制int pipefd[2] = {0};int n = pipe(pipefd);

创建好读写管道之后让我们的父子进程关闭对应的管道就可以进行进程间通信了。

然后我们再来介绍一下管道的特性:匿名管道用于具有血缘关系的进程之间。它是单向通信的,这个好理解,我们想一下生活中的管道基本都是单向通信的,还有是管道的生命周期随进程,这个也好理解,管道本质也是一个文件,只不过它是为了通信而创立的,进程没了,也就不需要通信了,它自然就没了,操作系统不会让废弃的管道占用资源,管道也自带同步机制,比如我们的管道里如果没有东西了,read会阻塞,如果我们管道被写满了数据,就不会再进行写入了,如果我们的写端关闭,我们的read会返回0,表示读到文件结尾了,如果读端关闭,写端正常,我们的操作系统会杀掉进程,因为我们的读端不读数据,你再往管道里面写数据就没有意义了。

我们再来提一个原子性的概念,我们学过化学,知道最小的就是原子构成的,所以原子在我们的计算机中说人话就是这个操作不可以被打断,很多人还是不怎么理解,我们打个比方,我们的a++,这操作其实要分很多步骤去完成,我先得吧我们的a从内存搞到CPU,然后CPU对a再进行加法操作,然后再把我们的结果写回到我们的内存,才完成了一次++,所以它的++动作就不是原子性的了,它是可以被打断的,比如刚把我们的a加载到内存准备++,这个时候我们的线程被切走了,不就没完成动作了吗?然而我们的原子性就是一下就完成,不可以被打断的,就是原子性操作。

而我们的管道当写入字节少于一定大小的话,写入就是原子性的。

通过我们管道的这些特性比如我们的阻塞特性,管道没数据,读端会阻塞,管道写满了就不会再写入了,我们可以让父进程来控制它的子进程。

我们父进程可以用什么时候写入数据来控制子进程来执行自己的任务,我父进程不往里面写,你就读不到数据,你读不到数据,你就会阻塞在那里。

还有就是我们父进程和子进程规定,写入4字节整数,整数不同,表示的任务不同。

我们可以采用轮询或者随机数的形式给不同的子进程进行写入,避免有的进程很忙,有的进程很闲。不准偷懒!!!


#include <sys/types.h>
#include <unistd.h>
#include <unistd.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
#include <cstdio>
#include <string>
#include <vector>
#include"Task.hpp"
#include <string>
#include <vector>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctime>
#include "Task.hpp"
// typedef std::function<void (int fd)> func_t;
using callback_t = std::function<void(int fd)>;//返回值,参数
class channel
{
public:channel(int wfd, string name, pid_t id): _wfd(wfd), _name(name), _id(id){}~channel(){}int Fd() { return _wfd; }std::string Name() { return _name; }pid_t Target() { return _id; }void Close() { close(_wfd); }void Wait(){pid_t rid = waitpid(_id, nullptr, 0);}
private:int _wfd;     // 父进程写入的文件描述符pid_t _id;    // 子进程的idstring _name; // 子进程的名字
};class processpool
{
public:processpool(int num) : _num(num){}~processpool(){}bool initpocesspool(callback_t cb){for (int i = 0; i < _num; i++){// 父进程先创建管道,让子进程去复制int pipefd[2] = {0};int n = pipe(pipefd);pid_t id = fork();if (id < 0)return false;if (id == 0) // 这个函数逻辑只有子进程进来,父进程不进来{// 子进程// 要读取,关闭写入close(pipefd[1]);// 子进程要干什么事情啊cb(pipefd[0]);exit(0);}else{// 父进程完成对子进程的描述+组织// 父进程// 要写入,关闭读取close(pipefd[0]);string name = "channel-" + to_string(i);_channels.emplace_back(pipefd[1], name, id);}}return true;}// 轮询控制子进程void processcontrol(){int count=5;int index = 0;while (count--){// 1. 选择一个通道(进程)int who = index;index++;index %= _channels.size();// 2. 选择一个任务,随机int x = index % tasks.size(); // [0, 3]// 3. 任务推送给子进程write(_channels[who].Fd(), &x, sizeof(x));//往哪个文件里写,写的内容的地址,写的内容大小//当任务被写入的时候,子进程要去读sleep(1);}}void WaitSubProcesses(){for(auto e:_channels){e.Close();}for(auto e:_channels){e.Wait();}}
private:int _num;                  // 有多少个子进程vector<channel> _channels; // 将子进程塞进vector,用channel描述,_chanells组织,先描述,再组织
};
#pragma once#include <iostream>
#include <string>
#include <vector>
#include <functional>
// 4种任务
// task_t[4];using task_t = std::function<void()>;void Download()
{std::cout << "我是一个downlowd任务" << std::endl;
}void MySql()
{std::cout << "我是一个 MySQL 任务" << std::endl;
}void Sync()
{std::cout << "我是一个数据刷新同步的任务" << std::endl;
}void Log()
{std::cout << "我是一个日志保存任务" << std::endl;
}std::vector<task_t> tasks;class Init
{
public:Init()//构造函数的初始化666{tasks.push_back(Download);tasks.push_back(MySql);tasks.push_back(Sync);tasks.push_back(Log);}
};Init ginit;//当实例化出对象的时候这个4个任务的插入将同步完成!!!利用了实例化的特性
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
#include <cstdio>
#include <string>
#include <vector>
#include "processpool.hpp"
int main()
{// 1.chuangjian processpoolprocesspool pp(5);// 2.进行初始化pp.initpocesspool([](int fd){while(true){//这个逻辑只有子进程能进来父进程往下一步走了int code = 0;//std::cout << "子进程阻塞: " << getpid() << std::endl;ssize_t n = read(fd, &code, sizeof(code));  // 阻塞读取// 确保读取到完整数据std::cout << "子进程被唤醒: " << getpid() << std::endl;tasks[code]();
// } else if (n == 0) {
//     break;  // 父进程关闭管道,子进程退出
// } else {
//     perror("read");
//     break;
// }if(n == 0){//父进程std::cout << "子进程应该退出了: " << getpid() << std::endl;break;}else{std::cerr << "read fd: " << fd << ", error" << std::endl;break;}}
});//3.进行进程池的控制,控制权在父进程手里pp.processcontrol();// 4. 结束线程池pp.WaitSubProcesses();std::cout << "父进程控制子进程完成,父进程结束" << std::endl;// //创建指定的子进程数// for(int i=0;i<count;i++)// {// //创建管道// int pipefd[2]={0};// int n=pipe(pipefd);// //2.创建子进程// pid_t id=fork();// if(id==0)// {//     //子进程//     close(pipefd[1]);//子进程关闭写端//     exit(0);// }// else// {//     close(pipefd[0]);//父进程关闭读端// }// }return 0;
}

还有就是我们是否注意过我们的子进程什么时候会退出,是我们的父进程的写端关闭,根据我们的管道特性,写端关闭我们的读端读到0就退出了,但是这个时候我们会有个bug,你看我们父进程创立了一个子进程,它搞了一个读管道,当我们第一次创建进程,它是对的,父进程关闭了它的读端,子进程关闭了它的写端,但是第二次就不太对了,你看我创建子进程子进程会拷贝我的文件描述符表一份,它也会把我第一次创建的指向第一次创建子进程写端的文件也会拷贝过去,这就导致我们关闭写端的时候没有关闭完全,子进程无法读到0,它也无法退出,进程卡bug了,所以我们需要在创建我们子进程的时候顺便把我们上次的写端关掉就好了。

怎么拿到父进程上次的写端,从我们的channel里拿就好了,我们每次创建子进程的时候都遍历一遍我们的_channels就好了,里面就有我们的父进程的写端,让子进程关闭了就好了。

 for(auto &e:_channels)close(e.Fd());

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

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

相关文章

光猫、路由器和交换机

光猫&#xff1a;全称为光调制解调器&#xff0c;负责光信号与电信号的转换。在光纤入户的网络环境中&#xff0c;运营商通过光纤传输光信号&#xff0c;光猫将其转换为电脑、路由器等设备能识别的电信号&#xff0c;反之亦然。它是用户端与运营商网络之间的桥梁&#xff0c;保…

从零开始理解编译原理:设计一个简单的编程语言

编译原理是计算机科学的核心领域之一&#xff0c;它研究如何将高级编程语言转换为目标机器能够执行的代码。对于许多开发者来说&#xff0c;编译原理可能是一个神秘而复杂的领域&#xff0c;但实际上&#xff0c;通过系统的学习和实践&#xff0c;我们可以逐步掌握其核心概念和…

年轻新标杆!东方心绣脸韧带年轻技术升级发布

年轻新标杆&#xff01;东方心绣脸韧带年轻技术升级发布近日&#xff0c;“东方心绣脸韧带年轻品项升级发布会”圆满落幕。本次发布会聚焦现代女性面临的衰老困扰&#xff0c;正式推出技术升级成果——“韧带年轻”品项&#xff0c;旨在通过更科学的方案&#xff0c;助力求美者…

qt文件操作与qss基础

文章目录qt文件操作文件概述文件读写文件属性界面优化qss基础选择器的用法结语很高兴和大家见面&#xff0c;给生活加点impetus&#xff01;&#xff01;开启今天的编程之路&#xff01;&#xff01; 作者&#xff1a;٩( ‘ω’ )و260 我的专栏&#xff1a;qt&#xff0c;Li…

spring.config.import 不存在

确认spring.config.import的语法是否正确根据Spring Cloud的官方文档&#xff0c;该属性的值应该指向配置信息&#xff0c;例如对于Nacos配置中心&#xff0c;其格式通常为&#xff1a;spring:config:import: nacos://<nacos-server-addr>/<data-id>?group<gro…

kettle插件-kettle MinIO插件,轻松解决文件上传到MinIO服务器

场景&#xff1a;周二下班刚下地铁的时候有一位大佬&#xff0c;咨询kettle是否可以适配MinIO&#xff0c;功能要实现将图片或者base64通过kettle直接上传到MinIO服务器。接到需求&#xff0c;沟通需求&#xff0c;开干。经过3天左右研发和调试MinIO插件已经成功交付&#xff0…

套接字编程UDP

1.创建套接字int socket(int domain, int type, int protocol);第一个参数&#xff0c;底层用的ip报文统一使用的网络协议都是AFIN第二个参数&#xff0c;面向流的传输协议SOCK_DGRAM&#xff08;数据报套接字类型&#xff09;&#xff1a;支持数据报&#xff08;无连接、不可靠…

计算机网络:如何判断B或者C类IP地址是否划分了子网

要判断B类或C类IP地址是否划分了子网,核心在于通过子网掩码分析其网络位长度是否超过该类地址的默认网络位长度。以下是具体的判断方法和细节说明: 一、基础概念:IP地址类别与默认网络位 IP地址分为A、B、C三类(常用),每类地址的默认网络位长度(即未划分子网时,用于标…

智慧农业温室大棚物联网远程监控与智能监测系统

一、痛点破局&#xff1a;从“靠天吃饭”到“知天而作”传统温室大棚管理依赖人工巡检与经验判断&#xff0c;存在三大核心痛点&#xff1a;数据孤岛&#xff1a;温湿度、光照、CO₂浓度等关键参数分散于不同设备&#xff0c;难以实时整合分析&#xff1b;响应滞后&#xff1a;…

PID学习笔记1

在学习江协科技PID课程时&#xff0c;做一些笔记&#xff0c;对应视频1-4&#xff0c;对应代码&#xff1a;02&#xff0c;03&#xff0c;04&#xff0c;0502-位置式PID定速控制main.c:#include "stm32f10x.h" // Device header #include "Del…

C++入门学习3

10.类和对象 C语言结构体中只能定义变量&#xff0c;在C中&#xff0c;结构体内不仅可以定义变量&#xff0c;也可以定义函数。 C中定义类&#xff08;结构体&#xff09;的语法&#xff1a; class className {// 类体&#xff1a;由成员函数和成员变量组成}; // 一定要注意…

奇偶校验码原理与FPGA实现

奇偶校验原理与FPGA实现写在前面一、基础原理2.1 奇校验2.2 偶校验2.3 缺点二、举个例子3.1 奇校验例子3.2 偶校验例子3.3 检测出错例子三、FPGA实现写在后面写在前面 奇偶校验码是一种简单的检错码&#xff0c;主要用于数据传输或存储过程中检测奇数个比特错误或者偶数个比特错…

Python中的Lambda函数详解

Lambda函数&#xff08;匿名函数&#xff09;是Python中一种简洁的函数定义方式&#xff0c;它允许你快速创建小型、一次性的函数对象而无需使用标准的def关键字。1. Lambda函数的基本语法lambda arguments: expressionlambda&#xff1a;定义匿名函数的关键字arguments&#x…

进阶向:Python编写网页爬虫抓取数据

Python网页爬虫入门指南&#xff1a;从零开始抓取数据在当今数据驱动的时代&#xff0c;网络爬虫已成为获取公开信息的重要工具。Python凭借其丰富的库和简洁的语法&#xff0c;成为编写网络爬虫的首选语言。本文将详细介绍如何使用Python编写一个基础的网页爬虫。什么是网页爬…

客服Agent革命:智能客服系统的技术实现与效果评估

客服Agent革命&#xff1a;智能客服系统的技术实现与效果评估 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特性都是我…

C++-红黑树

1、红黑树的概念红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff0c;…

在Python中避免使用`None`表示特殊情况:函数返回值与异常处理的最佳实践 (Effective Python 第20条)

在Python编程中&#xff0c;函数的设计与实现直接影响代码的可读性、可维护性和健壮性。一个常见的问题是如何处理函数的返回值&#xff0c;尤其是在需要表示某种特殊或异常情况时。许多开发者习惯性地使用None来表示这些特殊情况&#xff0c;但这种方法往往会导致意想不到的错…

从反射到方法句柄:深入探索Java动态编程的终极解决方案

&#x1f31f; 你好&#xff0c;我是 励志成为糕手 &#xff01; &#x1f30c; 在代码的宇宙中&#xff0c;我是那个追逐优雅与性能的星际旅人。 ✨ 每一行代码都是我种下的星光&#xff0c;在逻辑的土壤里生长成璀璨的银河&#xff1b; &#x1f6e0;️ 每一个算法都是我绘制…

算法_python_学习记录_01

人心的成见是一座大山。一旦有山挡在面前&#xff0c;则很难到达下一站。所需要做的&#xff0c;是穿过这座山。 偶然间看了一个视频&#xff0c;说的是EMASMA的自动交易策略&#xff0c;这个视频做的很用心&#xff0c;在入场的时间不仅要看EMA的金叉&#xff0c;还需要看其他…

机器翻译中的语言学基础详解(包括包括语法、句法和语义学等)

文章目录一、语法&#xff08;Grammar&#xff09;&#xff1a;语言规则的底层框架1.1 传统语法理论的应用1.2 生成语法&#xff08;Generative Grammar&#xff09;1.3 依存语法&#xff08;Dependency Grammar&#xff09;二、句法&#xff08;Syntax&#xff09;&#xff1a…