概述
管道最常見的地方是shell中,比如:
$ ls | wc -l
為了執(zhí)行上面的命令,shell創(chuàng)建了兩個進程來分別執(zhí)行 ls 和 wc (通過 fork() 和 exec() 完成),如下:
從上圖可以看出,可以將管道看成是一組水管,它允許數(shù)據(jù)從一個進程流向另一個進程,這也是管道名稱的由來。
從上圖可以看出,由兩個進程連接到了管道上,這樣寫入進程 ls 就將其標準輸出(文件描述符為1)連接到來管道的寫入段,讀取進程 wc 就將其標準輸入(文件描述符為0)連接到管道的讀取端。實際上,這兩個進程并不知道管道的存在,它們只是從標準文件描述符中讀取和寫入數(shù)據(jù)。shell 必須要完成相關的工作。
一個管道是一個字節(jié)流
管道是一個字節(jié)流,即在使用管道時是不存在消息或者消息邊界的概念的:
從管道中讀取數(shù)據(jù)的進程可以讀取任意大小的數(shù)據(jù)塊,而不管寫入進程寫入管道的數(shù)據(jù)塊的大小是什么
通過管道傳遞的數(shù)據(jù)是順序的,從管道中讀取出來的字節(jié)的順序與它們被寫入管道的順序是完全一樣的,在管道中無法使用 lseek() 來隨機的訪問數(shù)據(jù)
如果需要在管道中實現(xiàn)離散消息的概念,那么就必須要在應用程序中完成這些工作。雖然這是可行的,但如果碰到這種需求的話最好使用其他 IPC 機制,如消息隊列和數(shù)據(jù)報 socket。
從管道中讀取數(shù)據(jù)
試圖從一個當前為空的管道中讀取數(shù)據(jù)將會被阻塞直至至少有一個字節(jié)被寫入到管道中為止。
如果管道的寫入端被關閉了,那么從管道中讀取數(shù)據(jù)的進程在讀完管道中剩余的所有數(shù)據(jù)之后將會看到文件結束(即 read() 返回 0)。
管道是單向的
在管道中數(shù)據(jù)的傳遞方向是單向的。管道的一端用于寫入,另一端則用于讀取。
在其他一些 UNIX 實現(xiàn)上,特別是那些從 System V Release 4 演化而來的系統(tǒng),管道是雙向的(所謂的流管道)。雙向管道并沒有在任何 UNIX 標準中進行規(guī)定,因此即使在提供了雙向管道的實現(xiàn)上最好也避免依賴這種語義。作為替代方案,可以使用 UNIX domain 流 socket 對(通過 socketpair() 系統(tǒng)調用來創(chuàng)建),它提供了一種標準的雙向通信機制,并且其語義與流管道是等價的。
可以確保寫入不超過 PIPE_BUF 字節(jié)的操作是原子的
如果多個進程寫入同一個管道,那么如果它們在一個時刻寫入的數(shù)據(jù)量不超過 PIPE_BUF 字節(jié),那么就可以確保寫入的數(shù)據(jù)不會發(fā)生相互混合的情況。
SUSv3 要求 PIPE_BUF 至少為 _POSIX_PIPE_BUF(512)。一個實現(xiàn)應該定義 PIPE_BUF(在
當寫入管道的數(shù)據(jù)塊的大小超過了 PIPE_BUF 字節(jié),那么內核可能會將數(shù)據(jù)分割成幾個較小的片段來傳輸,在讀者從管道中消耗數(shù)據(jù)時再附加上后繼的數(shù)據(jù)(write()調用會阻塞直到所有數(shù)據(jù)被寫入到管道為止)
當只有一個進程向管道寫入數(shù)據(jù)時(通常的情況),PIPE_BUF 的取值就沒有關系了
但如果有多個寫入進程,那么大數(shù)據(jù)塊的寫入可能會被分解成任意大小的段(可能會小于 PIPE_BUF 字節(jié)),并且可能會出現(xiàn)與其他進程寫入的數(shù)據(jù)交叉的現(xiàn)象
只有在數(shù)據(jù)被傳輸?shù)焦艿赖臅r候 PIPE_BUF 限制才會起作用。當寫入的數(shù)據(jù)達到 PIPE_BUF 字節(jié)時,write() 會在必要的時候阻塞知道管道中的可用空間足以原子的完成此操作。如果寫入的數(shù)據(jù)大于 PIPE_BUF 字節(jié),那么 write() 會盡可能的多傳輸數(shù)據(jù)以充滿整個管道,然后阻塞直到一些讀取進程從管道中移除了數(shù)據(jù)。如果此類阻塞的 write() 被一個信號處理器中斷了,那么這個調用會被解除阻塞并返回成功傳輸?shù)焦艿乐械淖止?jié)數(shù),這個字節(jié)數(shù)會少于請求寫入的字節(jié)數(shù)(所謂的部分寫入)。
管道的容量是有限的
管道其實是一個在內核內存中維護的緩沖器,這個緩沖器的存儲能力是有限的。一旦管道被填滿之后,后繼向管道的寫入操作就會被阻塞直到讀者從管道中移除了一些數(shù)據(jù)為止。
SUSv3 并沒有規(guī)定管道的存儲能力。在早于 2.6.11 的 Linux 內核中,管道的存儲能力與系統(tǒng)頁面的大小是一致的(如在 x86-32 上是 4096 字節(jié)),而從 Linux 2.6.11 起,管道的存儲能力是 65,536 字節(jié)。其他 UNIX 實現(xiàn)上的管道的存儲能力可能是不同的。
一般來講,一個應用程序無需知道管道的實際存儲能力。如果需要防止寫者進程阻塞,那么從管道中讀取數(shù)據(jù)的進程應該被設計成以盡可能快的速度從管道中讀取數(shù)據(jù)。
創(chuàng)建和使用管道
#includeint pipe(int fd[2]);
pipe() 創(chuàng)建一個新管道
成功的調用在數(shù)組 fd 中返回兩個打開的文件描述符,一個表示管道的讀取端 fd[0],一個表示管道的寫入端 fd[1]
調用 pipe() 函數(shù)時,首先在內核中開辟一塊緩沖區(qū)用于通信,它有一個讀端和一個寫端,然后通過 fd 參數(shù)傳出給用戶進程兩個文件描述符,fd[0] 指向管道的讀端,fd[1] 指向管道的寫段。
不要用 fd[0] 寫數(shù)據(jù),也不要用 fd[1] 讀數(shù)據(jù),其行為未定義的,但在有些系統(tǒng)上可能會返回 -1 表示調用失敗。數(shù)據(jù)只能從 fd[0] 中讀取,數(shù)據(jù)也只能寫入到fd[1],不能倒過來。
與所有文件描述符一樣,可以使用 read() 和 write() 系統(tǒng)調用來在管道上執(zhí)行 IO,一旦向管道的寫入端寫入數(shù)據(jù)之后立即就能從管道的讀取端讀取數(shù)據(jù)。管道上的 read() 調用會讀取的數(shù)據(jù)量為所請求的字節(jié)數(shù)與管道中當前存在的字節(jié)數(shù)兩者之間的較小值。當管道為空時,讀取操作阻塞。
也可以在管道上使用 stdio 函數(shù)(printf()、scanf() 等),只需要首先使用 fdopen() 獲取一個與 filedes 中的某個描述符對應的文件流即可。但在這樣做的時候需要解決 stdio 緩沖問題。
管道可以用于進程內部自己通信:
管道可以用于親緣關系(子進程會繼承父進程中的文件描述符的副本)進程中通信:
不建議將單個 pipe 用作全雙工的,或者不關閉用作半雙工而不關閉相應的讀端/寫端,這樣很可能導致死鎖:如果兩個進程同時試圖從管道中讀取數(shù)據(jù),那么就無法確定哪個進程會首先讀取成功,從而產生兩個進程競爭數(shù)據(jù)了。要防止這種競爭情況的出現(xiàn)就需要使用某種同步機制。這時,就需要考慮死鎖問題了,因為如果兩個進程都試圖從空管道中讀取數(shù)據(jù)或者嘗試向已滿的管道中寫入數(shù)據(jù)就可能會發(fā)生死鎖。
如果我們想要一個雙向數(shù)據(jù)流時,可以創(chuàng)建兩個管道,每個方向一個。
管道允許相關進程間的通信
其實管道可以用于任意兩個甚至更多相關進程之間的通信,只要在創(chuàng)建子進程的系列 fork() 調用之前通過一個共同的祖先進程創(chuàng)建管道即可。
關閉未使用管道文件描述符
關閉未使用管道文件描述符不僅僅是為了確保進程不會消耗盡其文件描述符的限制。
從管道中讀取數(shù)據(jù)的進程會關閉其持有的管道的寫入描述符,這樣當其他進程完成輸出并關閉其寫入描述符之后,讀者就能夠看到文件結束。反之,如果讀取的進程沒有關閉管道的寫入端,那么在其他進程關閉了寫入描述符之后,即使讀者已經讀完了管道中的所有數(shù)據(jù),也不會看到文件結束。因為此時內核知道至少還有一個管道的寫入描述符打開著,從而導致 read() 阻塞。
當一個進程視圖向一個管道中寫入數(shù)據(jù)但沒有任何進程擁有該管道的打開著的讀取描述符時,內核會向寫入進程發(fā)送一個 SIGPIPE 信號,默認情況下,這個信號將會殺死進程,但進程可以選擇忽略或者設置信號處理器,這樣 write() 將因為 EPIPE 錯誤而失敗。收到 SIGPIPE 信號和得到 EPIPE 錯誤對于標識管道的狀態(tài)是有意義的,這就是為什么需要關閉管道的未使用讀取描述符的原因。如果寫入進程沒有關閉管道的讀取端,那么即使在其他進程已經關閉了管道的讀取端之后,寫入進程仍然能夠向管道寫入數(shù)據(jù),最后寫入進程會將數(shù)據(jù)充滿整個管道,后續(xù)的寫入請求會將永遠阻塞。
使用管道連接過濾器
當管道被創(chuàng)建之后,為管道的兩端分配的文件描述符是可用描述符中數(shù)值最小的兩個,由于通常情況下,進程已經使用了描述符 0,1,2,因此會為管道分配一些數(shù)值更大的描述符。如果需要使用管道連接兩個過濾器(即從 stdin 讀取和寫入到 stdout),使得一個程序的標準輸出被重定向到管道中,就需要采用復制文件描述符技術。
int pfd[2]; pipe(pfd); close(STDOUT_FILENO); dup2(pfd[1],STDOUT_FILENO);
上面這些調用的最終結果是進程的標準輸出被綁定到管道的寫入端,而對應的一組調用可以用來將進程的標準的輸入綁定到管道的讀取端上。
通過管道與 shell 命令進行通信: popen()
#includeFILE *popen (const char *command, const char *mode);
pipe() 和 close() 是最底層的系統(tǒng)調用,它的進一步封裝是 popen() 和 pclose()
popen()函數(shù)創(chuàng)建了一個管道,然后創(chuàng)建了一個子進程來執(zhí)行 shell,而 shell 又創(chuàng)建了一個子進程來執(zhí)行command字符串
mode 參數(shù)是一個字符串:
它確定調用進程是從管道中讀取數(shù)據(jù)(mode 是 r)還是將數(shù)據(jù)寫入到管道中(mode 是 w)
由于管道是向的,因此無法在執(zhí)行的 command 中進行雙向通信
mode 的取值確定了所執(zhí)行的命令的標準輸出是連接到管道的寫入端還是將其標準輸入連接到管道的讀取端
popen() 在成功時會返回可供 stdio 庫函數(shù)使用的文件流指針。當發(fā)生錯誤時,popen() 會返回 NULL 并設置 errno 以標示出發(fā)生錯誤的原因
在 popen() 調用之后,調用進程使用管道來讀取 command 的輸出或使用管道向其發(fā)送輸入。與使用 pipe() 創(chuàng)建的管道一樣,當從管道中讀取數(shù)據(jù)時,調用進程在 command 關閉管道的寫入端之后會看到文件結束;當向管道寫入數(shù)據(jù)時,如果 command 已經關閉了管道的讀取端,那么調用進程就會收到 SIGPIPE 信號并得到 EPIPE 錯誤
#includeint pclose ( FILE * stream);
一旦IO結束之后可以使用 pclose() 函數(shù)關閉管道并等待子進程中的 shell 終止(不應該使用 fclose() 函數(shù),因為它不會等待子進程。)
pclose() 在成功時會返回子進程中 shell 的終止狀態(tài)(即 shell 所執(zhí)行的最后一條命令的終止狀態(tài),除非 shell 是被信號殺死的)
和 system() 一樣,如果無法執(zhí)行shell,那么 pclose() 會返回一個值就像子進程中的 shell 通過調用 _exit(127) 來終止一樣
如果發(fā)生了其他錯誤,那么 pclose() 返回 ?1。其中可能發(fā)生的一個錯誤是無法取得終止狀態(tài)
當執(zhí)行等待以獲取子進程中 shell 的狀態(tài)時,SUSv3 要求 pclose() 與 system() 一樣,即在內部的 waitpid() 調用被一個信號處理器中斷之后自動重啟該調用。
與 system() 一樣,在特權進程中永遠都不應該使用 popen()。
popen優(yōu)缺點:
優(yōu)點:在 Linux 中所有的參數(shù)擴展都是由 shell 來完成的。所以在啟動 command 命令之前程序先啟動 shell 來分析 command 字符串,就可以使用各種 shell 擴展(比如通配符),這樣我們可以通過 popen() 調用非常復雜的 shell 命令
缺點:對于每個 popen() 調用,不僅要啟動一個被請求的程序,還需要啟動一個 shell。即每一個 popen() 將啟動兩個進程。從效率和資源的角度看,popen() 函數(shù)的調用比正常方式要慢一些
pipe() VS popen()
pipe()是一個底層調用,popen() 是一個高級的函數(shù)
pipe() 單純的創(chuàng)建管道,而 popen() 創(chuàng)建管道的同時 fork() 子進程
popen() 在兩個進程中傳遞數(shù)據(jù)時需要調用 shell 來解釋請求命令;pipe() 在兩個進程中傳遞數(shù)據(jù)不需要啟動 shell 來解釋請求命令,同時提供了對讀寫數(shù)據(jù)的更多控制(popen() 必須時 shell 命令,pipe() 則無硬性要求)
popen() 函數(shù)是基于文件流(FILE)工作的,而 pipe() 是基于文件描述符工作的,所以在使用 pipe() 后,數(shù)據(jù)必須要用底層的read() 和 write() 調用來讀取和發(fā)送
管道和 stdio 緩沖
由于 popen() 調用返回的文件流指針沒有引用一個終端,因此 stdio 庫會對這種流應用塊緩沖。這意味著當 mode 的值為 w 來調用 popen() 時,默認情況下只有當 stdio 緩沖區(qū)被充滿或者使用 pclose() 關閉了管道之后才會被發(fā)送到管道的另一端的子進程。在很多情況下,這種處理方式是不存在問題的。但如果需要確保子進程能夠立即從管道中接收數(shù)據(jù),那么就需要定期調用 fflush() 或使用 setbuf(fp, NULL) 調用禁用 stdio 緩沖。當使用 pipe() 系統(tǒng)調用創(chuàng)建管道,然后使用 fdopen() 獲取一個與管道的寫入端對應的 stdio 流時也可以使用這項技術
如果調用 popen() 的進程正在從管道中讀取數(shù)據(jù)(即 mode 是 r),那么事情就不是那么簡單了。在這樣情況下如果子進程正在使用 stdio 庫,那么——除非它顯式地調用了 fflush() 或 setbuf() ,其輸出只有在子進程填滿 stdio 緩沖器或調用了 fclose() 之后才會對調用進程可用。(如果正在從使用 pipe() 創(chuàng)建的管道中讀取數(shù)據(jù)并且向另一端寫入數(shù)據(jù)的進程正在使用 stdio 庫,那么同樣的規(guī)則也是適用的。)如果這是一個問題,那么能采取的措施就比較有限的,除非能夠修改在子進程中運行的程序的源代碼使之包含對 setbuf() 或 fflush() 調用。
如果無法修改源代碼,那么可以使用偽終端來替換管道。一個偽終端是一個 IPC 通道,對進程來講它就像是一個終端。其結果是 stdio 庫會逐行輸出緩沖器中的數(shù)據(jù)。
命名管道(FIFO)
上述管道雖然實現(xiàn)了進程間通信,但是它具有一定的局限性:
匿名管道只能是具有血緣關系的進程之間通信
它只能實現(xiàn)一個進程寫另一個進程讀,而如果需要兩者同時進行時,就得重新打開一個管道
為了使任意兩個進程之間能夠通信,就提出了命名管道(named pipe 或 FIFO):
FIFO 與管道的區(qū)別:FIFO 在文件系統(tǒng)中擁有一個名稱,并且其打開方式與打開一個普通文件一樣,能夠實現(xiàn)任何兩個進程之間通信。而匿名管道對于文件系統(tǒng)是不可見的,它僅限于在父子進程之間的通信
一旦打開了 FIFO,就能在它上面使用與操作管道和其他文件的系統(tǒng)調用一樣的 IO 系統(tǒng)調用 read(),write(),close()。與管道一樣,F(xiàn)IFO 也有一個寫入端和讀取端,并且總是遵循先進先出的原則,即第一個進來的數(shù)據(jù)會第一個被讀走
與管道一樣,當所有引用 FIFO 的描述符都關閉之后,所有未被讀取的數(shù)據(jù)都將被丟棄
使用 mkfifo 命令可以在 shell 中創(chuàng)建一個 FIFO:
mkfifo [-m mode] pathname
pathname 是創(chuàng)建的 FIFO 的名稱,-m 選項指定權限 mode,其工作方式與 chmod 命令一樣
fstat() 和 stat() 函數(shù)會在 stat 結構的 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 的權限,這些權限會按照進程的 umask 值來取掩碼
一旦創(chuàng)建了 FIFO,任何進程都能夠打開它,只要它通過常規(guī)的文件權限檢測
使用 FIFO 時唯一明智的做法是在兩端分別設置一個讀取進程和一個寫入進程。這樣在默認情況下,打開一個 FIFO 以便讀取數(shù)據(jù)(open() O_RDONLY 標記)將會阻塞直到另一個進程打開 FIFO 以寫入數(shù)(open() O_WRONLY 標記)為止。相應地,打開一個 FIFO 以寫入數(shù)據(jù)將會阻塞直到另一個進程打開 FIFO 以讀取數(shù)據(jù)為止。換句話說,打開一個 FIFO 會同步讀取進程和寫入進程。如果一個 FIFO 的另一端已經打開(可能是因為一對進程已經打開了 FIFO 的兩端),那么open() 調用會立即成功。
在大多數(shù) Unix 實現(xiàn)上(包含 Linux),當打開一個 FIFO 時可以通過指定 O_RDWR 標記來繞過打開 FIFO 時的阻塞行為。這樣,open() 會立即返回,但無法使用返回的文件描述符在 FIFO 上讀取和寫入數(shù)據(jù)。這種做法破壞了 FIFO 的 IO 模型,SUSv3 明確指出以 O_RDWR 標記打開一個 FIFO 的結果是未知的,因此出于可移植性的原因,開發(fā)人員不應該使用這項技術。對于那些需要避免在打開 FIFO 時發(fā)生阻塞的需求,open() 的 O_NONBLOCK 標記提供了一種標準化的方法來完成這個任務:
open(const char *path, O_RDONLY | O_NONBLOCK); open(const char *path, O_WRONLY | O_NONBLOCK);
在打開一個 FIFO 時避免使用 O_RDWR 標記還有另外一個原因,當采用那種方式調用 open() 之后,調用進程在從返回的文件描述符中讀取數(shù)據(jù)時永遠都不會看到文件結束,因為永遠都至少存在一個文件描述符被打開著以等待數(shù)據(jù)被寫入 FIFO,即進程從中讀取數(shù)據(jù)的那個描述符。
使用 FIFO 和 tee 創(chuàng)建雙重管道線
shell 管道線的其中一個特征是它們是線性的,管道線中的每個進程都能讀取前一個進程產生的數(shù)據(jù)并將數(shù)據(jù)發(fā)送到其后一個進程中,使用 FIFO 就能夠在管道線中創(chuàng)建子進程,這樣除了將一個進程的輸出發(fā)送給管道線中的后面一個進程之外,還可以復制進程的輸出并將數(shù)據(jù)發(fā)送到另一個進程中,要完成這個任務就需要使用 tee 命令,它將其從標準輸入中讀取到的數(shù)據(jù)復制兩份并輸出:一份寫入標準輸出,另一份寫入到通過命令行參數(shù)指定的文件中。
mkfifo myfifo wc -l < myfifo & ls -l | tee myfifo | sort -k5n
非阻塞 IO
當一個進程打開一個 FIFO 的一端時,如果 FIFO 的另一端還沒有被打開,那么該進程會被阻塞。但有些時候阻塞并不是期望的行為,而這可以通過在調用 open() 時指定 O_NONBLOCK 標記來實現(xiàn)。
如果 FIFO 的另一端已經被打開,那么 O_NONBLOCK 對 open() 調用不會產生任何影響,它會像往常一樣立即成功地打開 FIFO。只有當 FIFO 的另一端還沒有被打開的時候 O_NONBLOCK 標記才會起作用,而具體產生的影響則依賴于打開 FIFO 是用于讀取還是用于寫入的:
如果打開 FIFO 是為了讀取,并且 FIFO 的寫入端當前已經被打開,那么 open() 調用會立即成功(就像 FIFO 的另一端已經被打開一樣)
如果打開 FIFO 是為了寫入,并且還沒有打開 FIFO 的另一端來讀取數(shù)據(jù),那么 open() 調用會失敗,并將 errno 設置為 ENXIO
為讀取而打開 FIFO 和為寫入而打開 FIFO 時 O_NONBLOCK 標記所起的作用不同是有原因的。當 FIFO 的另一個端沒有寫者時打開一個 FIFO 以便讀取數(shù)據(jù)是沒有問題的,因為任何試圖從 FIFO 讀取數(shù)據(jù)的操作都不會返回任何數(shù)據(jù)。但當試圖向沒有讀者的 FIFO 中寫入數(shù)據(jù)時將會導致 SIGPIPE 信號的產生以及 write() 返回 EPIPE 錯誤。
在 FIFO 上調用 open() 的語義總結如下:
在打開一個 FIFO 時,使用 O_NOBLOCK 標記存在兩個目的:
它允許單個進程打開一個 FIFO 的兩端,這個進程首先會在打開 FIFO 時指定 O_NOBLOCK 標記以便讀取數(shù)據(jù),接著打開 FIFO 以便寫入數(shù)據(jù)
它防止打開兩個 FIFO 的進程之間產生死鎖
例如,下面的情況將會發(fā)生死鎖:
非阻塞 read() 和 write()
O_NONBLOCK 標記不僅會影響 open() 的語義,而且還會影響——因為在打開的文件描述中這個標記仍然被設置著——后續(xù)的 read() 和 write() 調用的語義。
有些時候需要修改一個已經打開的 FIFO(或另一種類型的文件)的 O_NONBLOCK 標記的狀態(tài),具體存在這個需求的場景包括以下幾種:
使用 O_NONBLOCK 打開了一個 FIFO 但需要后續(xù)的 read() 和 write() 在阻塞模式下運行
需要啟用從 pipe() 返回的一個文件描述符的非阻塞模式。更一般地,可能需要更改從除 open() 調用之外的其他調用中,如每個由 shell 運行的新程序中自動被打開的三個標準描述符的其中一個或 socket() 返回的文件描述符,取得的任意文件描述符的非阻塞狀態(tài)
出于一些應用程序的特殊需求,需要切換一個文件描述符的 O_NONBLOCK 設置的開啟和關閉狀態(tài)
當碰到上面的需求時可以使用 fcntl() 啟用或禁用打開著的文件的 O_NONBLOCK 狀態(tài)標記。通過下面的代碼(忽略的錯誤檢查)可以啟用這個標記:
int flags; flags = fcntl(fd, F_GETFL); flags != O_NONBLOCK; fcntl(fd, F_SETFL, flags);
通過下面的代碼可以禁用這個標記:
flags = fcntl(fd, F_GETFL); flags &= ~O_NONBLOCK; fcntl(fd, F_SETFL, flags);
管道和 FIFO 中 read() 和 write() 的語義
FIFO 上的 read() 操作:
只有當沒有數(shù)據(jù)并且寫入端沒有被打開時阻塞和非阻塞讀取之間才存在差別。在這種情況下,普通的 read() 會被阻塞,而非阻塞 read() 會失敗并返回 EAGAIN 錯誤。
當 O_NONBLOCK 標記與 PIPE_BUF 限制共同起作用時 O_NONBLOCK 標記對象管道或 FIFO 寫入數(shù)據(jù)的影響會變得復雜。
FIFO 上的 write() 操作:
當數(shù)據(jù)無法立即被傳輸時 O_NONBLOCK 標記會導致在一個管道或 FIFO 上的 write() 失敗(錯誤是 EAGAIN)。這意味著當寫入了 PIPE_BUF 字節(jié)之后,如果在管道或 FIFO 中沒有足夠的空間了,那么 write() 會失敗,因為內核無法立即完成這個操作并且無法執(zhí)行部分寫入,否則就會破壞不超過 PIPE_BUF 字節(jié)的寫入操作的原子性的要求
當一次寫入的數(shù)據(jù)量超過 PIPE_BUF 字節(jié)時,該寫入操作無需是原子的。因此,write() 會盡可能多地傳輸字節(jié)(部分寫)以充滿管道或 FIFO。在這種情況下,從 write() 返回的值是實際傳輸?shù)淖止?jié)數(shù),并且調用者隨后必須要進行重試以寫入剩余的字節(jié)。但如果管道或 FIFO 已經滿了,從而導致哪怕連一個字節(jié)都無法傳輸了,那么 write() 會失敗并返回 EAGAIN 錯誤
審核編輯:湯梓紅
-
Linux
+關注
關注
87文章
11345瀏覽量
210398 -
fifo
+關注
關注
3文章
389瀏覽量
43858 -
命令
+關注
關注
5文章
696瀏覽量
22111 -
管道
+關注
關注
3文章
145瀏覽量
18023 -
Shell
+關注
關注
1文章
366瀏覽量
23447
原文標題:Linux管道和FIFO應用筆記
文章出處:【微信號:strongerHuang,微信公眾號:strongerHuang】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論