天天看點

《UNIXLinux程式設計教程》一3.6 檔案控制函數fcntl()

函數fcntl()提供了進一步管理低級檔案描述字的各種手段,用它可以對已打開的描述字執行各種控制操作,例如,重複一個檔案描述字,查詢或設定檔案描述字标簽,查詢或設定檔案描述字标簽或檔案狀态标簽,操縱檔案鎖(10.1節)等。

fcntl()對打開的檔案描述字filedes執行各種控制操作,具體是哪一種操作由它的第二個參數cmd給出,表3-2列出了該參數的所有允許值。根據參數cmd的值,有一些操作還要求提供第三個參數。除了檔案鎖操作之外,其他操作均要求第三個參數是int類型。對于檔案鎖操作,第三個參數是一個指向結構struct flock的指針(10.1節)。fcntl()的正常傳回值也取決于cmd參數。

《UNIXLinux程式設計教程》一3.6 檔案控制函數fcntl()

fcntl()可執行表3-2所列10種不同的操作,前5種分别完成三類功能:重複檔案描述字(f_dupfd),設定/擷取描述字标簽(f_getfd/f_setfd),設定/擷取檔案狀态标簽(f_getfl/f_setfl)。本節中隻描述這5種操作,後5種操作推遲至第10章講述。

調用

的作用是重複檔案描述字filedes:它傳回一個其值等于或大于arg的新檔案描述字。該檔案描述字與filedes共享同一個系統打開檔案表,但有自己的檔案描述字标簽,并且它的fd_cloexec标簽被清除。

dup()是fcntl()這種操作的特例,它等價于

而dup2()在filedes與arg不同且filedes是合法的檔案描述字的情況下,等價于

不過,dup2()是原子操作,即關閉arg以及複制filedes這兩個動作的完成保證是完整的,在它們之間不會出現arg被關閉但filedes還未複制便被中斷的情形。

回顧圖3-2,我們看到與每個打開的檔案描述字相連有一個檔案描述字标簽。這個标簽隻與特定的檔案描述字有關,是以,如果從單次打開的檔案建立了多個重複的檔案描述字,則每個描述字有它自己的檔案描述字标簽。

目前,檔案描述字标簽中隻有一個标志:fd_cloexec,此标志指明當exec()(6.3節)執行時将關閉檔案描述字。當用open()或dup()打開檔案描述字時,此标志位的初始狀态是清除的,這意味着在exec()之後描述字仍将在新程式中保留。

如下調用擷取與參數filedes相連的檔案描述字标簽:

它的傳回值是一個非負整數,該整數解釋為标簽中各個标志位的按位或,其中包括fd_cloexec(在目前隻有一個标簽在使用的情況下,則隻有一位)。

如下調用:

設定與檔案filedes相連的檔案描述字标簽,它需要類型為int的第三個參數給出新标簽值。

例3-4 當修改檔案描述字标簽時,首先應當用f_getfd獲得目前标志,然後修改其值。不要假定本書所列的标志是實作提供的全部标志。盡管目前隻支援一個标志,但我們寫的程式可能從現在起還需運作多年且今後可能存在更多标志。應當如程式3-4所示來設定或清除标志fd_cloexec,這樣便不會對其他标志造成影響。

《UNIXLinux程式設計教程》一3.6 檔案控制函數fcntl()

檔案狀态标簽指明檔案的打開屬性,它們由open()的flags參數指明(見表3-1)。與描述字标簽不同,檔案狀态标簽由與同一次打開檔案相連的所有重複檔案描述字所共享(參見圖3-3)。檔案狀态标簽中的标志可分為三類:通路方式、打開時标志和i/o操作方式。

通路方式

通路方式指明允許檔案描述字用于讀、寫或兩者兼之,包括o_rdonly、o_wronly和o_rdwr。這些通路方式在檔案被打開時標明,之後便不能再改變。

為了确定檔案通路方式,必須從fcntl()取回的檔案狀态标簽中抽取通路方式。因為讀和寫通路方式不一定是獨立的标志位,是以抽取檔案通路方式的可移植方法是用宏常數o_accmode位串與檔案狀态标簽值作按位與操作(c中的'&'運算),由此生成表示檔案通路方式的值,即o_rdonly、o_wronly或o_rdwr(參見程式3-5)。

打開時标志

打開時标志指明打開檔案時影響open()行為的一些選項。這些選項一旦檔案打開就不保留,但有一個例外是o_nonblock,因為o_nonblock同時也是一個i/o操作方式,故此标志被保留(3.7節)。

o_creat:若設定,當該檔案不存在時建立此檔案。

o_excl:若o_creat和o_excl同時設定,則當指定的檔案已經存在時,open()失敗。這保證不會破壞已存在的檔案。

