文件描述符是操做系統的資源,對於實體文件來講,每打開一次文件,操做系統都會爲該進程分配一個文件描述符來關聯(指向)這個文件,之後操做文件數據都根據這個文件描述符來操做,而不是文件名。就像對文件句柄的操做同樣。shell
實際上,文件句柄、文件描述符和實體文件的關係存在層次上的關係。文件句柄指向文件描述符,文件描述符指向實體文件結構。以下圖:bash
(圖注:fd是用戶空間的內容,圖中放在內核層是爲了歸納與之關聯的內核層的幾個結構:fd對應內核層的這幾個結構)函數
正如圖中所示,文件句柄是文件描述符的更上層封裝,文件句柄指向文件描述符,且多個文件句柄還能夠指向同一個文件描述符。一樣地,多個文件描述符能夠指向同一個實體文件。實際上,從文件到文件描述符,是採用引用計數的方式的,表示有多少個文件描述符還關聯在這個文件上。同理,文件描述符到文件句柄,也是使用引用計數方式的,表示有多少文件句柄指向這個文件描述符。編碼
使用引用計數的特色之一就是只有引用數爲0以後才表示關閉/刪除/釋放行爲。例如,關閉文件句柄只是在文件描述符上引用數減一,而不是真的關閉文件描述符,直到文件描述符上的文件句柄引用數爲0以後,這個文件描述符纔會被關閉。同理,關閉文件描述符只是對文件結構的引用計數減1,直到這個文件結構的全部文件描述符都關閉了,才表示釋放這個文件結構。操作系統
由於文件句柄是文件描述符的上層封裝,因此文件句柄比文件描述符的功能多一些。實際上,從文件描述符到實體文件,中間的數據傳輸是純裸數據流,不會有緩衝行爲(固然,從操做系統的角度上看,有文件系統層的IO緩衝或操做系統層的緩衝,如page cache、buffer cache)。而文件句柄到文件描述符,中間有好幾個IO層次,例如編碼層(utf8)、換行符層(raw/crlf)、標準IO庫層(stdio/perlio)、最底層(unix)。以下圖:unix
(圖注:fd是用戶空間的內容,圖中放在內核層是爲了歸納與之關聯的內核層的幾個結構:fd對應內核層的這幾個結構。但page cache是內核空間的)rest
其中標準IO庫層用來提供IO buffer層,stdio是操做系統提供的標準IO庫,perlio是Perl提供的標準IO庫,在Perl中能夠選擇使用哪一種IO庫提供buffer。unix是perl io的最底層(就算是在win下也是unix層),它幾乎等價於直接操做文件描述符,至關因而純裸數據,沒有IO buffer。code
模塊PerlIO::Layers提供了Perl在文件描述符到文件句柄的IO層次上的一些檢測功能,例如檢測文件句柄是否已打開,是否設置了autoflush,是否使用緩衝等等。對象
另外,從文件句柄到文件描述符中間的IO Buffer中的數據,能夠經過perl IO::Handle->flush()
來刷,flush時,io buffer中未讀數據被丟棄,未寫數據將寫入到文件描述符。從文件描述符到設備文件(如磁盤上的abc.log)中間的緩衝(如page cache),能夠經過操做系統的fsync()系統調用或perl IO::Handle->sync()
來刷盤。blog
在bash shell中常常見到的>file 2>&1
,它表示將標準錯誤、標準輸出都重定向到file文件中。這裏的過程是將標準輸入重定向到file文件,而後duplicate文件描述符fd=1獲得fd=2,使得fd=2也指向fd=1對應的文件(即file),從而使得標準錯誤、標準輸出都輸出到file中。
除了重定向、文件描述符的duplicate,bash shell還支持文件描述符的手動打開(分配文件描述符)、移動、關閉。
Perl固然也支持相似的重定向和duplcate,並且不只支持文件描述符級別的,還支持更上層別文件句柄級,不管是duplicate文件句柄仍是duplicate文件描述符,都會生成新的文件描述符。另外,duplicate的對象是文件句柄時,不會將IO Buffer中的內容也duplicate,也就是說新的文件句柄中沒有緩衝任何數據。
在Perl中,能夠在open時在>、>>、<、+>、+>>、+<
的後面加上符號&
,這就表示文件句柄或文件描述符的duplicate。給文件句柄就是文件句柄的duplicate,給數值就是文件描述符的duplicate。open能夠是兩參數的或三參數的,三參數時,能夠是文件句柄、文件句柄的引用(即\*FILEHANDLE
格式),能夠是文件描述符數值。若是須要獲取文件句柄指向的文件描述符,可使用fileno FILEHANDLE
函數來獲取。
例如,下面將STDOUT文件句柄duplicate一份獲得NEWOUT,使得NEWOUT也指向標準輸出,即向NEWOUT寫入數據時也會出如今屏幕上(默認)。
# 兩參數或三參數的文件句柄duplicate open NEWOUT, ">&STDOUT"; open NEWOUT, ">&", "STDOUT"; open NEWOUT, ">&", "\*STDOUT"; # 三參數的文件描述符duplicate open NEWOUT, ">&", fileno STDOUT;
按照上面的duplicate過程,結果以下圖:
(圖注:fd是用戶空間的內容,圖中放在內核層是爲了歸納與之關聯的內核層的幾個結構:fd對應內核層的這幾個結構)
在duplicate時,所選的模式必定要匹配源文件句柄的模式。例如STDOUT是可寫不可讀(write-only)的文件句柄,在duplicate STDOUT時,就必須只能選擇可寫不可讀的>&
模式。duplicate後,新的文件句柄或文件描述符和源文件句柄/文件描述符的讀、寫模式是徹底同樣的。
下面是將STDOUT複製多份的示例:
#!/usr/bin/perl # use strict; use warnings; use 5.010; open NEWOUT, ">&STDOUT" or die "duplicate1 failed: $!"; say NEWOUT "hello world1, fd=", fileno NEWOUT; open NEWOUT1, ">&", "NEWOUT" or die "duplicate2 failed: $!"; say NEWOUT1 "hello world2, fd=", fileno NEWOUT1; open NEWOUT2, ">&", "\*NEWOUT" or die "duplicate3 failed: $!"; say NEWOUT2 "hello world3, fd=", fileno NEWOUT2; open NEWOUT3, ">&", fileno NEWOUT or die "duplicate4 failed: $!"; say NEWOUT3 "hello world4, fd=", fileno NEWOUT3; close NEWOUT; close NEWOUT1; close NEWOUT2; close NEWOUT3;
執行後輸出結果:
hello world1, fd=3 hello world2, fd=4 hello world3, fd=5 hello world4, fd=6
duplicate文件句柄或文件描述符時,都會自動新建一個新的文件描述符,並自動新建指向這個文件描述符的文件句柄。也就是說,只要duplicate一次,就至少有2個描述符,兩個句柄。
若是想要重用文件描述符,只新建文件句柄,Perl中可使用&=
符號(<&=、>&=、>>&=、+<&=、+>&=、+>>&=
),這表示建立一個文件句柄別名,使這個文件句柄也指向同一個文件描述符。也支持直接對文件描述符設置別名句柄,它會新建一個句柄指向這個文件描述符。
例如:
open(ALIAS, ">&=HANDLE"); open ALIAS, ">&=", fileno HANDLE;
這表示建立HANDLE句柄的一個別名,使得它兩指向同一個文件描述符。以下:
(圖注:fd是用戶空間的內容,圖中放在內核層是爲了歸納與之關聯的內核層的幾個結構:fd對應內核層的這幾個結構)
由於兩個句柄指向同一個文件描述符,因此這兩個文件句柄共享了這個文件描述符,包括這個描述符上的鎖。另外,從任一句柄更改描述符狀態,都會直接反映到另外一個文件句柄上,好比從一個文件句柄上加一把flock鎖,由於flock鎖是直接文件描述符上的,因此另外一個文件句柄別名也會持有這把鎖。
在bash中重定向很是的簡單,在Perl中重定向直接使用> < >> +> +>> +<
便可,只不過open的第一個參數是一個已存在的文件句柄,其無非是將輸入自或輸出到的某個文件句柄/文件描述符的數據轉向另外一個方向。
例如,將輸出到標準輸出的數據重定向到某個文件中,就像使用select FILEHANDLE
同樣。
open STDOUT, "> abc.log"; say "hello world"; # 將輸出到abc.log文件中
再例如輸入重定向,STDIN本該是從標準輸入中讀取數據的,可是如今改從一個文件中讀取數據。
open STDIN, "< abc.log"; while(<STDIN>){ chomp; print "$_\n"; }
可是這樣使用重定向功能會有一個問題,STDOUT或STDIN或其它重定向句柄無法還原回原始的目標了。例如STDOUT本來是輸出到終端的,將其重定向到某個文件後就無法找回輸出到終端的方法了。因此,在程序中使用重定向時,常常會將重定向配合duplicate使用,在重定向以前,先將重定向句柄dup保存一份,而後重定向,重定向結束後再使用保存的句柄恢復回來。
例如:
# 1.dup。OLDOUT和STDOUT都將指向同一個底層文件結構:終端設備文件 open OLDOUT, ">&", STDOUT or die "duplicate failed: $!"; # 2.redirect。OLDOUT仍然指向終端設備文件,可是STDOUT指向新文件結構 open STDOUT, "> $newfile" or die "redirect failed: $!"; ... do something to STDOUT ... # 3.restore。經過dup的方式從OLDOUT恢復STDOUT close STDOUT or die "close failed: $!"; open STDOUT, ">&", OLDOUT or die "duplicate failed: $!";
注意上面第三步中恢復以前,記得先關閉STDOUT,若是不關閉STDOUT,在第二步過程當中STDOUT中的緩衝不會flush。