色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux管道和FIFO應(yīng)用筆記

strongerHuang ? 來源:TLPI系統(tǒng)編程筆記 ? 2023-03-13 10:12 ? 次閱讀

概述

管道最常見的地方是shell中,比如:

$ ls | wc -l

為了執(zhí)行上面的命令,shell創(chuàng)建了兩個進(jìn)程來分別執(zhí)行 ls 和 wc (通過 fork() 和 exec() 完成),如下:

92300dc0-bfe8-11ed-bfe3-dac502259ad0.png

從上圖可以看出,可以將管道看成是一組水管,它允許數(shù)據(jù)從一個進(jìn)程流向另一個進(jìn)程,這也是管道名稱的由來。

從上圖可以看出,由兩個進(jìn)程連接到了管道上,這樣寫入進(jìn)程 ls 就將其標(biāo)準(zhǔn)輸出(文件描述符為1)連接到來管道的寫入段,讀取進(jìn)程 wc 就將其標(biāo)準(zhǔn)輸入(文件描述符為0)連接到管道的讀取端。實(shí)際上,這兩個進(jìn)程并不知道管道的存在,它們只是從標(biāo)準(zhǔn)文件描述符中讀取和寫入數(shù)據(jù)。shell 必須要完成相關(guān)的工作。

一個管道是一個字節(jié)流

管道是一個字節(jié)流,即在使用管道時是不存在消息或者消息邊界的概念的:

從管道中讀取數(shù)據(jù)的進(jìn)程可以讀取任意大小的數(shù)據(jù)塊,而不管寫入進(jìn)程寫入管道的數(shù)據(jù)塊的大小是什么

通過管道傳遞的數(shù)據(jù)是順序的,從管道中讀取出來的字節(jié)的順序與它們被寫入管道的順序是完全一樣的,在管道中無法使用 lseek() 來隨機(jī)的訪問數(shù)據(jù)

如果需要在管道中實(shí)現(xiàn)離散消息的概念,那么就必須要在應(yīng)用程序中完成這些工作。雖然這是可行的,但如果碰到這種需求的話最好使用其他 IPC 機(jī)制,如消息隊(duì)列和數(shù)據(jù)報 socket。

從管道中讀取數(shù)據(jù)

試圖從一個當(dāng)前為空的管道中讀取數(shù)據(jù)將會被阻塞直至至少有一個字節(jié)被寫入到管道中為止。

如果管道的寫入端被關(guān)閉了,那么從管道中讀取數(shù)據(jù)的進(jìn)程在讀完管道中剩余的所有數(shù)據(jù)之后將會看到文件結(jié)束(即 read() 返回 0)。

管道是單向的

在管道中數(shù)據(jù)的傳遞方向是單向的。管道的一端用于寫入,另一端則用于讀取。

在其他一些 UNIX 實(shí)現(xiàn)上,特別是那些從 System V Release 4 演化而來的系統(tǒng),管道是雙向的(所謂的流管道)。雙向管道并沒有在任何 UNIX 標(biāo)準(zhǔn)中進(jìn)行規(guī)定,因此即使在提供了雙向管道的實(shí)現(xiàn)上最好也避免依賴這種語義。作為替代方案,可以使用 UNIX domain 流 socket 對(通過 socketpair() 系統(tǒng)調(diào)用來創(chuàng)建),它提供了一種標(biāo)準(zhǔn)的雙向通信機(jī)制,并且其語義與流管道是等價的。

可以確保寫入不超過 PIPE_BUF 字節(jié)的操作是原子的

如果多個進(jìn)程寫入同一個管道,那么如果它們在一個時刻寫入的數(shù)據(jù)量不超過 PIPE_BUF 字節(jié),那么就可以確保寫入的數(shù)據(jù)不會發(fā)生相互混合的情況。

SUSv3 要求 PIPE_BUF 至少為 _POSIX_PIPE_BUF(512)。一個實(shí)現(xiàn)應(yīng)該定義 PIPE_BUF(在 中)并/或允許調(diào)用 fpathconf(fd,_PC_PIPE_BUF) 來返回原子寫入操作的實(shí)際上限。不同 UNIX 實(shí)現(xiàn)上的 PIPE_BUF 不同,如在 FreeBSD 6.0 其值為 512 字節(jié),在 Tru64 5.1 上其值為 4096 字節(jié),在 Solaris 8 上其值為 5120 字節(jié)。在 Linux 上,PIPE_BUF 的值為 4096。

當(dāng)寫入管道的數(shù)據(jù)塊的大小超過了 PIPE_BUF 字節(jié),那么內(nèi)核可能會將數(shù)據(jù)分割成幾個較小的片段來傳輸,在讀者從管道中消耗數(shù)據(jù)時再附加上后繼的數(shù)據(jù)(write()調(diào)用會阻塞直到所有數(shù)據(jù)被寫入到管道為止)

當(dāng)只有一個進(jìn)程向管道寫入數(shù)據(jù)時(通常的情況),PIPE_BUF 的取值就沒有關(guān)系了

