【Linux】從零開始認識進程間通信 —— 管道

在這里插入圖片描述
送給大家一句話:
人要成長,必有原因,背后的努力與積累一定數倍于普通人。所以,關鍵還在于自己。 – 楊絳


從零開始認識進程間通信

  • 1 為什么要進程間通信
  • 2 進程如何通信
  • 3 進程通信的常見方式
  • 4 管道
    • 4.1 什么是管道
    • 4.2 管道通信的系統調用
    • 4.3 小試牛刀
  • 5 總結
  • Thanks?(・ω・)ノ謝謝閱讀!!!
  • 下一篇文章見!!!

1 為什么要進程間通信

以前我們學習的過程中,進程是具有獨立性的。但有些時候需要多個進程進行協同,這時候就需要進程間的通信來保證信息的互通。

就比如學校就分有教務處 , 學生處,教研組,班主任等部分。如果學校想要組織一場考試,就通知教務處安排好考場和監考員,告訴教研組老師需要出卷子,等教務處與教研組完成對應工作再告知學生處和班主任,然后通知學生進行考試,班主任和學生處做好考試監督工作。

這里面就少不了溝通交流,傳遞信息。進程工作也是這樣:進程的協同工作需要一個前提提交——通信。通信就是傳遞數據,控制相關信息

2 進程如何通信

首先 , 我們知道進程是具有獨立性的,一個進程的狀態不會影響其他進程的運行。而獨立性的本質是進程 = 內核數據結構 + 代碼和數據 ,內核數據結構PCB是獨立的,代碼和數據是獨立的 , 進程自然就是獨立的。

所以,進程間通信的成本的成本稍微高一些,因為進程本身是獨立的,兩個進程天然是無法進行數據共享的!

可是子進程建立的時候不是會拷貝(繼承)一份父進程的數據嗎,這不是進行通信嗎???
這就要我們明確區分兩個概念:能通信可以一直通信是不一樣的。子進程繼承的父進程數據是只讀的,而且只進行一次。而一直通信是時不時就“打個電話”。

所以進程間通信的前提是:先讓不同的進程看到同一份(操作系統)資源(“一段內存”)。

在這里插入圖片描述

  1. 首先,一定是某一個進程先需要通信,讓OS創建一個共享資源
  2. 那么OS必須通過對應的系統調用來創建共享資源
  3. OS創建的共享資源的不同 , 系統調用接口的不同----就導致進程間通信會有不同的種類

3 進程通信的常見方式

一般通信有以下種類:

  1. 管道
    • 匿名管道pipe
    • 命名管道
  2. System V IPC 標準 (早期的本地通信)
    • System V 消息隊列
    • System V 共享內存
    • System V 信號量
  3. POSIX IPC 標準(現代版本)
    • 消息隊列
    • 共享內存
    • 信號量
    • 互斥量
    • 條件變量
    • 讀寫鎖

今天來講解管道

早期的時候,程序員們面對通信的需求時,不想再單獨設計一個通信模塊,直接復用內核級代碼,就產生了管道!!!管道分為命名管道和匿名管道。

4 管道

4.1 什么是管道

【Linux】 拿下 系統 基礎文件操作!!!
【Linux】開始了解重定向

這兩篇文章了我們講解了文件的底層,知道每一個進程都有對應的文件管理結構體,文件管理結構體中有管理已經打開文件的數組。數組下標為文件描述符,指向文件結構體,而文件結構體又會指向文件真正屬性inode。
在這里插入圖片描述
當我們以不同方式打開文件時,只需要在內存中加載一份數據(通過引用計數來管理),以讀寫方式打開,便會有兩個對應的文件結構體。他們共同使用一份代數據,那自然就使用同一個內核級緩沖區。

那么為了要通信,不用在寫一個新的模塊,直接建立一個子進程來通信多簡單。子進程會以父進程為模版進行寫時拷貝。
在這里插入圖片描述
進行拷貝的只有進程對應的結構體,因為進程具有獨立性,而文件系統我們可沒提過什么對立性,所以文件管理數組進行淺拷貝,同樣指向原先的文件結構體。

這時也就理解為什么父子進程會向同一塊顯示器終端打印數據了。

也理解為什么進程會默認打開012三個標準輸入輸出:因為所有進程都是bash的子進程,而bash打開了這三個文件,所以自然就打開了!!!

