【Linux系統】進程間通信:命名管道

1.?匿名管道的限制

匿名管道存在以下核心限制:

  • 僅限親緣關系進程:只能用于父子進程等有血緣關系的進程間通信(如通過?fork()?創建的子進程)。
  • 單向通信:數據只能單向流動(一端寫,另一端讀),雙向通信需創建兩個管道。
  • 臨時性:存在于內存中,進程結束后自動銷毀。
  • 緩沖區有限:大小固定(通常為一個內存頁,如4KB),易寫滿阻塞。

引入命名管道的原因
為解決匿名管道的局限性,命名管道允許任意進程(無論是否有親緣關系)通過文件系統路徑訪問,實現跨進程通信。


2.?什么是命名管道

命名管道(Named Pipe/FIFO)是一種特殊的文件類型,特點包括:

  • 文件系統可見:通過路徑名(如?/tmp/myfifo)標識,任何進程可訪問。
  • 遵循FIFO原則:數據按寫入順序讀取,嚴格保持先進先出。
  • 突破親緣限制:不相關進程可通過路徑名打開同一管道通信。
  • 雙向通信支持:部分場景下支持讀寫雙向操作(需顯式設計)。

示例:命名管道在文件系統中顯示為特殊文件(權限位帶?p,如?prw-r--r--)。


3.?如何創建命名管道

方法一:命令行創建

mkfifo <路徑名>   # 例如:mkfifo /tmp/my_pipe

生成一個具名管道文件,權限默認受?umask?影響。

示例:

方法二:程序內創建

使用?mkfifo()?函數:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);  // 成功返回0,失敗返回-1
  • 參數
    • pathname:管道路徑(如?/tmp/my_pipe)。
    • mode:權限標志(如?0666?表示所有用戶可讀寫)。
  • 后續操作
    • 需用?open()?打開管道(讀模式?O_RDONLY?或寫模式?O_WRONLY)。
    • 默認阻塞行為:讀端打開時寫端阻塞,反之亦然;可通過?O_NONBLOCK?設為非阻塞。

刪除管道

  • 命令行:rm <路徑名>?或?unlink <路徑名>
  • 程序內:unlink(pathname)

4.?匿名管道和命名管道的區別

特性匿名管道命名管道證據來源
創建方式pipe(fd)?一步創建并打開mkfifo()?創建 +?open()?打開
進程關系要求必須具有親緣關系(如父子進程)任意進程均可訪問
持久性隨進程結束銷毀文件系統持久,需手動刪除
通信方向僅單向可支持雙向通信
性能略快(無文件系統操作)稍慢(涉及磁盤索引節點)
使用場景短期親緣進程通信長期/跨進程通信(如C/S架構)

關鍵補充

  • 語義一致性:打開后兩者操作方式相同(如?read()/write())。
  • 網絡支持:命名管道可跨機器通信,匿名管道僅限本地。
  • 阻塞行為:兩者均受緩沖區影響,但命名管道可通過?O_NONBLOCK?靈活控制阻塞。

5. 命名管道的打開規則