但如果有多個寫入進(jìn)程,那么大數(shù)據(jù)塊的寫入可能會被分解成任意大小的段(可能會小于 PIPE_BUF 字節(jié)),并且可能會出現(xiàn)與其他進(jìn)程寫入的數(shù)據(jù)交叉的現(xiàn)象

只有在數(shù)據(jù)被傳輸?shù)焦艿赖臅r候 PIPE_BUF 限制才會起作用。當(dāng)寫入的數(shù)據(jù)達(dá)到 PIPE_BUF 字節(jié)時,write() 會在必要的時候阻塞知道管道中的可用空間足以原子的完成此操作。如果寫入的數(shù)據(jù)大于 PIPE_BUF 字節(jié),那么 write() 會盡可能的多傳輸數(shù)據(jù)以充滿整個管道,然后阻塞直到一些讀取進(jìn)程從管道中移除了數(shù)據(jù)。如果此類阻塞的 write() 被一個信號處理器中斷了,那么這個調(diào)用會被解除阻塞并返回成功傳輸?shù)焦艿乐械淖止?jié)數(shù),這個字節(jié)數(shù)會少于請求寫入的字節(jié)數(shù)(所謂的部分寫入)。

管道的容量是有限的

管道其實(shí)是一個在內(nèi)核內(nèi)存中維護(hù)的緩沖器,這個緩沖器的存儲能力是有限的。一旦管道被填滿之后,后繼向管道的寫入操作就會被阻塞直到讀者從管道中移除了一些數(shù)據(jù)為止。

SUSv3 并沒有規(guī)定管道的存儲能力。在早于 2.6.11 的 Linux 內(nèi)核中,管道的存儲能力與系統(tǒng)頁面的大小是一致的(如在 x86-32 上是 4096 字節(jié)),而從 Linux 2.6.11 起,管道的存儲能力是 65,536 字節(jié)。其他 UNIX 實(shí)現(xiàn)上的管道的存儲能力可能是不同的。

一般來講,一個應(yīng)用程序無需知道管道的實(shí)際存儲能力。如果需要防止寫者進(jìn)程阻塞,那么從管道中讀取數(shù)據(jù)的進(jìn)程應(yīng)該被設(shè)計成以盡可能快的速度從管道中讀取數(shù)據(jù)。

創(chuàng)建和使用管道

#include 

int pipe(int fd[2]);  

pipe() 創(chuàng)建一個新管道

成功的調(diào)用在數(shù)組 fd 中返回兩個打開的文件描述符,一個表示管道的讀取端 fd[0],一個表示管道的寫入端 fd[1]

調(diào)用 pipe() 函數(shù)時,首先在內(nèi)核中開辟一塊緩沖區(qū)用于通信,它有一個讀端和一個寫端,然后通過 fd 參數(shù)傳出給用戶進(jìn)程兩個文件描述符,fd[0] 指向管道的讀端,fd[1] 指向管道的寫段。

不要用 fd[0] 寫數(shù)據(jù),也不要用 fd[1] 讀數(shù)據(jù),其行為未定義的,但在有些系統(tǒng)上可能會返回 -1 表示調(diào)用失敗。數(shù)據(jù)只能從 fd[0] 中讀取,數(shù)據(jù)也只能寫入到fd[1],不能倒過來。

與所有文件描述符一樣,可以使用 read() 和 write() 系統(tǒng)調(diào)用來在管道上執(zhí)行 IO,一旦向管道的寫入端寫入數(shù)據(jù)之后立即就能從管道的讀取端讀取數(shù)據(jù)。管道上的 read() 調(diào)用會讀取的數(shù)據(jù)量為所請求的字節(jié)數(shù)與管道中當(dāng)前存在的字節(jié)數(shù)兩者之間的較小值。當(dāng)管道為空時,讀取操作阻塞。

也可以在管道上使用 stdio 函數(shù)(printf()、scanf() 等),只需要首先使用 fdopen() 獲取一個與 filedes 中的某個描述符對應(yīng)的文件流即可。但在這樣做的時候需要解決 stdio 緩沖問題。

管道可以用于進(jìn)程內(nèi)部自己通信:

9248e354-bfe8-11ed-bfe3-dac502259ad0.png

管道可以用于親緣關(guān)系(子進(jìn)程會繼承父進(jìn)程中的文件描述符的副本)進(jìn)程中通信:

92572720-bfe8-11ed-bfe3-dac502259ad0.png

不建議將單個 pipe 用作全雙工的,或者不關(guān)閉用作半雙工而不關(guān)閉相應(yīng)的讀端/寫端,這樣很可能導(dǎo)致死鎖:如果兩個進(jìn)程同時試圖從管道中讀取數(shù)據(jù),那么就無法確定哪個進(jìn)程會首先讀取成功,從而產(chǎn)生兩個進(jìn)程競爭數(shù)據(jù)了。要防止這種競爭情況的出現(xiàn)就需要使用某種同步機(jī)制。這時,就需要考慮死鎖問題了,因?yàn)槿绻麅蓚€進(jìn)程都試圖從空管道中讀取數(shù)據(jù)或者嘗試向已滿的管道中寫入數(shù)據(jù)就可能會發(fā)生死鎖。