子進程要主動close(0 / 1 /2)不影響父進程繼續使用顯示器文件!只有引用計數(類似硬鏈接數)歸零才會清理數據

今天我們進行進程間通信的前提——先讓不同的進程看到同一份(操作系統)資源,不就解決了嗎!!!

文件的內存緩沖區不就是兩個進程共享的一份資源嗎!而所謂的管道文件就是這個文件緩沖區!

但是呢,管道只允許進行單向通信(父->子 或 子->父),因為管道如果允許父子進程都可以寫,就會導致數據紊亂!進行通信的時候,每個進程關閉不需要的文件描述符,然后通過緩沖區來單向通信。一個進程把信息寫入緩沖區,另一個進程從緩沖區讀取數據,不需要刷新到硬盤,直接從內存進行操作!

在這里插入圖片描述

有個問題:父子既然要關閉不需要的fd那為什么曾經還要打開呢?可以不關閉嗎?
如果父進程只打開讀寫的fd,那么子進程也就只能繼承讀寫的fd,這就壞事了,總得有人寫入吧!那為什么不直接以讀寫方式打開一個fd呢?這樣肯定不可以,子進程繼承后也具有讀寫,也壞事了!
所以不關閉是為了讓子進程可以繼承下去,到時候關閉不需要的就可以了!當然也可以不關閉,只要你不亂使用,所以為了排除風險,建議直接關閉

4.2 管道通信的系統調用

了解了管道是什么,我們就來看看關于管道的系統調用是什么吧?
通過手冊我們可以看到:

PIPE(2)                                                             Linux Programmer's Manual                                                            PIPE(2)NAMEpipe, pipe2 - create pipeSYNOPSIS#include <unistd.h>/* On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64; see NOTES */struct fd_pair {long fd[2];};struct fd_pair pipe();/* On all other architectures */int pipe(int pipefd[2]);#define _GNU_SOURCE             /* See feature_test_macros(7) */#include <fcntl.h>              /* Obtain O_* constant definitions */#include <unistd.h>int pipe2(int pipefd[2], int flags);

新的pipe(int pipefd[2]) 是今天的主角(Ubuntu提供了新的選擇pipe2),其實底層就是open

pipefd[2] 這是一個輸出型參數,把以讀方式打開的文件描述符rfd和以寫方式打開的文件描述符wfd記錄下來!

open不同的是,這個系統調用不需要文件路徑和文件名,所以才叫匿名管道!

那么如果我們想要雙向通信呢???
干脆建兩個管道不就行了!

那為什么要進行單向通信呢?
因為這個管道的單向通信簡單,對代碼的復用率很高!

4.3 小試牛刀

接下來我們就來寫一個demo,來試試管道接口。

首先我們來搭建一個框架:

  1. 建立一個管道,得到對應的文件描述符
  2. 創建子進程,關閉對應文件
  3. 我們進行子進程寫入,父進程讀取
  4. 等待子進程退出,避免僵尸進程出現!
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<sys/wait.h>
#include<cerrno>using namespace std;void SubProcessWrite(int wfd)
{
}
void FatherProcessRead(int rfd)
{
}
int main()
{//創建管道int pipefd[2];//[0] -> r | [1] -> wint n = pipe(pipefd);if(n != 0){perror("創建管道錯誤!\n");}cout << "pipefd[0] : " << pipefd[0] << " pipefd[1] :" << pipefd[1] << endl;//創建子進程//關閉對應文件pid_t id = fork();if(id == 0){//子進程 -- writeclose(pipefd[0]);SubProcessWrite(pipefd[1]);//使用完都關閉close(pipefd[1]);exit(0);}//父進程 -- readclose(pipefd[1]);FatherProcessRead(pipefd[0]);//使用完都關閉close(pipefd[0]);pid_t rid = waitpid(id ,nullptr , 0 );if(rid > 0){cout << "wait child process done!" << endl;}return 0;
}

