計算機可以在各種儲存媒介(諸如磁碟、磁帶和光碟)上儲存資訊。為了方便使用計算機系統,作業系統提供了資訊儲存的統一邏輯檢視。作業系統對儲存裝置的物理屬性加以抽象,從而定義邏輯儲存單位,即
檔案(file)。檔案由作業系統對映到物理裝置上。這些儲存裝置通常是非易失性的,因此在系統重新啟動之間內容可以持久。
檔案是記錄在外存上的相關資訊的命名組合。從使用者角度來看,檔案是邏輯外存的最小分配單元,也就是說,資料只有通過檔案才能寫到外存。
通常,檔案表示程式(源形式和目標形式)和資料。資料檔案可以是數位的、字元的、字元數位的或二進位制的。檔案可以是自由形式的,例如文字檔案,或者可以是具有嚴格格式的。通常,檔案為位、位元組、行或記錄的序列,其含義由檔案的建立者和使用者定義。因此,檔案概念非常通用。
檔案資訊由建立者定義。檔案可儲存許多不同型別的資訊,如源程式或可執行程式、數位或文字資料、照片、音樂、視訊等。檔案具有某種定義的結構,這取決於其型別。比如:
-
文字檔案為按行(可能還有頁)組織的字元序列;
-
原始檔為函數序列,而每個函數包括宣告和可執行語句;
-
可執行檔案為一系列程式碼段,以供載入程式調入記憶體並執行。
檔案屬性
檔案被命名以方便使用者,並且通過名稱可以參照。名稱通常為字串,例如 example.c。有的系統區分名稱內的大小寫字元,而其他系統則不區分。當檔案被命名後,它就獨立於進程、使用者,甚至建立它的系統。
例如,一個使用者可能建立檔案 example.c,而另一個使用者可能通過這個名稱來編輯它。檔案所有者可能會將檔案寫入 USB 盤,或作為 email 附件傳送,或複製到網路上並且在目標系統上仍可稱為 example.c。
檔案的屬性因作業系統而異,但通常包括:
-
名稱:符號檔名是以人類可讀形式來儲存的唯一資訊。
-
識別符號:這種唯一標記(通常為數位)標識檔案系統的檔案,它是檔案的非人類可讀的名稱。
-
型別:支援不同型別檔案的系統需要這種資訊。
-
位置:該資訊為指向裝置與裝置上檔案位置的指標。
-
尺寸:該屬性包括檔案的當前大小(以位元組、字或塊為單位)以及可能允許的最大尺寸。
-
保護:存取控制資訊確定誰能進行讀取、寫入、執行等。
-
時間、日期和使用者標識:檔案建立、最後修改和最後使用的相關資訊可以儲存。這些資料用於保護、安全和使用監控。
有些較新的檔案系統還支援擴充套件檔案屬性,包括檔案的字元編碼和安全功能,如檔案校驗和。
所有檔案的資訊儲存在目錄結構中,該目錄結構也儲存在外存上。通常,目錄條目由檔案的名稱及其唯一識別符號組成。根據識別符號可定位其他檔案屬性。記錄每個檔案的這些資訊可能超過 1KB 位元組。在具有許多檔案的系統中,目錄本身的大小可能有數兆位元組。由於目錄(如檔案)必須是非易失性的,因此必須存在裝置上,並根據需要而被調入記憶體。
檔案操作
檔案為抽象資料型別。為了正確定義檔案,需要考慮可以對檔案執行的操作。作業系統可以提供系統呼叫,來建立、寫入、讀取、重新定位、刪除及截斷檔案。
下面討論作業系統如何執行這 6 個基本檔案操作:
-
建立檔案:建立檔案需要兩個步驟。首先,必須在檔案系統中為檔案找到空間;其次,必須在目錄中建立新檔案的條目。
-
寫檔案:為了寫檔案,使用一個系統呼叫指定檔名稱和要寫入檔案的資訊。根據給定的檔名稱,系統搜尋目錄以查詢檔案位置。系統應保留寫指標(write pointer),用於指向需要進行下次寫操作的檔案位置。每當發生寫操作時,寫指標必須被更新。
-
讀檔案:為了讀檔案,使用一個系統呼叫,指明檔名稱和需要檔案的下一個塊應該放在哪裡(在記憶體中)。同樣,搜尋目錄以找到相關條目,系統需要保留一個讀指標,指向要進行下一次讀取操作的檔案位置。一旦發生了讀取,讀指標必須被更新。因為進程通常從檔案讀取或寫到檔案,所以當前操作位置可以作為進程的當前檔案位置指標。讀和寫操作都使用相同的指標,可節省空間並降低系統複雜性。
-
重新定位檔案:搜尋目錄以尋找適當的條目,並且將當前檔案位置指標重新定位到給定值。重新定位檔案不需要涉及任何實際的 I/O。這個檔案操作也稱為檔案定位。
-
刪除檔案:為了刪除檔案,在目錄中搜尋給定名稱的檔案。找到關聯的目錄條目後,釋放所有檔案空間,以便它可以被其他檔案重複使用,並刪除目錄條目。
-
截斷檔案:使用者可能想要刪除檔案的內容,但保留它的屬性。不是強制使用者刪除檔案再建立檔案,這個功能允許所有屬性保持不變(除了檔案長度),但讓檔案重置為零,並釋放它的檔案空間。
這 6 個基本操作組成了所需檔案操作的最小集合。其他常見操作包括:將新資訊附加到現有檔案的末尾和重新命名現有檔案。然後,這些基本操作可以組合起來實現其他檔案操作。
例如,建立一個檔案副本,或複製檔案到另一 I/O 裝置,如印表機或顯示器,可以這樣來完成:建立一個新檔案,從舊檔案讀入並寫出到新檔案。還希望有檔案操作,以用於獲取和設定檔案的各種屬性的操作。例如,可能需要操作以允許使用者確定檔案狀態,如檔案長度;設定檔案屬性,如檔案所有者等。
以上提及的大多數檔案操作涉及搜尋目錄,以得到命名檔案的相關條目。為了避免這種不斷的搜尋,許多系統要求,在首次使用檔案之前進行系統呼叫 open()。作業系統有一個開啟檔案表以用於維護所有開啟檔案的資訊。當請求檔案操作時,可通過該表的索引指定檔案,而不需要搜尋。當檔案最近不再使用時,進程關閉它,作業系統從開啟檔案表中刪除它的條目。系統呼叫 create() 和 delete() 是針對關閉檔案而不是開啟檔案而進行操作的。
有的系統在首次參照檔案時,會隱式地開啟它。當開啟檔案的作業或程式終止時,將自動關閉它。然而,大多數作業系統要求,程式設計師在使用檔案之前通過系統呼叫 open() 顯式地開啟它。操作 open() 根據檔名搜尋目錄,以將目錄條目複製到開啟檔案表。
呼叫 open() 也會接受存取模式資訊,如建立、唯讀、讀寫、只附加等。根據檔案許可權,檢查這種模式。如果允許請求模式,則會為進程開啟檔案。系統呼叫 open() 通常返回一個指標,以指向開啟檔案表的對應條目。這個指標,而不是實際的檔名,會用於所有 I/O 操作,以避免任何進一步搜尋,並簡化系統呼叫介面。
對於多個進程可以同時開啟檔案的環境,操作 open() 和 close() 的實現更加複雜。在多個不同的應用程式同時開啟同一個檔案的系統中,這可能發生。通常,作業系統採用兩級的內部表:每個進程表和整個系統表。每個進程表跟蹤它開啟的所有檔案。該表所存的是進程對檔案的使用資訊。例如,每個檔案的當前檔案指標就存在這裡,檔案存取許可權和記賬資訊也存在這裡。
單個進程表的每個條目相應地指向整個系統的開啟檔案表。系統表包含與進程無關的資訊,如檔案在磁碟上的位置、存取日期和檔案大小。一旦有進程開啟了一個檔案,系統表就包含該檔案的條目。當另一個進程執行呼叫 open(),只要簡單地在其進程開啟表中增加一個條目,並指向系統表的相應條目。通常,系統開啟檔案表為每個檔案關聯一個開啟計數,用於表示多少進程開啟了這個檔案。每次 close() 遞減開啟計數;當開啟計數為 0 時,表示不再使用該檔案,並且可從系統開啟檔案表中刪除這個檔案條目。
總而言之,每個開啟檔案具有如下關聯資訊:
-
檔案指標:對於沒有將檔案偏移作為系統呼叫 read() 和 write() 引數的系統,系統必須跟蹤上次讀寫位置,以作為當前檔案位置指標。該指標對操作檔案的每個進程是唯一的,因此必須與磁碟檔案屬性分開儲存。
-
檔案開啟計數:當檔案關閉時,作業系統必須重新使用它的開啟檔案表的條目,否則表的空間會不夠用。多個進程可能開啟同一檔案,這樣在刪除它的開啟檔案表條目之前,系統必須等待最後一個進程關閉這個檔案。檔案開啟計數跟蹤開啟和關閉的次數,在最後關閉時為 0。然後,系統可以刪除這個條目。
-
檔案的磁碟位置:大多數檔案操作要求系統修改檔案資料。查詢磁碟上的檔案所需的資訊儲存在記憶體中,以便系統不必為每個操作從磁碟上讀取該資訊。
-
存取許可權:每個進程採用存取模式開啟檔案。這種資訊儲存在進程的開啟檔案表中,因此作業系統可以允許或拒絕後續的 I/O 請求。
有的作業系統提供功能,用於鎖定開啟的檔案(或檔案的部分)。檔案鎖允許一個進程鎖定檔案,以防止其他進程存取它。檔案鎖對於多個進程共用的檔案很有用,例如,系統中的多個進程可以存取和修改的系統紀錄檔檔案。
檔案鎖提供類似於讀者一寫者鎖。共用鎖類似於讀者鎖,以便多個進程可以並行獲取它。獨占鎖類似於寫者鎖,一次只有一個進程可以獲取這樣的鎖。重要的是要注意,並非所有作業系統都提供兩種型別的鎖,有些系統僅提供獨占檔案鎖。
另外,作業系統可以提供強制或建議檔案鎖定機制。如果鎖是強制性的,則一旦進程獲取獨占鎖,作業系統就阻止任何其他進程存取鎖定的檔案。
例如,假設有一個進程獲取了檔案 system.log 的獨占鎖。如果另一進程(如文字編輯器)嘗試開啟 system.log,則作業系統將阻止存取,直到獨占鎖被釋放。即使文字編輯器並沒明確地獲取鎖,也會發生這種情況。或者,如果鎖是建議性的,則作業系統不會阻止文字編輯器獲取對 system.log 的存取。相反,必須編寫文字編輯器,以便在存取檔案之前手動獲取鎖。
換句話說,如果鎖定方案是強制性的,則作業系統確保鎖定完整性。對於建議鎖定,軟體開發人員應確保適當地獲取和釋放鎖。作為一般規則,Windows 作業系統採用強制鎖定,UNIX 系統採用建議鎖定。
使用檔案鎖定,與普通進程同步一樣,還是需要謹慎的。例如,程式設計師在具有強制鎖定的系統上開發時,應小心地確保只有在存取檔案時才鎖定獨占檔案。否則,他們將阻止其他進程也對檔案進行存取。此外,必須採取一些措施,來確保兩個或更多進程在嚐試獲取檔案鎖時不會捲入死鎖。
檔案型別
當設計檔案系統(甚至整個作業系統)時,總是需要考慮作業系統是否應該識別和支援檔案型別。如果作業系統識別檔案的型別,則它就能按合理的方式來操作檔案。
例如,一個經常發生的錯誤就是,使用者嘗試輸出二進位制目標形式的一個程式。這種嘗試通常會產生垃圾;然而,如果作業系統已得知一個檔案是二進位制目標程式,則嘗試可以成功。
實現檔案型別的常見技術是將型別作為檔名的一部分。檔名分為兩部分,即名稱和擴充套件,通常由句點分開(表 1)。這樣,使用者和作業系統僅從檔名就能得知檔案的型別。大多數作業系統允許使用者將檔名命名為字元序列,後跟一個句點,再以由附加字元組成的 擴充套件名結束,範例包括 resume.docx、server.c 和 ReaderTliread.cpp。
表 1 檔案型別
檔案型別 |
常用擴充套件名 |
功能 |
可執行檔案 |
exe, com, binor none |
可執行的機器語言程式 |
目標檔案 |
obj, o |
已編譯的、尚未連結的機器語言 |
原始碼檔案 |
c, cc, java,perl,asm |
各種語言的原始碼 |
批次檔 |
bat, sh |
命令解釋程式的命令 |
標記檔案 |
xml, html,tex |
文字資料、文件 |
文書處理檔案 |
xml, rtf,docx |
各種文書處理程式的格式 |
庫檔案 |
lib, a, so, dll |
為程式設計師提供的程式庫 |
列印或可視檔案 |
gif, pdf, jpg |
列印或影象格式的 ASCII 或二進位制檔案 |
檔案檔案 |
rar,zip, tar |
相關檔案組成的一個檔案,(有時壓縮)用於歸檔或儲存 |
多媒體檔案 |
mpeg, mov, mp3,mp4, avi |
包含音訊或 A/V 資訊的二進位制檔案 |
作業系統使用擴充套件名來指示檔案型別和可用於檔案的操作型別。例如,只有擴充套件名為 .com、.exe 或 .sli 的檔案才能執行。.com 和 .exe 檔案是兩種形式的二進位制可執行檔案,而 .sh 檔案是外殼指令碼,包含 ASCII 格式的作業系統命令。
應用程式也使用擴充套件名來表示所感興趣的檔案型別。例如,Java 編譯器的原始檔具有 .java 擴充套件名,Microsoft Word 字處理程式的檔案以 .doc 或 .docx 擴充套件名來結束。這些擴充套件名不總是必需的,因此使用者可以不用擴充套件名(節省打字)來指明檔案,應用程式根據給定的名稱和預期的擴充套件名來查詢檔案。因為這些擴充套件名沒有作業系統的支援,所以它們只能作為應用程式的“提示”。
下面考慮 Mac OS X 作業系統。這個系統的每個檔案都有型別,例如 .app (用於應用程式)。每個檔案還有一個建立者屬性,用來包含建立它的程式名稱。這種屬性是由作業系統在呼叫 create() 時設定的,因此其使用由系統強制和支援。
例如,由字處理器建立的檔案採用字處理器名稱作為建立者。當使用者通過在表示檔案的圖示上雙擊滑鼠以開啟檔案,就會自動呼叫字處理器,並載入檔案以便編輯。
UNIX 系統採用位於某些檔案開始部分的幻數,大致表明檔案型別,如可執行程式、shell 指令碼、PDF 檔案等。不是所有檔案都有幻數,因此系統特徵不能僅僅基於這種資訊。UNIX 也不記錄建立程式的名稱。UNIX 允許檔名擴充套件提示,但是作業系統既不強制也不依賴這些擴充套件名;這些擴充套件名主要幫助使用者確定檔案內容的型別。擴充套件名可以由給定的應用程式採用或忽略,但這是由應用程式的開發者所決定的。
檔案結構
檔案型別也可用於指示檔案的內部結構。我們直到,原始檔和目標檔案具有一定結構,以便匹配讀取它們的程式的期望。此外,有些檔案必須符合作業系統理解的所需結構。
例如,作業系統要求可執行檔案具有特定的結構,以便可以確定將檔案載入到記憶體的哪裡以及第一條指令的位置是什麼。有些作業系統將這種想法擴充套件到系統支援的一組檔案結構,以便採用特殊操作來處理具有這些結構的檔案。
讓作業系統支援多個檔案結構帶來一個缺點,作業系統會變得太複雜。如果作業系統定義了5個不同的檔案結構,則它需要包含程式碼,以便支援這些檔案結構。此外,可能需要將每個檔案定義為作業系統支援的檔案型別之一。如果新應用程式需要按作業系統不支援的方式來組織資訊,則可能導致嚴重的問題。
例如,假設有個系統支援兩種型別的檔案:文字檔案(由回車符和換行符分隔的 ASCII 字元組成)和可執行的二進位制檔案。現在,如果我們(作為使用者)想要定義一個加密的檔案,以保護內容不被未經授權的人讀取,則我們可能會發現兩種檔案型別都不合適。加密檔案不是 ASCII 文字行,而是(看起來)隨機位。雖然加密檔案看起來是二進位制檔案,但是它不是可執行的。因此,我們可能要麼必須繞過或濫用作業系統檔案型別機制,要麼放棄加密方案。
有些作業系統強加(並支援)最小數量的檔案結構。UNIX、Windows 等都採用這種方案。UNIX 認為每個檔案為 8 位位元組序列,而作業系統並不對這些位做出解釋。這種方案提供了最大的靈活性,但是支援的也很少。每個應用程式必須包含自己的程式碼,以便按適當結構來解釋輸入檔案。但是,所有作業系統必須支援至少一種結構,即可執行檔案的結構,以便系統能夠載入和執行程式。
內部檔案結構
在內部,定位檔案的偏移對作業系統來說可能是比較複雜的。磁碟系統通常具有明確定義的塊大小,這是由磁區大小決定的。所有磁碟 I/O 按塊(物理記錄)為單位執行,而所有塊的大小相同。物理記錄大小不太可能剛好匹配期望的邏輯記錄的長度。邏輯記錄的長度甚至可能不同。這個問題的常見解決方案是,將多個邏輯記錄包裝到物理塊中。
例如,UNIX 作業系統將所有檔案定義為簡單的位元組流。每個位元組可以通過距檔案的開始(或結束)的偏移來單獨定址。在這種情況下,邏輯記錄大小為 1 位元組。根據需要,檔案系統通常會自動將位元組打包以存入物理磁碟塊,或從磁碟塊中解包得到位元組(每塊可為 512 位元組)。
邏輯記錄大小、物理塊大小和打包技術確定了每個物理塊可有多少邏輯記錄。打包可以通過使用者應用程式或作業系統來完成。不管如何,檔案都可當作塊的序列。所有基本 I/O 功能都以塊為單位來進行。從邏輯記錄到物理塊的轉換是個相對簡單的軟體問題。
由於磁碟空間總是按塊為單位來分配的,因此每個檔案的最後一塊的某些部分通常會被浪費。例如,如果每個塊是 512 位元組,則 1949 位元組的檔案將分得 4 個塊(2048 位元組),最後 99 位元組就浪費了。按塊(而不是位元組)為單位來保持一切而浪費的位元組稱為內部碎片。所有檔案系統都有內部碎片,塊大小越大,內部碎片也越大。