如果我們想要一個雙向數(shù)據(jù)流時,可以創(chuàng)建兩個管道,每個方向一個。

管道允許相關(guān)進(jìn)程間的通信

其實(shí)管道可以用于任意兩個甚至更多相關(guān)進(jìn)程之間的通信,只要在創(chuàng)建子進(jìn)程的系列 fork() 調(diào)用之前通過一個共同的祖先進(jìn)程創(chuàng)建管道即可。

關(guān)閉未使用管道文件描述符

關(guān)閉未使用管道文件描述符不僅僅是為了確保進(jìn)程不會消耗盡其文件描述符的限制。

從管道中讀取數(shù)據(jù)的進(jìn)程會關(guān)閉其持有的管道的寫入描述符,這樣當(dāng)其他進(jìn)程完成輸出并關(guān)閉其寫入描述符之后,讀者就能夠看到文件結(jié)束。反之,如果讀取的進(jìn)程沒有關(guān)閉管道的寫入端,那么在其他進(jìn)程關(guān)閉了寫入描述符之后,即使讀者已經(jīng)讀完了管道中的所有數(shù)據(jù),也不會看到文件結(jié)束。因?yàn)榇藭r內(nèi)核知道至少還有一個管道的寫入描述符打開著,從而導(dǎo)致 read() 阻塞。

當(dāng)一個進(jìn)程視圖向一個管道中寫入數(shù)據(jù)但沒有任何進(jìn)程擁有該管道的打開著的讀取描述符時,內(nèi)核會向?qū)懭脒M(jìn)程發(fā)送一個 SIGPIPE 信號,默認(rèn)情況下,這個信號將會殺死進(jìn)程,但進(jìn)程可以選擇忽略或者設(shè)置信號處理器,這樣 write() 將因?yàn)?EPIPE 錯誤而失敗。收到 SIGPIPE 信號和得到 EPIPE 錯誤對于標(biāo)識管道的狀態(tài)是有意義的,這就是為什么需要關(guān)閉管道的未使用讀取描述符的原因。如果寫入進(jìn)程沒有關(guān)閉管道的讀取端,那么即使在其他進(jìn)程已經(jīng)關(guān)閉了管道的讀取端之后,寫入進(jìn)程仍然能夠向管道寫入數(shù)據(jù),最后寫入進(jìn)程會將數(shù)據(jù)充滿整個管道,后續(xù)的寫入請求會將永遠(yuǎn)阻塞。

使用管道連接過濾器

當(dāng)管道被創(chuàng)建之后,為管道的兩端分配的文件描述符是可用描述符中數(shù)值最小的兩個,由于通常情況下,進(jìn)程已經(jīng)使用了描述符 0,1,2,因此會為管道分配一些數(shù)值更大的描述符。如果需要使用管道連接兩個過濾器(即從 stdin 讀取和寫入到 stdout),使得一個程序的標(biāo)準(zhǔn)輸出被重定向到管道中,就需要采用復(fù)制文件描述符技術(shù)。

int pfd[2];
pipe(pfd);

close(STDOUT_FILENO);
dup2(pfd[1],STDOUT_FILENO);

上面這些調(diào)用的最終結(jié)果是進(jìn)程的標(biāo)準(zhǔn)輸出被綁定到管道的寫入端,而對應(yīng)的一組調(diào)用可以用來將進(jìn)程的標(biāo)準(zhǔn)的輸入綁定到管道的讀取端上。

通過管道與 shell 命令進(jìn)行通信: popen()

#include 

FILE *popen (const char *command, const char *mode);

pipe() 和 close() 是最底層的系統(tǒng)調(diào)用,它的進(jìn)一步封裝是 popen() 和 pclose()

popen()函數(shù)創(chuàng)建了一個管道,然后創(chuàng)建了一個子進(jìn)程來執(zhí)行 shell,而 shell 又創(chuàng)建了一個子進(jìn)程來執(zhí)行command字符串

mode 參數(shù)是一個字符串:

它確定調(diào)用進(jìn)程是從管道中讀取數(shù)據(jù)(mode 是 r)還是將數(shù)據(jù)寫入到管道中(mode 是 w)

由于管道是向的,因此無法在執(zhí)行的 command 中進(jìn)行雙向通信

mode 的取值確定了所執(zhí)行的命令的標(biāo)準(zhǔn)輸出是連接到管道的寫入端還是將其標(biāo)準(zhǔn)輸入連接到管道的讀取端

92726fda-bfe8-11ed-bfe3-dac502259ad0.png

popen() 在成功時會返回可供 stdio 庫函數(shù)使用的文件流指針。當(dāng)發(fā)生錯誤時,popen() 會返回 NULL 并設(shè)置 errno 以標(biāo)示出發(fā)生錯誤的原因