管道本質也是文件,我們讀寫同樣使用write和read。
我們完善一下代碼:

  1. 子進程每隔一秒寫入一次數據
  2. 父進程每隔一秒讀取一次數據
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<string>
#include<sys/wait.h>
#include<cerrno>using namespace std;string GetOtherMessage()
{static int cnt = 0;string messageid = to_string(cnt);cnt++;pid_t self_id = getpid();string stringid = std::to_string(self_id);string message = "messageid : ";message += messageid;message += "my pid :";message += stringid;return message;
}void SubProcessWrite(int wfd)
{string messages = "father,I am your soon process !";while (true){string info = messages + GetOtherMessage();//寫入管道時沒有寫入'\0',沒有必要。在讀取時添加write(wfd , info.c_str() , info.size());sleep(1);}}
#define size 1024void FatherProcessRead(int rfd)
{char inbuffer[size];while (true){//注意傳入的參數 , 讀取 rfd 內的數據到inbuffer中,返回成功讀取的個數。ssize_t n = read(rfd , inbuffer , sizeof(inbuffer) - 1);if( n > 0 ) {inbuffer[n] = '\0';//添加'\0'cout << "father get message: " << inbuffer << endl;}}
}
int main()
{//創建管道int pipefd[2];//[0] -> r | [1] -> wint n = pipe(pipefd);if(n != 0){perror("創建管道錯誤!\n");}cout << "pipefd[0] : " << pipefd[0] << " pipefd[1] :" << pipefd[1] << endl;sleep(1);//創建子進程//關閉對應文件pid_t id = fork();if(id == 0){cout << "子進程關閉不需要的fd , 準備開始發消息" << endl;sleep(1);//子進程 -- writeclose(pipefd[0]);SubProcessWrite(pipefd[1]);//使用完都關閉close(pipefd[1]);exit(0);}cout << "子進程關閉不需要的fd , 準備開始接收消息" << endl;sleep(1);//父進程 -- readclose(pipefd[1]);FatherProcessRead(pipefd[0]);//使用完都關閉close(pipefd[0]);pid_t rid = waitpid(id ,nullptr , 0 );if(rid > 0){cout << "wait child process done!" << endl;}return 0;
}

運行看看:
在這里插入圖片描述
這樣驗證管道通信的可行性。

5 總結

管道的4種情況

  1. 如果管道內部是空的 && write fd沒有關閉:
    讀取條件不具備,讀取進程會被阻塞 – wait 等待條件具備(寫入了數據)
  2. 管道別寫滿 && read fd 不讀且沒有關閉 :
    管道被寫滿,寫進程會被阻塞,寫條件不具備-- wait 等待條件具備(讀取走一部分數據才能繼續寫)
  3. 管道一直在被讀 && 寫端關閉了wfd:
    讀端read的返回值會讀到 0 ,表示到了文件結尾!!!所以可以在讀取的時候進行一下判斷,為0就直接退出讀取!
  4. rfd 直接關閉 , 寫端wfd一直在寫入:
    首先管道只有一對讀寫端,讀端被關閉了,那么管道就不能稱之為管道了。OS系統也不會做這樣的無用功,直接把broken pipe壞的管道 進行殺掉!會發送對應的13號信號SIGPIPE:
    在這里插入圖片描述

我們可以總結出管道的5 種特征:

  1. 匿名管道:只用來進行父子進程之間,因為他們可以看到同一資源
  2. 同步性管道內部自帶進程之間的同步機制!同步是多執行流代碼的時候,具有明顯的順序性。父子進程的讀寫一定要同步進行,不然可能會出現并發讀取的情況,出現錯誤!
  3. 文件的生命周期是隨進程的:當一個文件沒有進程調用的時候,就會釋放掉!
  4. 管道在通信的時候,是面向字節流的:write 的次數和read的次數不是一一匹配的!
    我們讓子進程瘋狂的寫,父進程也一直讀。子進程每 1 s寫一次,寫入時也向標準錯誤里進行打印(為了好觀察)。父進程每5s讀一次,并打印到顯示器:
    在這里插入圖片描述
    可以看到,右側的子進程,左邊是父進程。子進程寫入好幾次的數據,會被父進程一次讀取一大批!!!
  5. 管道的通信模式,是一種特殊的半雙工模式:與之對應的是全雙工模式,即雙方交流可以同時說話。半雙工是只能一方說話,一方聆聽,不能同時說(對講機模式)。

這里提一個概念,在管道讀寫是"原子"的,每個"原子"是 4096 bytes。只有小于這個大小,就不會在讀寫時被其他人影響。如果大于一個原子的大小,就不能保證安全了。

下一篇文章我們進行管道的實戰——進程池項目!

Thanks?(・ω・)ノ謝謝閱讀!!!

下一篇文章見!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/14799.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/14799.shtml
英文地址,請注明出處:http://en.pswp.cn/web/14799.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

交叉編譯程序,提示 incomplete type “struct sigaction“ is not allowed

問題描述 incomplete type "struct sigaction" is not allowed解決辦法 在代碼的最頂端添加如下代碼即可 #define _XOPEN_SOURCE此定義不是簡單的宏定義&#xff0c;是使程序符合系統環境的不可缺少的部分 _XOPEN_SOURCE為了實現XPG&#xff1a;The X/Open Porta…

零一萬物Yi-1.5開源,34B/9B/6B多尺寸,34B超Qwen1.5-72B

前言 近年來&#xff0c;大型語言模型&#xff08;LLM&#xff09;在各個領域展現出驚人的能力&#xff0c;為人們的生活和工作帶來了巨大的改變。然而&#xff0c;大多數開源 LLM 的性能仍然無法與閉源模型相媲美&#xff0c;這限制了 LLM 在科研和商業領域的進一步應用。為了…

element-plus表格的表單校驗如何實現,重點在model和prop

文章目錄 vue&#xff1a;3.x element-plus&#xff1a;2.7.3 重點&#xff1a; 1) tableData放到form對象里 2) form-item的prop要寫成tableData.序號.屬性 <!--table-表單校驗--> <template><el-form ref"forms" :model"form"><e…

leetcode 207.課程表

思路&#xff1a;拓補排序 數據結構中的知識&#xff0c;這道題其實本質上就是判斷在課程表的這個有向圖當中是否有環存在&#xff0c;如果有環&#xff0c;說明不能學完&#xff1b;沒有環說明可以。判斷有無環的做法是拓補排序最好解決。 下面就是拓補排序的做法了&#xf…

安裝mpi4py與dlio_profiler_py的總結

安裝mpi4py mpi4py是一個Python庫&#xff0c;它提供了與MPI&#xff08;Message Passing Interface&#xff09;兼容的接口&#xff0c;使得Python程序能夠利用MPI實現并行計算。mpi4py 的核心是基于MPI標準的C/C實現&#xff0c;它能夠在高性能計算環境下進行高效的并行處理…

軟考之信息系統管理知識點(3)

流水線&#xff1a;是指在程序執行時多條指令重疊進行操作的一種準并行處理實現技術。各種部件同時處理是針對不同指令而言的&#xff0c;它們可同時為多條指令的不同部分進行工作&#xff0c;以提高各部件的利用率和指令的平均執行速度。 編譯得過程 關系數據庫是表的集合 …

【全網最全】2024電工杯數學建模A題前兩問完整解答+21頁初步參考論文+py代碼+保獎思路等(后續會更新成品論文)

您的點贊收藏是我繼續更新的最大動力&#xff01; 一定要點擊如下的卡片鏈接&#xff0c;那是獲取資料的入口&#xff01; 【全網最全】2024電工杯數學建模A題前兩問完整解答21頁初步參考論文py代碼保獎思路等&#xff08;后續會更新成品論文&#xff09;「首先來看看目前已有…

力扣:92. 反轉鏈表 II(Java)

目錄 題目描述&#xff1a;示例 1&#xff1a;示例 2&#xff1a;代碼實現&#xff1a; 題目描述&#xff1a; 給你單鏈表的頭指針 head 和兩個整數 left 和 right &#xff0c;其中 left < right 。請你反轉從位置 left 到位置 right 的鏈表節點&#xff0c;返回 反轉后的…

Springboot 開發 -- 創建Spring Boot Starter

一、簡介 Spring Boot Starter是Spring Boot生態中非常重要的一部分&#xff0c;它允許開發者通過簡單的依賴管理來快速集成各種功能和庫。在開發過程中&#xff0c;我們經常會遇到一些通用的功能或配置&#xff0c;如果每次都需要手動添加這些配置和依賴&#xff0c;那么將會…

網絡采集受限?如何解決指紋識別、IP封禁、驗證碼、賬號多登等問題

網頁采集是什么 網頁采集&#xff0c;也常被稱作網絡采集、網絡數據抓取&#xff0c;是一種通過自動化工具從網站上獲取信息的技術。這些技術通過訪問網頁&#xff0c;解析頁面上的內容&#xff0c;并提取出有價值的數據&#xff0c;如文本、圖片、鏈接等。 網頁采集通常用于…

Leedcode34. 在排序數組中查找元素的第一個和最后一個位置_Java解法

Problem: 34. 在排序數組中查找元素的第一個和最后一個位置 題目描述思路解題方法復雜度Code 題目描述 34. 在排序數組中查找元素的第一個和最后一個位置 力扣鏈接 給定一個按照升序排列的整數數組 nums&#xff0c;和一個目標值 target。找出給定目標值在數組中的開始位置…

Python 調整PDF文件的頁面大小

在處理PDF文件時&#xff0c;我們可能會遇到這樣的情況&#xff1a;原始PDF文檔不符合我們的閱讀習慣&#xff0c;或者需要適配不同顯示設備等。這時&#xff0c;我們就需要及時調整PDF文檔中的頁面尺寸&#xff0c;以滿足不同應用場景的需求。 利用Python語言的高效性和靈活性…

Linux--網絡通信(一)概述

網絡通信概述 網絡通信本質上是一種進程間通信&#xff0c;是位于網絡中不同主機上的進程之間的通信&#xff0c;屬于 IPC 的一種&#xff0c; 通常稱為 socket IPC。所以網絡通信是為了解決在網絡環境中&#xff0c;不同主機上的應用程序之間的通信問題。 大概可以分為三個層…

優化關聯查詢

三個表的創建語句 CREATE TABLE test.afx_output_source_item (cheadguid INT NOT NULL,goodsid INT NULL,goodsno VARCHAR(45) NULL,goodsname VARCHAR(45) NULL,model VARCHAR(45) NULL,goodstaxno VARCHAR(45) NULL,PRIMARY KEY (cheadguid));CREATE TABLE test.afx_output…

23種設計模式之一————外觀模式詳細介紹與講解

外觀模式詳細講解 一、概念二、 外觀模式結構核心思想及解釋模式的UML類圖模式角色應用場景模式優點模式缺點 三、實例演示圖示代碼展示運行結果 一、概念 外觀模式&#xff08;Facade Pattern&#xff09;是一種結構型設計模式&#xff0c;它提供了一個統一的接口&#xff0c…

【問題解決】Android Studio Jellyfish新建Kotlin項目后Gradle Sync及Maven下載很慢

創建新項目之后&#xff0c;Gradle Sync和Build都很慢&#xff0c;因為下載Gradle和Maven等工具。 代碼默認配置 settings.gradle.kts pluginManagement {repositories {google {content {includeGroupByRegex("com\\.android.*")includeGroupByRegex("com\\.g…

ASSM是Automatic Segment Space Management(自動段空間管理)解析

ASSM是Automatic Segment Space Management&#xff08;自動段空間管理&#xff09;的縮寫&#xff0c;是Oracle數據庫引入的一項重要特性&#xff0c;首次出現在Oracle 9i中。ASSM旨在簡化空間管理和提高數據庫性能&#xff0c;特別是對于表和索引段的空間分配和回收過程。 在…

Android Activity 設計詳解

文章目錄 Android Activity 設計說明1. Activity 的生命周期2. Activity 的啟動模式3. Activity 的通信4. Activity 的布局和視圖管理5. Activity 的配置變化處理6. Activity 的保存和恢復狀態7. Activity 的任務和返回棧 總結 Android Activity 設計說明 在 Android 中&#…

Ansible01-Ansible的概述、實驗環境初始化、Inventory

目錄 寫在前面1. Ansible是什么1.1 簡介與來歷1.2 Ansible的特點1.3Ansible的架構與工作流程1.3.1 ansible 任務執行模式1.3.2 ansible 執行流程1.4 Ansible的模塊 2. Ansible實驗初始化2.1 實驗環境2.2Ansible的安裝2.2.1 Ansible的程序結構 2.3 修改Ansible配置文件2.3.1 配置…

【408精華知識】頁、頁面、頁框、頁幀、內存塊、物理塊、物理頁面還傻傻分不清?

在做題過程中&#xff0c;我們經常能看到頁、頁框、塊等概念&#xff0c;初接觸時&#xff0c;常感覺傻傻分不清&#xff0c;這篇文章將簡潔地介紹它們之間的聯系與區別。 這些概念之間的根本區別&#xff0c;在于是物理上的概念還是邏輯上的概念&#xff0c;也即是虛地址還是實…