linux 重定向_Unix/Linux编程实践之IO重定向和管道
I/O重定向的原理模型
ls > test.file是如何工作的?shell是如何告訴程序把結果輸出到文件,而不是屏幕?
在who | sort > user.file中,shell是如何把一個進程的輸出連接到另一個進程的輸入的?
其實原理就是>操作符是把文件看成任意大小的和結構的變量。
下面兩個實現是等價的,其中前者用c實現,后者用shell實現:
等價于
prog_b?|?prog_a?>?x標準I/O
標準I/O的定義是什么?
所有的Unix I/O重定向都是基于標準數據流的原理,所有的Unix工具默認都會攜帶三個數據流:
標準輸入,輸出處理的數據流
標準輸出,結果數據流
標準錯誤處理,錯誤消息流
從上面兩個圖可以看出來,所有的Unix工具都是使用這種數據流模型。
這三種流的每一種都是一種特別文件描述符fd。
重定向的是shell
一般情況下,通過shell來運行一個程序時,該進程的stdin、stdout和stderr都是默認被連接到當前終端上。
所以用戶的輸入,程序的輸出或者錯誤消息都是顯示到屏幕終端。
重定向的是shell,而不是程序本身。
程序僅僅是持續不斷地和文件描述符0、1、2打交道,把fd和指定文件聯系起來的是shell。
也就是說shell本身并不會把重定向這個動作和指定文件傳遞給程序。
最低可用文件描述符
文件描述符是一個數組的索引號。
每個進程都有其打開的一組文件,這些打開的文件被保存在一個數組里面,fd就是某文件在此數組中的索引號。
當打開(open)一個文件的時候,內核為此文件安排的fd總是此數組中最低的可用位置的索引。
最低可用fd原則最后,庫函數isatty(fd),可以用來給一個進程判斷一個fd的指向,和系統調用fstat有關。
重定向的實現
根據上面說明的模型,可以設想一下如何寫一個程序來實現重定向的功能。
有很多種實現方法,下面以stdin定向到一個文件為例進行說明。
close-then-open策略
close-then-open策略,具體步驟如下:
close(0),把標準輸入的連接掛斷
fd = open(file_name,O_RDONLY), 和指定的文件建立一個連接,此時最低可用的fd應該是0
open-close-dup-close策略
open-close-dup-close策略,具體步驟如下:
fd = open(file_name,O_RDONLY), 和指定的文件建立一個連接
close(0)
newfd = dup(fd) ,copy open fd to 0,得到的newfd是0
close(fd)
open-dup2-close策略
把newfd = dup(fd)和close(0)組成成一個單獨的系統調用dup2,newfd=dup2(oldfd,newfd),一般使用的newfd設置為0。
shell為子進程重定向其輸出
以who>userlist.file為例子,shell運行who程序,并將who的輸出重定向到userlist.file。其中的原理是什么?
關鍵之處在于fork和exec的時間間隙。
fork之后,子進程準備執行exec。
shell就利用這個時間間隙來完成子進程的輸出重定向工作。
系統調用exec替換進程中運行的程序,但它不會改變進程的屬性、和進程中所有的連接。
文件描述符fd(復制了一個連接)、進程的用戶ID、進程的優先級都不會被系統調用exec改變。
這是因為打開的文件的并不是程序的代碼,也不是數據。fd是進程的一個屬性,故exec不能改變它們。
也就是文件描述符fd是可以被exec傳遞的,也會從父進程傳遞到子進程。
shell為子進程重定向其輸出具體流程如下:
父進程fork()
子進程繼承了父進程的stdout fd=1
子進程close(1)
子進程fd=create(file,0644),此時最低可用fd=1
子進程exec()
管道pipe
管道是內核中一個數據隊列,其每一端都連接著一個文件描述符fd,管道有一個讀取端和寫入端。
pipe又被稱為無名管道,只有共同父進程的進程之間才可以用pipe連接。原因是它沒有“名字”或者說是匿名的,另外的進程看不到它。
函數原型resutl=pipe(int array[2]),其中array[0]為讀取端的fd,array[1]為寫入端的fd。
管道的實現隱藏在內核中,進程只能看到兩個文件描述符fd。
pipe()和前面open()這些系統調用是類似的,都適用于最低可用fd原則。
那么一個進程創建一個pipe之后的效果圖是怎樣的?
可以發現一個進程剛創建一個管道成功的之后,一個pipe的兩端都是連著自己的。
一般而言,很少有進程利用管道向自己發送數據。
都是把pipe()和fork()結合起來,連接兩個不同的進程。
使用fork共享管道
一個進程剛創建一個管道成功的之后,調用fork()。那么子進程就擁有了兩個fd,分別執行該管道的兩端。
理論上,父子進程可以同時對該管道進行讀寫操作。
一般是一個讀,一個寫。
docker就是把fork、pipe和exec結合起來使用。
管道并非文件
一方面,管道和文件一樣,是不帶任何結構的字節序列,都可以使用read、write等操作。
另一方面,兩者還是存在不同之處的。
管道的讀取,必須要有進程往管道中寫入數據,否則會產生阻塞
多個讀者可能會引起麻煩,因為管道是一個隊列,讀取之后,數據就不存在了
寫進程會產生阻塞,直到管道有足夠的容量允許你寫入
若讀者在讀取數據,寫操作會失敗
如果兩個進程沒有關聯,使用FIFO聯系。
如果兩個進程處于不同的主機上面呢?這個時候把管道的思路擴展到套接字socket上。
pipe和fifo的本質區別
無名管道不屬于任何文件系統,只存在于內存中,它是無名無形的,但是可以把它看作一種特殊的文件,通過使用普通文件的read(),write()函數對管道進行操作。
為了使用fifo,LINUX中設立了一個專門的特殊文件系統--管道文件,它存在于文件系統中,任何進程可以在任何時候通過有名管道的路徑和文件名來訪問管道。但是在磁盤上的只是一個節點,而文件的數據則只存在于內存緩沖頁面中,與普通管道一樣。
fifo是基于VFS,對應的文件類型就是FIFO文件,可以通過mknod命令在磁盤上創建一個FIFO文件。
注意:這就是fifo與pipe的本質區別,pipe完全就是存在于內存中,在磁盤上毫無痕跡。
當進程想通過該FIFO來通信時就可以標準的API open打開該文件,然后開始讀寫操作。對于FIFO的讀寫實現,它與pipe是相同的。
區別在于,FIFO有open這一操作,而pipe是在調用pipe這個系統調用時直接創建了一對文件描述符用于通信。
并且,FIFO的open操作還有些細致的地方要考慮,例如如果寫者先打開,尚無讀者,那么肯定是不能通信了,所以就需要先去睡眠等待讀者打開該FIFO,反之對讀者亦然。
參考
Linux中的pipe與named pipe
總結
以上是生活随笔為你收集整理的linux 重定向_Unix/Linux编程实践之IO重定向和管道的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无法分配更多的internet句柄怎么回
- 下一篇: python networkx进行最短路