在 popen() 調(diào)用之后,調(diào)用進(jìn)程使用管道來讀取 command 的輸出或使用管道向其發(fā)送輸入。與使用 pipe() 創(chuàng)建的管道一樣,當(dāng)從管道中讀取數(shù)據(jù)時,調(diào)用進(jìn)程在 command 關(guān)閉管道的寫入端之后會看到文件結(jié)束;當(dāng)向管道寫入數(shù)據(jù)時,如果 command 已經(jīng)關(guān)閉了管道的讀取端,那么調(diào)用進(jìn)程就會收到 SIGPIPE 信號并得到 EPIPE 錯誤

#include 

int pclose ( FILE * stream);

一旦IO結(jié)束之后可以使用 pclose() 函數(shù)關(guān)閉管道并等待子進(jìn)程中的 shell 終止(不應(yīng)該使用 fclose() 函數(shù),因?yàn)樗粫却舆M(jìn)程。)

pclose() 在成功時會返回子進(jìn)程中 shell 的終止?fàn)顟B(tài)(即 shell 所執(zhí)行的最后一條命令的終止?fàn)顟B(tài),除非 shell 是被信號殺死的)

和 system() 一樣,如果無法執(zhí)行shell,那么 pclose() 會返回一個值就像子進(jìn)程中的 shell 通過調(diào)用 _exit(127) 來終止一樣

如果發(fā)生了其他錯誤,那么 pclose() 返回 ?1。其中可能發(fā)生的一個錯誤是無法取得終止?fàn)顟B(tài)

當(dāng)執(zhí)行等待以獲取子進(jìn)程中 shell 的狀態(tài)時,SUSv3 要求 pclose() 與 system() 一樣,即在內(nèi)部的 waitpid() 調(diào)用被一個信號處理器中斷之后自動重啟該調(diào)用。

與 system() 一樣,在特權(quán)進(jìn)程中永遠(yuǎn)都不應(yīng)該使用 popen()。

popen優(yōu)缺點(diǎn):

優(yōu)點(diǎn):在 Linux 中所有的參數(shù)擴(kuò)展都是由 shell 來完成的。所以在啟動 command 命令之前程序先啟動 shell 來分析 command 字符串,就可以使用各種 shell 擴(kuò)展(比如通配符),這樣我們可以通過 popen() 調(diào)用非常復(fù)雜的 shell 命令

缺點(diǎn):對于每個 popen() 調(diào)用,不僅要啟動一個被請求的程序,還需要啟動一個 shell。即每一個 popen() 將啟動兩個進(jìn)程。從效率和資源的角度看,popen() 函數(shù)的調(diào)用比正常方式要慢一些

pipe() VS popen()

pipe()是一個底層調(diào)用,popen() 是一個高級的函數(shù)

pipe() 單純的創(chuàng)建管道,而 popen() 創(chuàng)建管道的同時 fork() 子進(jìn)程

popen() 在兩個進(jìn)程中傳遞數(shù)據(jù)時需要調(diào)用 shell 來解釋請求命令;pipe() 在兩個進(jìn)程中傳遞數(shù)據(jù)不需要啟動 shell 來解釋請求命令,同時提供了對讀寫數(shù)據(jù)的更多控制(popen() 必須時 shell 命令,pipe() 則無硬性要求)

popen() 函數(shù)是基于文件流(FILE)工作的,而 pipe() 是基于文件描述符工作的,所以在使用 pipe() 后,數(shù)據(jù)必須要用底層的read() 和 write() 調(diào)用來讀取和發(fā)送

管道和 stdio 緩沖

由于 popen() 調(diào)用返回的文件流指針沒有引用一個終端,因此 stdio 庫會對這種流應(yīng)用塊緩沖。這意味著當(dāng) mode 的值為 w 來調(diào)用 popen() 時,默認(rèn)情況下只有當(dāng) stdio 緩沖區(qū)被充滿或者使用 pclose() 關(guān)閉了管道之后才會被發(fā)送到管道的另一端的子進(jìn)程。在很多情況下,這種處理方式是不存在問題的。但如果需要確保子進(jìn)程能夠立即從管道中接收數(shù)據(jù),那么就需要定期調(diào)用 fflush() 或使用 setbuf(fp, NULL) 調(diào)用禁用 stdio 緩沖。當(dāng)使用 pipe() 系統(tǒng)調(diào)用創(chuàng)建管道,然后使用 fdopen() 獲取一個與管道的寫入端對應(yīng)的 stdio 流時也可以使用這項(xiàng)技術(shù)

