2.1 UNIX 輸入輸出基本概念
在任何一種操作系統中,程序開始讀寫一個文件的內容之前,必須首先在程序與文件之間建立連接或通信通道,這一過程稱為打開文件。打開一個文件的目的可能是要讀其中的數據,也可能是要往其中寫入數據,還可能是既要讀又要寫數據。
UNIX系統有兩種機制用于描述程序與文件的這種連接:一種稱為文件描述字,另一種稱為流。因此,系統中關于I/O的函數也分為兩大類:一類針對文件描述字操作,另一類針對流操作。
當用流或描述字I/O函數打開一個文件時,它們分別返回一個流或文件描述字,然后便可以將這個流或文件描述字作為參數傳遞給相應讀寫函數來完成實際的讀寫操作。
當已完成對文件的讀寫之后,可以通過關閉文件而終止程序與文件的這種連接。一旦關閉了一個文件描述字或者一個流,就不能再對它進行輸入輸出。
- 文件描述字與流
UNIX系統中,文件描述字表示為int類型的對象,而流表示為指向類型為FILE結構的指針。文件描述字函數多數是系統調用,它們提供底層基本的輸入輸出操作接口。當需要對特定設備進行控制操作時,往往必須使用文件描述字,流函數不能夠進行這類操作。另外,如果程序需要按特殊方式進行輸入輸出(如非阻塞輸入),也必須使用文件描述字。
流函數建立在文件描述字之上,通過文件描述字函數而實現,它給程序提供了更高一級的輸入輸出接口。流函數比對應的文件描述字函數更豐富,功能更強大,也更利于程序的移植。任何運行ANSI C的系統均支持流,但并不是所有系統都支持文件描述字,有的系統根本不支持文件描述字或僅僅實現了文件描述字函數集合的一個子集。因此,一般情況下,應當堅持使用流而不是文件描述字,除非是想做某種特殊操作,而此操作只能用文件描述字才能完成。
本章將介紹的標準I/O函數均針對流操作,第3章將介紹針對文件描述字的I/O函數。
- 文件名與路徑名
UNIX系統中幾乎每一種對象都表示為文件,不僅是通常的數據集合,系統中的每一個設備也表示為文件。文件被安排在目錄中,目錄本身又含有子目錄,由此形成了文件系統的層次結構。
目錄本身也是一種文件,不過它的內容是一組連接實際文件的文件名及相關信息,這些連接稱為鏈或目錄登記項(4.2.2節)。我們前面雖然說 “文件被安排在目錄中”,但是實際上目錄只包含指向文件的指針而不是文件本身。為了理解文件名的語法,首先需要理解UNIX文件系統的目錄層次結構。
系統中,每一個用戶均有一個主目錄,其文件通常存儲在這個目錄以及該目錄的子目錄中。例如,用戶kjzhao,他的主目錄是/home/kjzhao,在其主目錄中有系統幫助建立的幾個標準目錄,如.bash_profile等;也有由他自己創建的子目錄,如用于日常工作的目錄work,用于應用程序的目錄program等。另外一些用戶的主目錄也可能位于/home目錄中,而/home則是根目錄“/”的子目錄。在根目錄中通常還包括用于系統程序的子目錄/bin,用于系統配置文件的子目錄/etc,用于系統庫文件的子目錄/lib,以及代表各種物理設備的子目錄/dev等。圖2-1是這種目錄層次的一個示例圖。
UNIX中,當涉及一個文件的名字時,常常使用術語“文件名”或“路徑名”。但按POSIX標準術語,文件名和路徑名分別有不同的含義:文件名指的是不帶路徑的文件名,而路徑名的含義則較廣泛,它既包括含路徑的文件名,也包括單個文件名。不過,從一般用戶的角度來看,由于路徑名總是用來引用文件,因此有時也不加區分地統稱路徑名為文件名。為了敘述方便,本節我們按POSIX標準區分路徑名和文件名。在其他章節中,只要上下文含義清楚,我們也不加區分地使用路徑名和文件名。
同其他操作系統一樣,UNIX中每一個文件都有一個名字,此名字為一字符串,即文件名。文件名用于命名一個文件,它由1至NAME_MAX個字符組成,這些字符可以是字符集中除斜線字符(/)和空字符(NUL)之外的任意字符。系統宏NAME_MAX是POSIX定義的文件名的最大字符個數(不是字符串的長度,該計數不包括結束的空字符)。文件名也稱為路徑名分量。
路徑名用于標識一個文件,它是由1至PATH_MAX個字符組成的字符串。路徑名由用斜線“/”分隔的一至多個路徑名分量構成的序列組成。路徑名開始的斜線是可選的,僅由一個分量構成的路徑名標識一個相對于當前目錄的文件,多個連續的“/”字符等價于單個“/”字符。一個具有多個路徑名分量的路徑名命名一個目錄以及在該目錄中的文件。系統宏PATH_MAX是POSIX定義的路徑名的最大字符個數,大多數現代UNIX版本中該值定為1024, Linux則沒有限制。
以“/”開頭的路徑名稱為絕對路徑名,此路徑名的第一個分量位于根目錄;其他路徑名稱為相對路徑名,它們的第一個分量位于當前工作目錄(4.9.1節)。

路徑名分量“.”和“..”有特殊的含義。“.”表示當前目錄,“..”表示當前目錄的父目錄。作為一個例外,根目錄中的“..”表示的是根目錄本身。
標識一個目錄的路徑名可以任選地以斜線“/”結尾。可用路徑名“/”來引用根目錄。下面是一些路徑名的例子:
/a/b 根目錄下子目錄a中的文件b
a 當前工作目錄中名為a的文件
./a 與a相同
../a 當前工作目錄的父目錄中名為a的文件
與Windows操作系統不同,UNIX沒有任何關于文件擴展名或文件版本的內建支持作為文件名語法的一部分。雖然許多實用程序使用有關文件名的一些約定,如C源代碼文件通常具有后綴為“.c”的名字,但是,這并不意味著UNIX文件系統本身強制這類約定。
- 文件位置
對于已打開的文件,它的屬性之一是文件位置。文件位置給出文件中當前可讀寫字符的位置,在所有POSIX兼容的系統中,它是一個表示距文件開始多少字節數的整數。
當文件剛打開時,文件位置位于文件的開始處,之后每當讀出或者寫入一個字符,文件的位置便增加一字節。換言之,對文件的訪問是順序的。
但是,對于以“添加”(append)打開的文件(2.3節),其寫出的處理有點特殊。對這種文件的寫出總是順序地附加在該文件的末尾,而不管文件的位置如何。其文件位置只用于控制從文件讀出數據。
普通文件(4.2.1節)允許讀寫文件的任意位置。這種允許讀寫任意位置的文件也稱為隨機文件。可以用函數fseek()或lseek()改變隨機文件的位置。如果企圖改變一個不支持隨機訪問的文件的位置,則會得到ESPIPE錯誤。磁盤文件一般均是隨機文件,終端則不是隨機文件。
UNIX環境中,多個進程可同時讀一個文件。為了使得每個進程都能夠按自己的步調讀文件,每個進程必須有自己的文件位置指針,這樣才不會受到其他進程的影響。事實上,進程每次打開一個文件都會創建一個獨立的文件位置。因此,即使在同一個程序中打開一個文件兩次,也會得到兩個具有獨立文件位置的流或描述字。但是,如果打開一個文件描述字,然后復制它得到另一個文件描述字(3.5節),則這兩個文件描述字會共享同一文件位置:改變一個文件描述字的文件位置將影響另一個描述字的文件位置。