o_nonblock:防止open()為打開檔案而阻塞很長時間。這通常僅對諸如串行端口的裝置檔案才有意義。o_nonblock标志同時也作為i/o操作方式标志,這意味着在open()中指明o_nonblock就同時設定了非阻塞i/o方式(3.7節)。為了非阻塞地打開一個檔案且不影響正常的阻塞i/o,必須先設定o_nonblock來調用open(),然後調用fcntl()關閉此位。

o_noctty:若命名的檔案是終端裝置,不讓它成為該程序的控制終端(6.9節)。

o_trunc:截斷檔案為零長度,這一選項隻對普通檔案(4.2.1節)有用,對諸如目錄或fifo之類的特殊檔案(4.2.5節)無用。由open()來做截斷而不直接調用ftruncate()函數并沒有什麼太好的理由,隻是因為o_trunc标志在ftruncate()引入之前就已存在于unix中,保留它隻是為了向下相容。

i/o操作方式

i/o操作方式影響使用檔案描述字進行輸入輸出操作的工作方式。這些标志由open()設定,之後可以用fcntl()擷取和改變。

o_append:檔案的附加方式位。若此位設定,所有write()操作寫資料至檔案尾而不管檔案位置在何處。這是附加資料至檔案尾唯一可靠的方法。用附加方式可以保證無論是否有其他程序正在寫同一個檔案,write()操作總是将資料寫在目前檔案尾。相反,在未設定此位的情況下,如果通過簡單地移動檔案位置到檔案尾,然後再寫資料,則在設定檔案位置之後開始寫之前,可能有其他程序擴充此檔案(對應于兩個不同的程序打開同一個檔案的情形,它們共享同一個vnode,但各自有自己的系統打開檔案表,因而有自己的檔案位置,參見圖3-2),進而導緻所寫的資料出現在實際檔案尾之前的某個地方。

o_nonblock:此标志同時也作為i/o操作方式标志。如果這一位設定,對檔案的read()請求,當無立即可用的輸入時能以eagain錯誤狀态立即傳回,而不是阻塞。類似地,write()請求也能在輸出不能寫出時以eagain錯誤狀态立即傳回。

o_async:此标志用于信号驅動的i/o(10.2節)。如果這一位設定,當檔案描述字中有輸入資料時會生成sigio信号。

o_sync:如果這一位設定,檔案按同步i/o方式打開,并将導緻任何寫該檔案的操作都阻塞調用程序直至資料(包括核心i/o緩沖區中的資料)以及與此次寫有關的檔案屬性(4.1.1節)已全部寫至實體存儲媒體(3.9節)。

o_dsync:如果這一位設定,檔案按同步i/o方式打開,并将導緻任何寫該檔案的操作都阻塞調用程序直至資料(包括核心i/o緩沖區中的資料)已全部已寫至實體存儲媒體(3.9節)。但如果所寫的資料不影響讀剛寫入的資料,則不等待檔案屬性更新。

o_rsync:如果這一位設定,檔案按同步i/o方式打開,并将導緻任何讀該檔案的操作都将等待所有寫入同一區域的寫操作按o_dsync和o_sync完成後再進行。如果同時設定了o_sync 和 o_rsync标志,則讀操作将阻塞直到檔案的通路時間屬性已寫至實體存儲媒體。如果同時設定了o_dsync 和 o_rsync标志,則讀操作将阻塞直到所有與保持檔案完整性有關的資料都已寫至實體存儲媒體。

簡單地說,o_sync、o_dsync和o_rsync這幾個标志的主要作用是使資料直接寫到磁盤或直接從磁盤讀入。

擷取和改變檔案狀态标簽

檔案狀态标簽可以用fcntl()函數擷取或改變。如下調用:

擷取描述字 filedes的檔案狀态标簽。它的正常傳回值是一個非負整數,此數解釋為各個獨立标志的按位或。因為檔案通路方式不是用獨立的位表示的,是以,為比較它們需要用o_accmode屏蔽傳回值中的其他位。

例3-5 程式3-5是讀取檔案狀态标簽的例子,它列印出指定的檔案描述字中已設定了的檔案狀态标簽。為了讀取檔案通路方式标志,我們先用fcntl()傳回的标簽值val和o_accmode進行“與”操作,然後再比較其結果。而對于其他标志,則直接從val中抽取。

為了設定描述字filedes的檔案狀态标簽,用指令f_setfl調用fcntl(),該指令要求int類型的第三個參數以指明新标簽:

它的正常傳回值是一個非–1的不确定值。–1指出出錯。

檔案狀态标簽中,檔案的通路方式是在打開檔案時設定的,一旦設定便不能改變;打開時标志隻在打開時使用,之後便不再保留。是以,應用唯一可以設定的是檔案的i/o操作方式,即o_append、o_nonblock。

同設定檔案描述字标簽類似,如果想修改檔案狀态标簽,同樣應當先用f_getfl調用fcntl()獲得目前标簽,然後再修改其值。

例3-6 程式3-6給出的函數保證在設定或清除标志o_nonblock 時不會改變其他标簽。

程式3-6 設定檔案狀态标簽為非阻塞方式