如果調(diào)用 popen() 的進(jìn)程正在從管道中讀取數(shù)據(jù)(即 mode 是 r),那么事情就不是那么簡單了。在這樣情況下如果子進(jìn)程正在使用 stdio 庫,那么——除非它顯式地調(diào)用了 fflush() 或 setbuf() ,其輸出只有在子進(jìn)程填滿 stdio 緩沖器或調(diào)用了 fclose() 之后才會對調(diào)用進(jìn)程可用。(如果正在從使用 pipe() 創(chuàng)建的管道中讀取數(shù)據(jù)并且向另一端寫入數(shù)據(jù)的進(jìn)程正在使用 stdio 庫,那么同樣的規(guī)則也是適用的。)如果這是一個問題,那么能采取的措施就比較有限的,除非能夠修改在子進(jìn)程中運(yùn)行的程序的源代碼使之包含對 setbuf() 或 fflush() 調(diào)用。

如果無法修改源代碼,那么可以使用偽終端來替換管道。一個偽終端是一個 IPC 通道,對進(jìn)程來講它就像是一個終端。其結(jié)果是 stdio 庫會逐行輸出緩沖器中的數(shù)據(jù)。

命名管道(FIFO)

上述管道雖然實(shí)現(xiàn)了進(jìn)程間通信,但是它具有一定的局限性:

匿名管道只能是具有血緣關(guān)系的進(jìn)程之間通信

它只能實(shí)現(xiàn)一個進(jìn)程寫另一個進(jìn)程讀,而如果需要兩者同時進(jìn)行時,就得重新打開一個管道

為了使任意兩個進(jìn)程之間能夠通信,就提出了命名管道(named pipe 或 FIFO):

FIFO 與管道的區(qū)別:FIFO 在文件系統(tǒng)中擁有一個名稱,并且其打開方式與打開一個普通文件一樣,能夠?qū)崿F(xiàn)任何兩個進(jìn)程之間通信。而匿名管道對于文件系統(tǒng)是不可見的,它僅限于在父子進(jìn)程之間的通信

一旦打開了 FIFO,就能在它上面使用與操作管道和其他文件的系統(tǒng)調(diào)用一樣的 IO 系統(tǒng)調(diào)用 read(),write(),close()。與管道一樣,F(xiàn)IFO 也有一個寫入端和讀取端,并且總是遵循先進(jìn)先出的原則,即第一個進(jìn)來的數(shù)據(jù)會第一個被讀走

與管道一樣,當(dāng)所有引用 FIFO 的描述符都關(guān)閉之后,所有未被讀取的數(shù)據(jù)都將被丟棄

使用 mkfifo 命令可以在 shell 中創(chuàng)建一個 FIFO:

mkfifo [-m mode] pathname

pathname 是創(chuàng)建的 FIFO 的名稱,-m 選項(xiàng)指定權(quán)限 mode,其工作方式與 chmod 命令一樣

fstat() 和 stat() 函數(shù)會在 stat 結(jié)構(gòu)的 st_mode 字段返回 S_IFIFO,使用 ls -l 列出文件時,F(xiàn)IFO 文件在第一列的類型為 p,ls -F 會在 FIFO 路徑名后面附加管道符 |

#include 
#include 

int mkfifo(const char *pathname,mode_t mode);

mode 參數(shù)指定了新 FIFO 的權(quán)限,這些權(quán)限會按照進(jìn)程的 umask 值來取掩碼

一旦創(chuàng)建了 FIFO,任何進(jìn)程都能夠打開它,只要它通過常規(guī)的文件權(quán)限檢測

使用 FIFO 時唯一明智的做法是在兩端分別設(shè)置一個讀取進(jìn)程和一個寫入進(jìn)程。這樣在默認(rèn)情況下,打開一個 FIFO 以便讀取數(shù)據(jù)(open() O_RDONLY 標(biāo)記)將會阻塞直到另一個進(jìn)程打開 FIFO 以寫入數(shù)(open() O_WRONLY 標(biāo)記)為止。相應(yīng)地,打開一個 FIFO 以寫入數(shù)據(jù)將會阻塞直到另一個進(jìn)程打開 FIFO 以讀取數(shù)據(jù)為止。換句話說,打開一個 FIFO 會同步讀取進(jìn)程和寫入進(jìn)程。如果一個 FIFO 的另一端已經(jīng)打開(可能是因?yàn)橐粚M(jìn)程已經(jīng)打開了 FIFO 的兩端),那么open() 調(diào)用會立即成功。

在大多數(shù) Unix 實(shí)現(xiàn)上(包含 Linux),當(dāng)打開一個 FIFO 時可以通過指定 O_RDWR 標(biāo)記來繞過打開 FIFO 時的阻塞行為。這樣,open() 會立即返回,但無法使用返回的文件描述符在 FIFO 上讀取和寫入數(shù)據(jù)。這種做法破壞了 FIFO 的 IO 模型,SUSv3 明確指出以 O_RDWR 標(biāo)記打開一個 FIFO 的結(jié)果是未知的,因此出于可移植性的原因,開發(fā)人員不應(yīng)該使用這項(xiàng)技術(shù)。對于那些需要避免在打開 FIFO 時發(fā)生阻塞的需求,open() 的 O_NONBLOCK 標(biāo)記提供了一種標(biāo)準(zhǔn)化的方法來完成這個任務(wù):

  open(const char *path, O_RDONLY | O_NONBLOCK);
  open(const char *path, O_WRONLY | O_NONBLOCK);

