問題的需求
當我們需要把一個程序的輸出重定向到一個管道時,而由另一個程序從管道中讀取這些輸出時,自然想到的是下面辦法:
$ mkfifo mylog.fifo
$ /path/to/myprogram 1>mylog.fifo 2>&1
這個用法是有問題的:
程序myprogram的運行將會hang在第一個輸出語句,直到mylog.fifo里面的內(nèi)如被取走。換句話說,mylog.fifo的工作方式是阻塞的,即寫操作不會返回,直到寫的內(nèi)容已經(jīng)被讀取為止。
如果用戶能夠保證讀操作能實時健全的運行,不會阻塞對它的寫操作,那么這種模式也是可以工作的。但是這有很大風險,會阻擾主進程的正常功能,聽起來不是很完美。
那么能不能讓fifo有緩沖功能呢,即寫完立刻返回,只要fifo有足夠的buffer沒有被塞滿,而不需要等到被讀取,fifo自動緩沖寫進來的內(nèi)容,聽起來這個需求很合理。
fifo的非阻塞模式
實際上fifo是有非阻塞模式的:
- fifo可以工作在阻塞和非阻塞兩種模式。
- fifo還可以設(shè)置緩沖區(qū)(buffer)的大小。
這就很完美了,可是google了一番,這些開關(guān)模式只能在API里面通過對文件FD進行設(shè)置,例如open(..., mode),或者fnctl(fd, mode),而我們的使用場景是在shell里面通過重定向,此時fifo的打開和關(guān)閉都是有shell環(huán)境指定的,無法使用API來管理這些FD。
(關(guān)于program API設(shè)置fifo的工作模式,google一下資料很多,這里就不重復(fù)了)
shell模式下的非阻塞模式
目前找到一個辦法是在shell模式下,以READ-WRITE的方式打開fifo,這樣就能讓fifo工作在非阻塞模式(我不知道為什么):
$ exec 3<>/path/to/mylog.fifo
如果不想指定FD,而使用系統(tǒng)分配的FD(在bash 4.2上驗證通過):
$ exec {MYFD}<>/path/to/mylog.fifo
$ echo ${MYFD}
這條命令的功能是以讀寫的方式打開文件/path/to/mylog.fifo,并把fd=3分配這個文件,后面可以用fd=3來對這個文件進行操作。補充一下在linux進程的FD表是可以繼承的,即fork出來的子進程自動復(fù)制父進程的FD表,也就是在在當前shell下面起來的所有子進程都可以使用fd=3這個文件描述符。
這里比較有趣的一點是,F(xiàn)IFO的打開操作不必要和FIFO的使用在同一個進程里面,也不必是父子進程關(guān)系(考慮到子進程自動復(fù)制父進程的FD表);即FIFO的打開操作可以在任何一個SHELL里面,然后FIFO的使用在任意其它SHELL里面。舉例來說,Terminal 1以讀寫方式打開FIFO,然后Terminal 2往FIFO里面寫數(shù)據(jù),最后Terminal 3從FIFO里面讀取數(shù)據(jù),這種情況下Terminal 2的寫操作也是非阻塞的,但是Terminal 1必須保持,不能退出,否則FD會被關(guān)閉;一旦FD關(guān)閉,F(xiàn)IFO又重新變回阻塞方式。
查看當前進程fd列表會看到一條
$ lsof -f -p $$
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
...
bash 2347 <uid> 3u FIFO 249,0 0t0 51477762 /path/to/mylog.fifo
fd的值是3,指向fifo文件/path/to/mylog.fifo
再來測試用戶程序的讀寫操作
$ /path/to/myprogram 1>/path/to/mylog.fifo 2>&1
或者,直接使用fd,而不是文件名
$ /path/to/myprogram 1>&3 2>&1
此時myprogram就不會被阻塞在第一條output語句上,fifo緩存的輸出的內(nèi)容;不過也要注意不能把fifo的緩沖區(qū)寫滿;如果寫滿了,寫操作會繼續(xù)阻塞,直到fifo緩沖區(qū)釋放出新的空間。
最后我們關(guān)閉fifo對應(yīng)的fs
$ exec 3<&- # or, exec {MYFD}<&-
$ lsof -f -p $$