你是否想過:
- 電腦是如何儲存我們所建立的檔案?
- 為什麼要做磁碟重組?
如果不知道問題的答案,就跟著一起閱讀作業系統追尋問題的答案吧!
進入正題#
參考 Operating System Concepts - 9th 一書對 File system 的描述:
- File system resides on secondary storage (disks)
- File system organized into layers
- application programs:行程(Process)發出讀取需求並提供對應路徑
- logical file system:檢查儲存權限
- file-organization module:將路徑轉換成實體位址
- basic file system:讀取實體位址
- I/O control:實際的 I/O 控制
- devices:從儲存裝置讀取資料
檔案系統負責了邏輯塊與實體塊之間的轉換、管理剩餘空間並分配記憶體。採用分層的設計可以降低檔案系統的複雜度,實務上,不同檔案系統的分層都會有些微的差異。
檔案系統的多元性#
作業系統可能支援一個或多個以上的檔案系統,常見的檔案系統有:FAT、exFAT、NTFS、HFS、HFS+、ext2、ext3、ext4、ODS-5、btrfs、XFS、UFS、ZFS 等。
這裡的作業系統包含常見的作業系統(Mac OS、Windows、Android、UNIX、Linux……)以及一些運作在特殊裝置的作業系統(印表機、隨身聽等等)。
從理論到實務:吃拉麵也能學會 FAT32 檔案系統#
根據恐龍書,可以得知檔案目錄的實作有兩種方法,分別是:
Linear List:紀錄了所有檔案的位置(使用指標指向檔案的第一個位址)。
例如:
index.js➡️ Address 1readme.md➡️ Address 20
Hash table:因為 Hash table 的特性,如果以 Hash table 實作檔案目錄,可以大幅最佳化檔案的查詢速度。不過也因為該資料結構的缺點,會有碰撞和增加空間使用的問題。
檔案目錄紀錄了檔案(First block)的實體位址,不過一個檔案通常是由多個 block 組成。因為這樣,檔案系統也設計了 Block 的分配方法:
Contiguous allocation
File Start Length readme 0 2 main.c 2 3 index.js 5 3 根據上面的配置方法,檔案在儲存裝置的分配會像是:
0: readme 1: readme 2: main.c 3: main.c 4: main.c 5: index.js 6: index.js 7: index.jsLinked allocation
Contiguous allocation 的優點顯而易見:因為是連續記憶體,所以讀檔速度極快。但 Contiguous allocation 也有非常明顯的缺陷,以拉麵的故事為例:在中山區某知名拉麵店門口排隊,店內只有約 10 個位置排在吧檯,看起來就像是一條連續記憶體。如果店內有 3 個空位但不相鄰:
1: X 2: 空 3: X 4: X 5: 空 6: X 7: X 8: 空 9: X 10: X因為 Contiguous allocation 的特性,必須坐在一塊。為了阻止這樣的悲劇,Linked allocation 出現了:
1: X 2: 朋朋1 3: X 4: X 5: 朋朋2 6: X 7: X 8: 朋朋3 9: X 10: X朋朋 1 知道朋朋 2 坐在第五個位置,朋朋 2 知道朋朋 3 坐在第八個位置,而檔案目錄會記錄朋朋 1 的位置。如果看到這裡,恭喜你已經了解 FAT32 的設計了。
Indexed allocation
Linked allocation 雖然讓我們能順利吃到拉麵,但同行的朋友需要紀錄其他人的位置,應該還有更聰明的辦法吧?檔案目錄紀錄了檔案的 Index block,而存放在磁碟中的 Index block 就像是群組聊天室,讓同行的拉麵朋朋都可以參與視訊。
關於磁碟重組
當店內同行的客人分別坐在不同的地方時,機靈的拉麵店店員可以請客人挪動位子,讓同行的人盡可能的坐在一塊。當資料被寫在一起,磁碟的讀寫頭不需要不停地移動,一圈就能讀完所需資料,因此,磁碟重組(重新分配座位)可以大幅提升磁碟的讀寫速度。
Virtual File System#
Virtual File System(VFS)以物件導向方式設計,讓使用者/開發者在操作檔案時不需了解各個檔案系統的實作細節,只要透過系統呼叫(System Call)就能完成檔案操作。
換言之,有了 Virtual File System,管你拉麵店放圓桌還是吧台,我通通都能處理。
不只如此,有了 Virtual File System 讓作業系統可以存取多個不同檔案系統的裝置。
File Descriptor#
在 Unix 或是 Unix-like 中,作業系統抽象了一層資料結構用來存取 File、input/output 資源,該資料結構稱為 File descriptor。並且 File descriptor 是屬於 POSIX API 的一部份,在符合 POSIX 的作業系統上,每一個行程(除了守護程序)都應該具備至少三個 File descriptor,分別是:
| Integer value (>=0) | Name | file stream |
|---|---|---|
| 0 | Standard input | stdin |
| 1 | Standard output | stdout |
| 2 | Standard error | stderr |
inode#
inode 的設計最早可以追溯到第一版的 UNIX 作業系統,至今,大部分的 UNIX-like 都採用類似的檔案系統設計。
維基百科對 inode 的定義#
inode(index node)是指在許多「類 Unix 檔案系統」中的一種資料結構,用於描述檔案系統物件(包括檔案、目錄、裝置檔案、socket、管道等)。每個 inode 儲存了檔案系統物件資料的屬性和磁碟塊位置。檔案系統物件屬性包含了各種元資料,如:最後修改時間、使用者群組(owner)和權限資料。
什麼是 metadata#
Metadata(元資料)這個名詞對多數開發者都不算陌生。實際上,Metadata 就是一個用來描述資料的資料。舉例來說,在 *.html 檔案中,我們可以在 <head> 裡面找到許多 metadata 的 Target,這些標籤都是用來描述這個 *.html 檔案。
inode table#
每個 file descriptor 都會指向一個全域的檔案表,檔案表則會指向 inode table。inode table 其實就是一個存放 inode 的陣列,在檔案系統中,它會記錄所有的檔案(inode)。
xv6 中的 inode#
xv6 是一個研究/教學用途的小型作業系統,它對於檔案系統有完整的實作。在 xv6 的檔案系統中,inode 有兩種意義:
dinode
struct dinode { short type; // File type short major; // Major device number (T_DEVICE only) short minor; // Minor device number (T_DEVICE only) short nlink; // Number of links to inode in file system uint size; // Size of file (bytes) uint addrs[NDIRECT+1]; // Data block addresses };dinode 為實際存放在磁碟中的 inode:
type用來區分該 inode 是檔案、目錄還是特殊檔案。如果type為 0,代表該 inode 是空閒狀態。nlink紀錄有多少檔案指向該節點,這個屬性可以幫助檔案系統判斷該節點是否可以被回收。size用來記錄檔案的位元組數量。addrs紀錄 block 的實際位址。
inode
紀錄在 Memory 上的 inode,是紀錄在磁碟中的 inode 的副本。此外還記錄了 Kernel 的相關資訊:
struct inode { uint dev; // Device number uint inum; // Inode number int ref; // Reference count struct sleeplock lock; // protects everything below here int valid; // inode has been read from disk? short type; // copy of disk inode short major; short minor; short nlink; uint size; uint addrs[NDIRECT+1]; };
作業系統如何讀檔#
我們在撰寫 C 語言並用其讀取檔案時,會出現與下方程式類似的用法:
fp = fopen(file_name, "r");
while((ch = fgetc(fp)) != EOF)實際上,我們可以把讀檔拆成好幾個步驟:
fopen- Call system call
- Virtual File System
- File system
- HDD
作業系統透過 Driver 讀取 HDD 的內容時,還需要考慮排程演算法。第一次執行 dir 指令或是開檔案會比較慢,因為要從 File Control Block 查找資料。之後再開就會變快了(因為已經被 Cache 了)。通常來說,第一個 Block 一定是 Root。
在這個過程中,一共做了 2 次記憶體存取:
- Disk ➡️ buffer (Kernel space)
- Buffer (Kernel space) ➡️ Buffer (User space)
總結#
本篇文章用愉快的步調學習枯燥乏味的檔案系統,希望能夠引起讀者對作業系統的興趣。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10278625