在打開一個 FIFO 時避免使用 O_RDWR 標(biāo)記還有另外一個原因,當(dāng)采用那種方式調(diào)用 open() 之后,調(diào)用進(jìn)程在從返回的文件描述符中讀取數(shù)據(jù)時永遠(yuǎn)都不會看到文件結(jié)束,因?yàn)橛肋h(yuǎn)都至少存在一個文件描述符被打開著以等待數(shù)據(jù)被寫入 FIFO,即進(jìn)程從中讀取數(shù)據(jù)的那個描述符。

使用 FIFO 和 tee 創(chuàng)建雙重管道線

shell 管道線的其中一個特征是它們是線性的,管道線中的每個進(jìn)程都能讀取前一個進(jìn)程產(chǎn)生的數(shù)據(jù)并將數(shù)據(jù)發(fā)送到其后一個進(jìn)程中,使用 FIFO 就能夠在管道線中創(chuàng)建子進(jìn)程,這樣除了將一個進(jìn)程的輸出發(fā)送給管道線中的后面一個進(jìn)程之外,還可以復(fù)制進(jìn)程的輸出并將數(shù)據(jù)發(fā)送到另一個進(jìn)程中,要完成這個任務(wù)就需要使用 tee 命令,它將其從標(biāo)準(zhǔn)輸入中讀取到的數(shù)據(jù)復(fù)制兩份并輸出:一份寫入標(biāo)準(zhǔn)輸出,另一份寫入到通過命令行參數(shù)指定的文件中。

mkfifo myfifo
wc -l < myfifo &
ls -l | tee myfifo | sort -k5n

92e1dfaa-bfe8-11ed-bfe3-dac502259ad0.png

非阻塞 IO

當(dāng)一個進(jìn)程打開一個 FIFO 的一端時,如果 FIFO 的另一端還沒有被打開,那么該進(jìn)程會被阻塞。但有些時候阻塞并不是期望的行為,而這可以通過在調(diào)用 open() 時指定 O_NONBLOCK 標(biāo)記來實(shí)現(xiàn)。

如果 FIFO 的另一端已經(jīng)被打開,那么 O_NONBLOCK 對 open() 調(diào)用不會產(chǎn)生任何影響,它會像往常一樣立即成功地打開 FIFO。只有當(dāng) FIFO 的另一端還沒有被打開的時候 O_NONBLOCK 標(biāo)記才會起作用,而具體產(chǎn)生的影響則依賴于打開 FIFO 是用于讀取還是用于寫入的:

如果打開 FIFO 是為了讀取,并且 FIFO 的寫入端當(dāng)前已經(jīng)被打開,那么 open() 調(diào)用會立即成功(就像 FIFO 的另一端已經(jīng)被打開一樣)

如果打開 FIFO 是為了寫入,并且還沒有打開 FIFO 的另一端來讀取數(shù)據(jù),那么 open() 調(diào)用會失敗,并將 errno 設(shè)置為 ENXIO

為讀取而打開 FIFO 和為寫入而打開 FIFO 時 O_NONBLOCK 標(biāo)記所起的作用不同是有原因的。當(dāng) FIFO 的另一個端沒有寫者時打開一個 FIFO 以便讀取數(shù)據(jù)是沒有問題的,因?yàn)槿魏卧噲D從 FIFO 讀取數(shù)據(jù)的操作都不會返回任何數(shù)據(jù)。但當(dāng)試圖向沒有讀者的 FIFO 中寫入數(shù)據(jù)時將會導(dǎo)致 SIGPIPE 信號的產(chǎn)生以及 write() 返回 EPIPE 錯誤。

在 FIFO 上調(diào)用 open() 的語義總結(jié)如下:

92f2eafc-bfe8-11ed-bfe3-dac502259ad0.png

在打開一個 FIFO 時,使用 O_NOBLOCK 標(biāo)記存在兩個目的:

它允許單個進(jìn)程打開一個 FIFO 的兩端,這個進(jìn)程首先會在打開 FIFO 時指定 O_NOBLOCK 標(biāo)記以便讀取數(shù)據(jù),接著打開 FIFO 以便寫入數(shù)據(jù)

它防止打開兩個 FIFO 的進(jìn)程之間產(chǎn)生死鎖

例如,下面的情況將會發(fā)生死鎖:

9318e81a-bfe8-11ed-bfe3-dac502259ad0.png

非阻塞 read() 和 write()

O_NONBLOCK 標(biāo)記不僅會影響 open() 的語義,而且還會影響——因?yàn)樵诖蜷_的文件描述中這個標(biāo)記仍然被設(shè)置著——后續(xù)的 read() 和 write() 調(diào)用的語義。

