Linux之匿名管道和命名管道詳解
匿名管道
kubectl get pod -A | grep mysql
上面命令行里的「|」豎線就是一個管道,在命令行(如 Linux Shell 或 Windows CMD/PowerShell)中,管道操作符 |
的作用是將 前一個命令的標(biāo)準(zhǔn)輸出(stdout) 傳遞給 后一個命令的標(biāo)準(zhǔn)輸入(stdin)。但它默認(rèn) 不會傳遞標(biāo)準(zhǔn)錯誤(stderr),這是它的核心行為特點。
1. 管道|的基本行為
command1 | command2
command1
的stdout
→ 作為command2
的stdin
。command1
的stderr
→ 直接打印到終端,不會傳遞給command2
。
示例 1(stdout 被管道傳遞)
# ls 成功時,stdout(文件列表)會傳遞給 grep ls /usr/bin | grep "python"
示例 2(stderr 未被管道傳遞)
# 如果目錄不存在,錯誤信息會直接顯示,不會傳遞給 grep ls /nonexistent_dir | grep "error"
輸出:
ls: cannot access '/nonexistent_dir': No such file or directory
(錯誤信息直接顯示,grep
不會處理它)
2. 為什么|不處理 stderr
- 設(shè)計初衷:
|
的職責(zé)是傳遞 正常輸出,錯誤信息通常需要直接反饋給用戶。 - 分離數(shù)據(jù)流:stdout(正常輸出)和 stderr(錯誤信息)是獨立的流,
|
默認(rèn)只操作 stdout。
3. 原理
int pipe(int fd[2])
這里表示創(chuàng)建一個匿名管道,并返回了兩個描述符,一個是管道的讀取端描述符,另一個是管道的寫入端描述符fd。注意,這個匿名管道是特殊的文件,只存在于內(nèi)存,不存于文件系統(tǒng)中。
所謂的管道,就是內(nèi)核里面的一串緩存。從管道的一段寫入的數(shù)據(jù),實際上是緩存在內(nèi)核中的,另一端讀取,也就是從內(nèi)核中讀取這段數(shù)據(jù)。另外,管道傳輸?shù)臄?shù)據(jù)是無格式的流且大小受限。
管道只能一端寫入,另一端讀出,所以上面這種模式容易造成混亂,因為父進程和子進程都可以同時寫入,也都可以讀出。那么,為了避免這種情況,通常的做法是:
- 父進程關(guān)閉讀取的 fd[o],只保留寫入的 fd[1];
- 子進程關(guān)閉寫入的fd[1],只保留讀取的 fd[o];
在shell 里面執(zhí)行 A | B 命令的時候,A 進程和 B 進程都是shell 創(chuàng)建出來的子進程,A 和 B 之間不存在父子關(guān)系,它倆的父進程都是 shell。
命名管道
簡介
- 匿名管道,它的通信范圍是存在父子關(guān)系的進程。因為管道沒有實體,也就是沒有管道文件,只能通過fork來復(fù)制父進程 fd文件描述符,來達到通信的目的。
- 對于命名管道,它可以在不相關(guān)的進程間也能相互通信。因為命令管道,提前創(chuàng)建了一個類型為管道的設(shè)備文件,在進程里只要使用這個設(shè)備文件,就可以相互通信。
- 不管是匿名管道還是命名管道,進程寫入的數(shù)據(jù)都是緩存在內(nèi)核中,另一個進程讀取數(shù)據(jù)時候自然也是從內(nèi)核中獲取,同時通信數(shù)據(jù)都遵循先進先出原則,不支持Iseek之類的文件定位操作。
使用
int mkfifo(const char *pathname, mode_t mode);
創(chuàng)建 FIFO
使用 mkfifo
命令或 mkfifo()
函數(shù)創(chuàng)建:
mkfifo /tmp/myfifo # 創(chuàng)建一個名為 /tmp/myfifo 的 FIFO
pathname
:FIFO 的文件路徑(如/tmp/myfifo
)。mode
:權(quán)限(如0644
,表示用戶可讀寫,其他人只讀)。
FIFO 的特點
半雙工通信
- 同一時間只能 讀 或 寫,不能同時讀寫(類似單行道)。
- 示例:
# 終端1:寫入數(shù)據(jù) echo "Hello" > /tmp/myfifo # 終端2:讀取數(shù)據(jù) cat /tmp/myfifo # 輸出 "Hello"
阻塞機制
- 如果沒有進程在 讀,寫操作會 卡住,直到有進程來讀。
- 如果沒有進程在 寫,讀操作會 卡住,直到有進程來寫。
數(shù)據(jù)是流式的
- 數(shù)據(jù)像水流一樣,讀完后會消失,不能像普通文件那樣隨意跳轉(zhuǎn)(
lseek
無效)。
如何使用 FIFO?
(1)命令行測試
# 終端1:監(jiān)聽 FIFO(讀) cat /tmp/myfifo # 終端2:發(fā)送數(shù)據(jù)(寫) echo "Hello FIFO" > /tmp/myfifo
終端1 會顯示 Hello FIFO
。
(2)C 語言示例
#include <fcntl.h> #include <sys/stat.h> #include <unistd.h> int main() { mkfifo("/tmp/myfifo", 0644); // 創(chuàng)建 FIFO int fd = open("/tmp/myfifo", O_WRONLY); // 以寫方式打開 write(fd, "Hello", 6); // 寫入數(shù)據(jù) close(fd); return 0; }
常見問題
Q1: FIFO 和普通文件有什么區(qū)別?
- FIFO 是 內(nèi)存中的管道,數(shù)據(jù)讀完就沒了;普通文件會持久化存儲。
Q2: FIFO 和匿名管道(|
)有什么區(qū)別?
- 匿名管道只能用于 父子進程,F(xiàn)IFO 可用于 任意進程。
Q3: 如果 FIFO 已經(jīng)存在,再創(chuàng)建會怎樣?
mkfifo
會失敗,并提示File exists
。
原理
1. 普通文件 vs. 命名管道(FIFO)
普通文件(如 a.txt
):
- 數(shù)據(jù)存儲在 磁盤 上,打開時會加載到內(nèi)存。
- 修改后需要 寫回磁盤(刷盤)。
- 多個進程打開同一文件時,數(shù)據(jù)可能不同步(除非加鎖)。
命名管道(FIFO):
- 本質(zhì)是一個 內(nèi)存緩沖區(qū),只是偽裝成文件(磁盤上只有文件名,沒有實際數(shù)據(jù)塊)。
- 數(shù)據(jù) 不寫入磁盤,直接在進程間流動。
- 專為 進程間通信(IPC) 設(shè)計,速度快。
2. 命名管道的工作原理
(1)創(chuàng)建 FIFO
當(dāng)執(zhí)行 mkfifo /tmp/myfifo
時:
- 磁盤上創(chuàng)建一個 空文件(只有 inode,沒有數(shù)據(jù)塊)。
- 操作系統(tǒng)內(nèi)核維護一個 內(nèi)存緩沖區(qū)(用于存儲進程間傳遞的數(shù)據(jù))。
(2)進程 A 寫入數(shù)據(jù)
echo "Hello" > /tmp/myfifo
- 進程 A 打開 FIFO 以寫方式(
O_WRONLY
)。 - 數(shù)據(jù)
"Hello"
被寫入內(nèi)核的 內(nèi)存緩沖區(qū)(不刷盤?。?。 - 如果 沒有進程在讀取,進程 A 會 阻塞(卡?。钡接羞M程來讀。
(3)進程 B 讀取數(shù)據(jù)
cat /tmp/myfifo
- 進程 B 打開 FIFO 以讀方式(
O_RDONLY
)。 - 從內(nèi)核緩沖區(qū)讀取
"Hello"
,數(shù)據(jù)被消費(緩沖區(qū)清空)。 - 如果 沒有進程在寫入,進程 B 會 阻塞,直到有進程寫入。
3. 關(guān)鍵點解析
(1)文件描述符與內(nèi)核結(jié)構(gòu)
每個進程打開 FIFO 時,內(nèi)核會 復(fù)用同一個 struct file
(通過引用計數(shù) ref
管理)。
- 第一次打開時創(chuàng)建
struct file
,ref=1
。 - 第二個進程打開時,
ref=2
(指向同一個內(nèi)存緩沖區(qū))。 - 關(guān)閉時
ref--
,ref=0
時內(nèi)核才釋放資源。
(2)為什么數(shù)據(jù)不刷盤?
- FIFO 的 設(shè)計目的 是進程間通信,數(shù)據(jù)不需要持久化。
- 刷盤會 拖慢速度,且毫無意義(通信完數(shù)據(jù)即可丟棄)。
(3)半雙工通信
- 同一時間只能 單向流動(讀或?qū)懀?/li>
- 如果需要雙向通信,需創(chuàng)建 兩個 FIFO(一個負(fù)責(zé) A→B,一個負(fù)責(zé) B→A)。
4. 類比理解
把 FIFO 想象成 一條水管:
寫入端:進程 A 往水管里倒水(數(shù)據(jù))。
讀取端:進程 B 從水管接水(數(shù)據(jù))。
特性:
- 水管沒有儲水功能(數(shù)據(jù)不持久化)。
- 如果沒人接水,倒水的人會等待(阻塞寫入)。
- 如果沒人倒水,接水的人會等待(阻塞讀?。?。
總結(jié)
特性 | 命名管道(FIFO) | 普通文件 |
---|---|---|
存儲位置 | 內(nèi)存緩沖區(qū) | 磁盤 |
刷盤 | 永不刷盤 | 定期刷盤 |
多進程訪問 | 共享同一緩沖區(qū) | 可能數(shù)據(jù)不同步 |
阻塞行為 | 讀/寫會阻塞 | 無阻塞 |
用途 | 進程間通信 | 數(shù)據(jù)存儲 |
簡單來說:
FIFO 是 披著文件外衣的內(nèi)存管道,讓進程可以通過文件路徑名通信,數(shù)據(jù) 不落盤,速度極快!
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
在Linux環(huán)境下采用壓縮包方式安裝JDK 13的方法
JDK(Java Development Kit)是Sun公司(后被Oracle收購)推出的面向?qū)ο蟪绦蛟O(shè)計語言的開發(fā)工具包,擁有這個工具包之后我們就可以使用Java語言進行程序設(shè)計和開發(fā)。這篇文章主要介紹了在Linux環(huán)境下采用壓縮包方式安裝JDK 13,需要的朋友可以參考下2019-10-10Linux有限狀態(tài)機FSM的理解與實現(xiàn)
這篇文章主要幫助大家理解與實現(xiàn)Linux有限狀態(tài)機FSM,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06crontab執(zhí)行時間與系統(tǒng)時間不一致問題解決
這篇文章主要給大家介紹了關(guān)于crontab執(zhí)行時間與系統(tǒng)時間不一致問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01ubuntu20.04 LTS系統(tǒng)默認(rèn)源sources.list文件的修改
這篇文章主要介紹了ubuntu20.04 LTS系統(tǒng)默認(rèn)源sources.list文件的修改,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08Linux系統(tǒng)設(shè)置開機自動運行腳本的方法實例
這篇文章主要給大家介紹了關(guān)于Linux系統(tǒng)設(shè)置開機自動運行腳本的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Linux系統(tǒng)具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06使用 Apache Web 服務(wù)器配置兩個或多個站點的方法
這篇文章主要介紹了使用 Apache Web 服務(wù)器配置多個站點的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2018-10-10Linux安裝NodeJs并配合Nginx實現(xiàn)反向代理
本篇文章主要介紹了Linux安裝NodeJs并配合Nginx實現(xiàn)反向代理,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2016-11-11