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
英文地址,請注明出處:http://en.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…