有些時候需要修改一個已經(jīng)打開的 FIFO(或另一種類型的文件)的 O_NONBLOCK 標(biāo)記的狀態(tài),具體存在這個需求的場景包括以下幾種:

使用 O_NONBLOCK 打開了一個 FIFO 但需要后續(xù)的 read() 和 write() 在阻塞模式下運(yùn)行

需要啟用從 pipe() 返回的一個文件描述符的非阻塞模式。更一般地,可能需要更改從除 open() 調(diào)用之外的其他調(diào)用中,如每個由 shell 運(yùn)行的新程序中自動被打開的三個標(biāo)準(zhǔn)描述符的其中一個或 socket() 返回的文件描述符,取得的任意文件描述符的非阻塞狀態(tài)

出于一些應(yīng)用程序的特殊需求,需要切換一個文件描述符的 O_NONBLOCK 設(shè)置的開啟和關(guān)閉狀態(tài)

當(dāng)碰到上面的需求時可以使用 fcntl() 啟用或禁用打開著的文件的 O_NONBLOCK 狀態(tài)標(biāo)記。通過下面的代碼(忽略的錯誤檢查)可以啟用這個標(biāo)記:

int flags;

flags = fcntl(fd, F_GETFL);
flags != O_NONBLOCK;
fcntl(fd, F_SETFL, flags);

通過下面的代碼可以禁用這個標(biāo)記:

flags = fcntl(fd, F_GETFL);
flags &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, flags);

管道和 FIFO 中 read() 和 write() 的語義

FIFO 上的 read() 操作:

9329d9d6-bfe8-11ed-bfe3-dac502259ad0.png

只有當(dāng)沒有數(shù)據(jù)并且寫入端沒有被打開時阻塞和非阻塞讀取之間才存在差別。在這種情況下,普通的 read() 會被阻塞,而非阻塞 read() 會失敗并返回 EAGAIN 錯誤。

當(dāng) O_NONBLOCK 標(biāo)記與 PIPE_BUF 限制共同起作用時 O_NONBLOCK 標(biāo)記對象管道或 FIFO 寫入數(shù)據(jù)的影響會變得復(fù)雜。

FIFO 上的 write() 操作:

934065b6-bfe8-11ed-bfe3-dac502259ad0.png

當(dāng)數(shù)據(jù)無法立即被傳輸時 O_NONBLOCK 標(biāo)記會導(dǎo)致在一個管道或 FIFO 上的 write() 失敗(錯誤是 EAGAIN)。這意味著當(dāng)寫入了 PIPE_BUF 字節(jié)之后,如果在管道或 FIFO 中沒有足夠的空間了,那么 write() 會失敗,因?yàn)閮?nèi)核無法立即完成這個操作并且無法執(zhí)行部分寫入,否則就會破壞不超過 PIPE_BUF 字節(jié)的寫入操作的原子性的要求

當(dāng)一次寫入的數(shù)據(jù)量超過 PIPE_BUF 字節(jié)時,該寫入操作無需是原子的。因此,write() 會盡可能多地傳輸字節(jié)(部分寫)以充滿管道或 FIFO。在這種情況下,從 write() 返回的值是實(shí)際傳輸?shù)淖止?jié)數(shù),并且調(diào)用者隨后必須要進(jìn)行重試以寫入剩余的字節(jié)。但如果管道或 FIFO 已經(jīng)滿了,從而導(dǎo)致哪怕連一個字節(jié)都無法傳輸了,那么 write() 會失敗并返回 EAGAIN 錯誤

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11292

    瀏覽量

    209329
  • fifo
    +關(guān)注

    關(guān)注

    3

    文章

    387

    瀏覽量

    43649
  • 命令
    +關(guān)注

    關(guān)注

    5

    文章

    683

    瀏覽量

    22011
  • 管道
    +關(guān)注

    關(guān)注

    3

    文章

    145

    瀏覽量

    17963
  • Shell
    +關(guān)注

    關(guān)注

    1

    文章

    365

    瀏覽量

    23357

原文標(biāo)題:Linux管道和FIFO應(yīng)用筆記