一、為讀而打開 FIFO(O_RDONLY

  1. O_NONBLOCK?未設置(默認阻塞)
    • 行為:調用?open()?會阻塞當前進程,直到有另一個進程為寫而打開同一 FIFO
    • 原理:內核需確保存在數據生產者,否則讀操作無意義。

"open以只讀方式打開FIFO時,要阻塞到某個進程為寫而打開此FIFO"?。
"若沒有指定O_NONBLOCK,只讀 open 要阻塞到某個其他進程為寫而打開此 FIFO"?。

  1. O_NONBLOCK?設置(非阻塞)
    • 行為open()?立即成功返回(返回文件描述符),無論是否有寫端打開
    • 后續注意:此時若管道無數據,read()?可能返回 0(EOF)或?EAGAIN?錯誤(見下文讀寫規則)。

"先以只讀方式打開,如果沒有進程已經為寫而打開一個FIFO,只讀 open() 成功,并且 open() 不阻塞"?。
"若指定了O_NONBLOCK,則只讀 open 立即返回"?。


二、為寫而打開 FIFO(O_WRONLY

  1. O_NONBLOCK?未設置(默認阻塞)
    • 行為:調用?open()?會阻塞當前進程,直到有另一個進程為讀而打開同一 FIFO
    • 原理:內核需確保存在數據消費者,否則寫操作可能無限等待。

"open以只寫方式打開FIFO時,要阻塞到某個進程為讀而打開此FIFO"?。
"只寫 open 要阻塞到某個其他進程為讀而打開它"?。

  1. O_NONBLOCK?設置(非阻塞)
    • 行為:若無讀端已打開open()?立即失敗,返回?-1?并設置錯誤碼?ENXIO(表示設備不存在)。
    • 行為:若已有讀端打開,則?open()?成功。

"先以只寫方式打開,如果沒有進程已經為讀而打開一個FIFO,只寫 open() 將出錯返回 -1"?。
"若指定了O_NONBLOCK,則只寫 open 將出錯返回 -1 如果沒有進程已經為讀而打開該 FIFO,其errno置ENXIO"?。


三、關鍵補充與深度解析

  1. O_RDWR(讀寫模式)的特殊性

    • 行為:以?O_RDWR?模式打開時?永不阻塞,因進程自身已同時打開讀寫端?。
    • 風險:可能導致自我死鎖(如寫滿后讀阻塞),實踐中極少使用。
  2. 讀寫操作的阻塞行為(與?open?獨立)

    操作O_NONBLOCK?未設置O_NONBLOCK?設置
    read()?空管道阻塞直到有數據寫入立即返回?EAGAIN(或空數據)
    write()?滿管道阻塞直到有空間部分寫入或返回?EAGAIN
  3. 管道斷裂與信號處理

    • 寫端關閉:讀端?read()?返回 0(EOF),不阻塞?。
    • 讀端關閉:寫端?write()?觸發?SIGPIPE?信號(默認終止進程),錯誤碼?EPIPE?。
  4. 原子性與?PIPE_BUF

    • 規則:寫入 ≤?PIPE_BUF?字節的數據保證原子性(不與其他進程交織)。
    • 典型值:Linux 中?PIPE_BUF?為 4096 字節(一頁大小)。

?四、內核實現原理(選讀)

  1. 阻塞的本質
    • 進程休眠在 FIFO inode 的等待隊列中,由另一端打開或數據變動時喚醒?。
    • 示例:
      // Linux 內核片段(讀打開阻塞邏輯)
      if (PIPE_READERS(*inode)++ == 0) wait_for_partner(inode, &PIPE_WCOUNTER(*inode)); // 等待寫端
      
  1. 非阻塞的沖突處理
    • 寫打開時若無讀端,內核直接返回?ENXIO?而非加入等待隊列?:

"若命名管道讀端尚未打開,而 O_NONBLOCK=1,寫端打開失敗并釋放資源"?。


總結與建議

場景打開模式O_NONBLOCK結果
讀打開,無寫端存在O_RDONLY未設置阻塞
讀打開,無寫端存在O_RDONLY設置立即成功
寫打開,無讀端存在O_WRONLY未設置阻塞
寫打開,無讀端存在O_WRONLY設置立即失敗(ENXIO)
讀寫打開O_RDWR任意立即成功(不依賴外部進程)

工程建議

  1. 生產-消費模型:推薦讀端阻塞打開(確保寫端就緒),寫端非阻塞打開(快速失敗+重試邏輯)。
  2. 超時控制:若需阻塞但避免無限等待,結合?select()/poll()?設置超時。
  3. 錯誤處理:始終檢查?open()?返回值和?errno,尤其非阻塞模式。

6. 代碼示例

下面為了更好理解命名管道,我們直接來一段代碼,使用命名管道讓兩個無血緣關系的進程進行通信——一個進程寫一個進程讀。

這里client.cc和server.cc代表兩個沒有血緣關系的進程,在前面學習進程時我們知道,.cc文件跑起來就是一個進程,所以這里不多贅述。而我們命名管道的創建,以及打開管道文件進行操作的代碼則封裝在comm.hpp中。Makefile則是我們配置的自動化工具。

下面我們就來在comm.hpp中將代碼封裝起來

首先需要將命名管道創建,最后結束通信后還需要將管道回收,因為命名管道不會隨進程的生命周期,所以需要我們手動回收

代碼如下:

class NamedFifo
{
public:NamedFifo(const std::string &path, const std::string &name): _path(path), _name(name){_filename = _path + "/" + _name;// 創建命名管道int n = mkfifo(_filename.c_str(), 0666);if(n < 0){std::cerr << "mkfifo failed" << std::endl;}else{std::cout << "mkfifo success" << std::endl;}}~NamedFifo(){// 回收命名管道int n = unlink(_filename.c_str());if(n < 0){std::cerr << "remove fifo failed" << std::endl;}else{std::cout << "remove fifo success" << std::endl;}}private:std::string _path;std::string _name;std::string _filename;
};

由于我們要實現一個進程寫,一個進程讀的單向通信,所以我們先規定,讓客戶端client.cc進程來寫,服務端server.cc進程來讀,那么讀寫操作我們還需要再封裝一個類,因為我們只要創建一個管道就行了。

如果都封裝在一個類中,那么客戶端和服務端都需要實例化出一個對象,才能對管道讀寫通信,但這樣就會創建兩個命名管道了,因為只要構造函數就會創建命名管道,而我們不需要兩個命名管道,我們只需要創建一個命名管道,然后服務端和客戶端分別以讀寫的方式打開這個管道文件就可以進行通信了,所以我們可以再封裝一個類來實現對打開的命名管道進行操作。

代碼如下:

class Fileoper
{
public:Fileoper(const std::string &path, const std::string &name): _path(path), _name(name), _fd(-1){_filename = _path + "/" + _name;}void OpenForRead(){_fd = open(_filename.c_str(), O_RDONLY);if(_fd < 0){std::cerr << "open fifo failed" << std::endl;}else{std::cout << "open fifo success" << std::endl;}}void OpenForWrite(){_fd = open(_filename.c_str(), O_WRONLY);if(_fd < 0){std::cerr << "open fifo failed" << std::endl;}else{std::cout << "open fifo success" << std::endl;}}~Fileoper() {}private:std::string _path;std::string _name;std::string _filename;int _fd;
};

由于我們需要打開指定路徑的管道文件,所以成員變量仍然需要和NamedFifo類一樣,但是我們打開管道文件后,需要通過返回的文件描述符后續管理規管道文件,所以我們還需要一個成員變量_fd,來接收open返回的文件描述符。客戶端需要從管道寫入,服務端需要從管道讀取,所以客戶端以只寫的方式打開管道文件,而服務端以只讀的方式打開管道文件。但是打開之后我們客戶端和服務端還需要對管道進行讀寫操作,所以我們還需要分別實現一個寫函數和一個讀函數

代碼如下:

    void Write(){std::string message;while(true){std::cout << "Please Enter#";std::getline(std::cin, message);write(_fd, message.c_str(), message.size());}}void Read(){while(true){char buffer[1024];ssize_t n = read(_fd, buffer, sizeof(buffer)-1);if(n > 0){buffer[n] = 0;std::cout << "Client say#" << buffer << std::endl;}else if(n == 0){std::cout << "Client quit! me too!" << std::endl;break;}else{std::cerr << "read error" << std::endl;break;}}}

當然,通信結束之后我們需要關閉文件描述符

    void Close(){close(_fd);}

測試

我們先定義兩個宏

#define PATH "."
#define FILENAME "fifo"

我們想要在當前路徑下創建一個fifo的管道文件

服務端:

#include "comm.hpp"int main()
{// 創建管道NamedFifo f(PATH, FILENAME);// 文件操作Fileoper reader(PATH, FILENAME);reader.OpenForRead();reader.Read();reader.Close();return 0;
}

客戶端:

#include "comm.hpp"int main()
{Fileoper Writer(PATH, FILENAME);Writer.OpenForWrite();Writer.Write();Writer.Close();   return 0;
}

運行測試:

可以看到成功實現了兩個沒有血緣關系的進程的單向通信

源碼:

comm.hpp:

#pragma once#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>#define PATH "."
#define FILENAME "fifo"class NamedFifo
{
public:NamedFifo(const std::string &path, const std::string &name): _path(path), _name(name){_filename = _path + "/" + _name;// 創建命名管道int n = mkfifo(_filename.c_str(), 0666);if (n < 0){std::cerr << "mkfifo failed" << std::endl;}else{std::cout << "mkfifo success" << std::endl;}}~NamedFifo(){// 回收命名管道int n = unlink(_filename.c_str());if (n < 0){std::cerr << "remove fifo failed" << std::endl;}else{std::cout << "remove fifo success" << std::endl;}}private:std::string _path;std::string _name;std::string _filename;
};class Fileoper
{
public:Fileoper(const std::string &path, const std::string &name): _path(path), _name(name), _fd(-1){_filename = _path + "/" + _name;}void OpenForRead(){_fd = open(_filename.c_str(), O_RDONLY);if(_fd < 0){std::cerr << "open fifo failed" << std::endl;}else{std::cout << "open fifo success" << std::endl;}}void OpenForWrite(){_fd = open(_filename.c_str(), O_WRONLY);if(_fd < 0){std::cerr << "open fifo failed" << std::endl;}else{std::cout << "open fifo success" << std::endl;}}void Write(){std::string message;while(true){std::cout << "Please Enter#";std::getline(std::cin, message);write(_fd, message.c_str(), message.size());}}void Read(){while(true){char buffer[1024];ssize_t n = read(_fd, buffer, sizeof(buffer)-1);if(n > 0){buffer[n] = 0;std::cout << "Client say#" << buffer << std::endl;}else if(n == 0){std::cout << "Client quit! me too!" << std::endl;break;}else{std::cerr << "read error" << std::endl;break;}}}void Close(){close(_fd);}~Fileoper() {}private:std::string _path;std::string _name;std::string _filename;int _fd;
};

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

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

相關文章

Python Day24 多線程編程:核心機制、同步方法與實踐案例

一、線程事件對象&#xff08;threading.Event&#xff09;threading.Event 用于實現線程間的通信&#xff0c;可讓一個線程通知其他線程終止任務&#xff0c;核心是通過 “事件觸發” 機制協調線程行為。核心方法&#xff1a;創建事件對象&#xff1a;event threading.Event(…

007 前端( JavaScript HTML DOM+Echarts)

一.html dom運用查找html元素的三種方式通過 id 找到 HTML 元素通過標簽名找到 HTML 元素通過類名找到 HTML 元素1.通過 id 找到 HTML 元素<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>msf的網頁</title> <…

實習文檔背誦

實習內容:1.定時任務與數據補全:基于 XXL-JOB 實現分布式定時任務調度&#xff0c;補全近半年歷史操作日志數據&#xff0c;有效解決因網絡異常導致的數據缺失問題。業務場景&#xff1b;集團的4a日志半年內沒有同步&#xff0c;這邊需要把日志數據同步到集團上首先先評估每天的…

分布式CAP定理

CAP 定理在一個分布式系統中&#xff0c;以下三個特性不可能同時完全滿足&#xff0c;最多只能滿足其中兩個&#xff1a;C&#xff08;Consistency&#xff0c;一致性&#xff09;&#xff1a;所有節點在同一時間看到的數據是完全一致的&#xff08;即更新操作成功并返回后&…

PHP-Casbin:現代化 PHP 應用的權限管理引擎

在當今復雜的Web應用中&#xff0c;精細化的權限管理是保障系統安全的關鍵環節。PHP-Casbin 作為Casbin生態的PHP實現&#xff0c;憑借其靈活的模型支持和強大的擴展能力&#xff0c;已成為PHP開發者實現訪問控制的首選工具。 超越傳統權限模型 PHP-Casbin 基于PERM&#xff…

FastDeploy2.0:環境變量的說明

一、執行# 設置日志目錄 export FD_LOG_DIR/workspace/models/log# 指定使用的 GPU 設備 export CUDA_VISIBLE_DEVICES0,1,2,3# 創建日志目錄&#xff08;如果不存在&#xff09; mkdir -p "$FD_LOG_DIR"# 定義日志文件路徑 LOG_FILE"$FD_LOG_DIR/fastdeploy_se…

C語言:指針(1-2)

5. 指針運算指針的基本運算有三種&#xff0c;分別是&#xff1a;指針-整數指針-指針指針的關系運算5.1 指針運算在上面&#xff0c;我們知道&#xff0c;數組在內存中是連續存放的&#xff0c;只要知道第一個元素的地址&#xff0c;順藤摸瓜就能找到后面的所有元素。那么&…

【多模態】DPO學習筆記

DPO學習筆記1 原理1.0 名詞1.1 preference model1.2 RLHF1.3 從RLHF到DPOA.解的最優形式B. DPO下參數估計C. DPO下梯度更新D. DPO訓練的穩定性2 源代碼2.1 數據集構成2.2 計算log prob2.3 DPO loss1 原理 1.0 名詞 preference model&#xff1a;對人類偏好進行建模&#xff0…

2025最新、UI媲美豆包、DeepSeek等AI大廠的AIGC系統 - IMYAI源碼部署教程

IMYAI 系統部署與使用手冊 一、系統演示 &#x1f539; 快速體驗 前端演示地址&#xff1a;https://super.imyaigc.com后臺演示地址&#xff1a;https://super.imyaigc.com/settings &#x1f539; 技術架構 前端&#xff1a;Vite Vue3 NaiveUI TailwindCSS Plyr后端&…

【關于Java的反射】

在 Java 編程中&#xff0c;反射&#xff08;Reflection&#xff09; 是一個非常強大的工具&#xff0c;它允許你在運行時動態地獲取類的信息、創建對象、調用方法和訪問字段。雖然反射功能強大&#xff0c;但它也有一些局限性和性能開銷&#xff0c;因此需要謹慎使用。一、什么…

Gitee推出“移動軟件工廠“解決方案 解決嵌入式與涉密場景研發困局

Gitee推出"移動軟件工廠"解決方案 破解嵌入式與涉密場景研發困局 隨著數字化轉型浪潮的推進&#xff0c;軟件開發正面臨著前所未有的復雜環境挑戰。特別是在嵌入式系統、FPGA開發以及涉密信息系統等特殊場景下&#xff0c;研發團隊往往需要在高安全要求與有限網絡環境…

低功耗16*8位四線串行8*4按鍵陣矩LED驅動專用電路

概述&#xff1a;PC0340是占空比可調的LED顯示控制驅動電路。由16根段輸出、8根位輸出、數字接口、數據鎖存器、顯示存儲器、鍵掃描電路及相關控制電路組成了一個高可靠性的單片機外圍LED驅動電路。串行數據通過4線串行接口輸入到PC0340&#xff0c;采用LQFP44L的封裝形式。本產…

通過自定義注解加aop切面實現權限控制

前言&#xff1a;自定義注解&#xff0c;通過aop切面前置通知&#xff0c;對請求接口進行權限控制1&#xff0c;創建枚舉類package org.springblade.sample.annotationCommon;import lombok.AllArgsConstructor; import lombok.Getter;import java.util.Arrays; import java.ut…

IDS知識點

在網絡安全工程師、系統運維工程師等崗位的面試中&#xff0c;??IDS&#xff08;Intrusion Detection System&#xff0c;入侵檢測系統&#xff09;?? 是高頻考點&#xff0c;尤其是對網絡安全防護、安全監控類崗位。以下是IDS的核心考點和必須掌握的知識點&#xff0c;按優…

Adobe Analytics 數據分析平臺|全渠道客戶行為分析與體驗優化

Adobe Analytics 是業界領先的數據分析平臺&#xff0c;幫助企業實時追蹤客戶行為&#xff0c;整合多渠道數據&#xff0c;通過強大的分析與可視化工具深入分析客戶旅程&#xff0c;優化數字體驗。結合 Adobe Experience Cloud&#xff0c;Adobe Analytics 成為推動數字化增長和…

【輪播圖】H5端輪播圖、橫向滑動、劃屏效果實現方案——Vue3+CSS position/CSS scroller

文章目錄定位實現滑屏效果前置知識CSS: touch-action屬性CSS: transform屬性觸摸事件forEach回調占位符準備階段實現移動效果實現跟手效果觸摸結束優化完整代碼滾動實現滑屏效果前置知識CSS: scroll-snap-type屬性準備階段實現滑動效果實現吸附效果滾動條隱藏存在問題完整代碼s…

忘記了WordPress管理員密碼的找回方法

WordPress管理員密碼找回方法 如果您忘記了WordPress管理員密碼&#xff0c;可以通過以下幾種方法找回或重置&#xff1a; 方法1&#xff1a;通過電子郵件重置(最簡單) 訪問您的WordPress登錄頁面(通常是wodepress.com/wp-admin或wodepress.com/wp-login.php) 點擊”忘記密…

RAFT:讓語言模型更聰明地用文檔答題

RAFT&#xff1a;讓語言模型更聰明地用文檔答題 作者注&#xff1a; 本文旨在面向零基礎讀者介紹 UC Berkeley 提出的 RAFT&#xff08;Retrieval-Augmented Fine-Tuning&#xff09;方法。它是一種訓練語言模型的新方式&#xff0c;讓模型更好地利用“外部知識”——比如文檔、…

【緊急預警】NVIDIA Triton推理服務器漏洞鏈可導致RCE!

2025 年 8 月 4 日消息&#xff0c;NVIDIA 旗下的 Triton 推理服務器&#xff08;一款支持 Windows 和 Linux 系統、用于大規模運行 AI 模型的開源平臺&#xff09;被曝出一系列安全漏洞。這些漏洞一旦被利用&#xff0c;攻擊者有可能完全接管存在漏洞的服務器。 Wiz 安全公司…

基于深度學習的醫學圖像分析:使用PixelCNN實現醫學圖像生成

前言 醫學圖像分析是計算機視覺領域中的一個重要應用&#xff0c;特別是在醫學圖像生成任務中&#xff0c;深度學習技術已經取得了顯著的進展。醫學圖像生成是指通過深度學習模型生成醫學圖像&#xff0c;這對于醫學研究、疾病模擬和圖像增強等任務具有重要意義。近年來&#x…