文章出處:【微信號:strongerHuang,微信公眾號:strongerHuang】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Linux匿名管道和命名管道的區(qū)別

    數(shù)據(jù)。對于管道傳輸?shù)臄?shù)據(jù)是無格式的流且大小受限。對于管道來說,也分為匿名管道和命名管道,其中命名管道也被叫做
    發(fā)表于 10-12 12:50 ?994次閱讀

    PRM及VTM并聯(lián)應(yīng)用 應(yīng)用筆記

    PRM及VTM并聯(lián)應(yīng)用 應(yīng)用筆記 此應(yīng)用筆記將介紹使用兩組PRM-AL和VTM做出更大功率而且均流的并聯(lián)步驟。如果應(yīng)用需并聯(lián)多過兩組PRM,請與應(yīng)用工程部聯(lián)絡(luò)。 [/hide]
    發(fā)表于 11-20 08:50

    Linux下進(jìn)程間通信方式-管道

    Linux下進(jìn)程間通信方式-管道分享到: 本文關(guān)鍵字: linux 管道通信,linux 進(jìn)程通信方式 ,無名
    發(fā)表于 08-29 15:29

    命名管道FIFO讀寫規(guī)則

    Linux命名管道FIFO的讀寫規(guī)則《Linux程序設(shè)計(第3版)》對于Linux命名管道的讀寫
    發(fā)表于 09-24 10:49

    linux系統(tǒng)中管道的介紹和線程同步代碼示例

    傳統(tǒng)的進(jìn)程間通信其中有無名管道(PIPE)、有名管道(FIFO)和信號(Signal)。咱們今天就說說linux中基于POSIX的有名管道(
    發(fā)表于 10-06 09:55

    FIFO——怎樣清掉管道中的數(shù)據(jù)

    您好! ? ? ? 我的開發(fā)平臺是DM6446. ? ? ? 關(guān)于Fifo_get();Fifo_put();請問在不刪除管道的情況下,怎樣清除管道里已有的數(shù)據(jù)?
    發(fā)表于 06-21 13:24

    FIFO中文應(yīng)用筆記

    FIFO中文應(yīng)用筆記
    發(fā)表于 07-28 10:03 ?30次下載
    <b class='flag-5'>FIFO</b>中文應(yīng)<b class='flag-5'>用筆記</b>

    MAXIM 應(yīng)用筆記

    電子專業(yè)常用芯片的芯片手冊——MAXIM 應(yīng)用筆記
    發(fā)表于 08-16 19:07 ?0次下載

    CAD實(shí)用筆記

    CAD實(shí)用筆記
    的頭像 發(fā)表于 03-20 14:07 ?6609次閱讀

    你不知道的Linux有名管道FIFO)的阻塞和非阻塞讀寫

    還有我們在/tmp目錄下通過ls -la命令可以看到管道文件myfifo的大小總是0,這是因?yàn)殡m然FIFO文件存在于文件系統(tǒng)中,但FIFO中的內(nèi)容都存放在內(nèi)存中,所以文件大小始終為0。
    發(fā)表于 05-04 17:17 ?4219次閱讀

    linux系統(tǒng)中的有名管道FIFO

    時,linux將不再保證寫入的原子性。FIFO緩沖區(qū)一有空閑區(qū)域,寫進(jìn)程就會試圖向管道寫入數(shù)據(jù),寫操作在寫完所有請求寫的數(shù)據(jù)后返回。 對于沒有設(shè)置阻塞標(biāo)志的寫操作: 當(dāng)要寫入的數(shù)據(jù)量
    發(fā)表于 04-02 14:45 ?403次閱讀

    SmartMesh IP應(yīng)用筆記

    SmartMesh IP應(yīng)用筆記
    發(fā)表于 04-21 19:16 ?8次下載
    SmartMesh IP應(yīng)<b class='flag-5'>用筆記</b>

    SmartMesh WirelessHART應(yīng)用筆記

    SmartMesh WirelessHART應(yīng)用筆記
    發(fā)表于 04-28 12:53 ?9次下載
    SmartMesh WirelessHART應(yīng)<b class='flag-5'>用筆記</b>

    AN-761應(yīng)用筆記

    AN-761應(yīng)用筆記
    發(fā)表于 05-24 09:48 ?8次下載
    AN-761應(yīng)<b class='flag-5'>用筆記</b>

    ADI應(yīng)用筆記合集

    超實(shí)用的應(yīng)用筆記合集
    發(fā)表于 02-18 15:36 ?14次下載
    主站蜘蛛池模板: 欧美午夜理伦三级在线观看| 牛牛在线视频| 青娱乐在线一区| 无码国产成人午夜在线观看不卡| 亚洲欧美人成视频在线| 北岛玲手机在线观看视频观看| 国产扒开美女双腿屁股流白浆| 日韩性xxx| 最近中文字幕2019国语4| 国产精品亚洲专区在线播放| 毛片手机在线观看| 亚洲国产av| 边摸边吃奶边做带声音| 久久性色AV亚洲电影无码| 无码欧美喷潮福利XXXX | 亚洲精品91| 高清撒尿hdtube撒尿| 老司机午夜影院试看区| 亚洲免费观看| 国产手机在线视频| 视频一区国产第一页| ca88亚洲城娱乐| 老师给美女同学开嫩苞| 野花韩国视频中文播放| 国产麻豆AV伦| 亚州免费一级毛片| 国产电影无码午夜在线播放| 日本视频久久| YY8090福利午夜理论片| 欧美z000z猪| adc免费观看| 欧美性情video sexo视频| 野花日本高清在线观看免费吗| 国产AV99激情久久无码天堂| 日本真人啪啪试看30秒| 吃胸亲吻吃奶摸下面免费视频| 日本乱子人伦在线视频| 99精品视频在线观看免费播放 | 国产高清视频免费最新在线 | 久久综合网久久综合| 真人做受120分钟免费看|