文件系統是操作系統中管理持久性數據的子系統,提供數據存儲和訪問功能。對於服務器開發人員,比較關注的是unix(linux)環境下的文件系統,比如分區與磁盤關系,磁盤的剩余空間,文件的類型與權限控制,文件鏈接等相關知識。文章內容來自筆者學習清華大學和UCSD的操作系統課程的筆記和總結,以及自己的思考和實踐。
磁盤結構簡介:
文件系統是建立在物理磁盤之上的,因此在介紹文件系統之前先簡單介紹磁盤的結構,這樣便於理解后面的相關概念。本文對磁盤的介紹可能比較粗略,感興趣的讀者可以參考《硬盤的讀寫原理》這一篇文章,寫的非常詳盡。先來一張結構示意圖:
相關術語解釋如下:
- 磁盤面(platter):相互平行的存儲介質
- 磁頭(Heads):每個磁頭對應一個磁盤面,負責該磁盤面上的數據的讀寫。
- 磁道(Track):每個盤面會圍繞圓心划分出多個同心圓圈,每個圓圈叫做一個磁道。
- 柱面(Cylinders):所有盤片上的同一位置的磁道組成的立體叫做一個柱面。
- 扇區(Sector):以磁道為單位管理磁盤仍然太大,所以計算機前輩們又把每個磁道划分出了多個扇區
- 磁頭臂(arm):驅動磁頭的移動
邏輯上,磁盤被分成一個個扇區,扇區是磁盤訪問的最基本單位,如果所示,一塊區域由cylinder, head,sector三塊區域組成(CHS)。早期的操作系統需要知道磁盤的所有參數才能讀寫數據,而現在的磁盤越來越復雜,扇區可能還有不同的大小。因此,現在的磁盤需要提供更高階的接口,將磁盤的容量宦維城一組邏輯的塊(blocks),操作系統直接對這些邏輯塊進行操作。
一次磁盤的讀寫可能需要經歷以下三個步驟,因此讀寫性能也取決於這三部分的速度:
- 尋道(seek):磁頭移動定位到指定磁道,速度非常慢
- 旋轉延遲(rotation):等待指定扇區從磁頭下旋轉經過,速度比較慢
- 數據傳輸(transfer):數據在磁盤與內存之間的實際傳輸,速度比較快

什么是文件系統:
首先得知道什么文件,文件是具有符號名,由字節序列組成的數據項集合,是文件系統的基本數據單位。平常提到文件的時候,基本上只關注文件的內容,但是文件本身也有很多信息,比如文件名、類型、位置、大小、權限控制、創建時間、修改時間等,這些信息成為文件的元數據(meta data),在unix系統中個,元數據記錄在inode中。
文件系統是操作系統中管理文件的子系統,提供文件數據存儲和訪問功能,具體來說,文件系統應具備以下功能:
- 分配文件磁盤空間:需要管理已經分配的文件塊,包括位置與順序;管理所有的空閑空間;用一定的分配算法為新的文件分配空間
- 管理文件集合:以某種組織方式結構化文件的信息,以便能夠通過名字找到文件,並讀取文件的內容
- 數據可靠與安全:首先是提供不同的手段,多層次保護數據安全,比如訪問權限控制。通過持久保存文件,避免系統崩潰錯誤、冗余來保證文件的可靠。
操作系統中一般都是用分層的文件系統,即文件以目錄的形式組織,目錄里面也可以包含子目錄,這樣,整個文件系統就形成了一棵樹形結構。基於這種分層結構,文件的名字解析(用邏輯名字到物理資源)就比較簡單了,根據文件的完整路徑,遍歷文件目錄直到找到目標文件。
由於歷史和使用環境的差異,存在着各種不同的磁盤文件系統,比如FAT、NTFS、EXT2、EXT3;還有網絡分布式文件系統,比如NFS、SMB,不同的文件系統,作用不同,安全要求不同,優化的目標也不同。面對有多種不同的文件系統,又需要向上層提供一致的接口,因此引入了虛擬文件系統(virtual file system)。分區(即一個虛擬文件系統)與磁盤並不是一一對應的關系,一個磁盤可以對應多個分區,如LVM;多個磁盤也可以對應一個分區,如RAID
操作系統會為每個進程維護一個打開的文件表,為每一個打開的文件分配套一個唯一的標志,即文件描述符(file descriptor)。打開文件表用文件描述符做索引,對應的項維護打開文件的狀態和相關信息,比如:
- 文件指針:最近一次讀寫位置,寫過C語言的同學應該不陌生。雖然一個文件可以被多個進程同事打開,但每個進程分別維護自己的文件指針
- 文件打開計數:當前打開文件的次數,當最后一個進程關閉文件時,將其從打開文件表中移除??
- 文件的磁盤信息:操作系統會把一部分訪問的文件內容緩存在內存當中,以便加速訪問。緩存的數據信息也會記錄
- 訪問權限:每個進程的文件訪問模式信息,打開文件時的選項,只讀、只寫、還是可讀可寫
在用戶的角度,文件是持久化的數據結構。而在操作系統的角度,文件是數據塊(block)的集合,操作系統以快為單位對文件數據進行讀寫,即使只需讀寫一個字段,也需要讀取一個塊(大小為1k、2k或者4k),這里的塊是邏輯存儲單元,而扇區是物理存儲單元,塊大小不等同於扇區大小,一般來說,若干個扇區構成一個數據塊。
文件分配:
文件分配是指將哪些塊分配給文件來存儲數據,包括數據庫的位置與順序。在這里,塊的大小非常重要,第一:大多數文件都比較小,因此塊空間不能太大,需要對小文件提供很好的支持;第二,一些文件非常大,必須支持大文件,訪問需要高效(如果塊太小,那么大文件需要很多的塊)。衡量分配策略的指標主要有兩個:存儲效率,即外部碎片;讀寫性能,即訪問速度。分配方式有以下三種:
- 連續分配:優點在於訪問效率高,支持高效的順序和隨機訪問。但缺點在於會引入外部碎片,而且對於文件增長問題不是很好處理。
- 鏈式分配:優點在於創建、增大、縮小都容易,而且沒有外部碎片。缺點在於訪問效率低,無法實現真正的隨機訪問。
- 索引分配:優點在於創建、增大、縮小都容易,沒有外部碎片,而且支持直接訪問。缺點在於當文件很小時,存儲索引的開銷,對大文件的處理也是需要考慮的問題
在unix中,使用多級索引分配。
從上圖可以看到:文件頭總共包含13個指針(PS: 上面來自清華大學的課程,按照Inode_pointer_structure,現在的操作系統有15個block pointer),其中
- 前10個指針直接指向數據庫
- 第11個指針指向索引塊i
- 第12個指針指向二級索引快
- 第13個指針指向三級索引快
多級索引分配提高了文件大小限制閾值,而且可以動態分配數據塊,文件的擴展很容易。文件比較小的時候,直接索引;文件比較大的時候,也能處理,只是效率會差一點。
文件共享和訪問控制
在多用戶操作系統中(比如unix),文件的共享是分成有必要的,首先一部分文件對於每個用戶都是相同的(或者默認是情況下是相同的),沒有必要每個用戶都保存一份;其次,用戶之間可能需要協同處理同一份文件。有共享就需要互斥,這個跟進程線程間的同步互斥是一樣的道理,比如對於同一個文件,一個進程在讀,另一個進程在寫,怎么協調,操作系統並不解決多個進程共享文件時候的一致性問題,需要英語程序來規范解決。
訪問控制分為兩個層面,第一個是哪些用戶可以訪問(廣義的訪問,不限於讀寫執行)到文件,第二是怎么訪問這個文件。更一般的抽象的就是某個用戶用什么樣的權限來訪問某個文件,文件(objects)是what,用戶(subjects)是who,方式(actions)是how。從用戶的角度來看,需要維護文件與權限的列表,即Access Control list(ACL);從文件的角度看,需要維護用戶與用戶對該文件的權限的列表,即Capabilities:
對於每一個用戶(subject),都有一個ACL,那么當用戶用來越時且一個文件在很多個用戶之間共享的時候,ACL列表就會變得非常龐大,因此在unix中,提出了group的概念,同一個group里面的用戶對同一個文件共享相同的權限。
文件系統性能:
由於物理設備的制約,磁盤文件的讀寫與內存比起來相差十萬八千里。為了提升文件的讀寫效率,磁盤會根據自身的參數做優化,比如盡量把一個文件(包括元數據信息)放在臨界的扇區。同時,文件系統也會做一些優化,比如:
文件緩存,File buffer cache,將部分文件內容緩存在內存中,由於內存的讀寫比較快,這樣就能極大提高性能。操作系統統一管理緩存,所有進程共享緩存信息,在需要置換的時候也是使用LRU之類的算法。
滯后寫,wtire behind,需要寫操作的時候,並不立即刷到磁盤,而是維護未提交塊的隊列,周期性把隊列內容刷到磁盤上。但問題是不可靠,可能造成數據不一致。
預先讀,read ahead,如果文件系統預測進程會讀取下一個塊,就預先將下一個塊讀到cache中來,這個在連續的文件讀取環境下,十分有效。
RAID:數據可靠性
RAID是Redundant Array of Independent Disks,即獨立磁盤冗余序列。其基本思想就是把多個相對便宜的硬盤組合起來,成為一個硬盤陣列組,使性能達到甚至超過一個價格昂貴、容量巨大的硬盤。根據選擇的版本不同,RAID比單顆硬盤有以下一個或多個方面的好處:增強數據集成度,增強容錯功能,增加處理量或容量。另外,磁盤陣列對於電腦來說,看起來就像一個單獨的硬盤或邏輯存儲單元。下圖是wiki上各種磁盤陣列的比較表:
linux下的文件
linux環境下的文件系統分為三個層級,其基本數據結構如下:
(1)文件卷控制塊:superblock
每一個文件系統一個,在文件系統掛接的時候加載,記錄文件系統的詳細信息,比如塊、塊大小、空閑塊、各種計數和指針。
(2)目錄項:dentry(dictionary entry)
每一個目錄項一個,在遍歷文件的時候加載到內存,包含指向文件控制塊,父目錄、子目錄等信息
(3)文件控制塊:inode
每個文件一個,在文件訪問的時候加載到內存,記錄的就是文件的元數據(meta data)。
在linux環境下,可以使用df(disk free)查看本系統上的文件系統的剩余空間與相應的掛載情況,然后使用dumpe2fs xxx(文件系統名)即查看文件系統信息,包括superblock信息,總共/可用的block inode信息,每個block inode的大小等。
文件的meta data
使用list -l可以查看一部分文件的元數據,不過使用stat命令會更加詳細一些, for example:
比較重要的信息包括:
File type: 文件類型,上圖已經指出為“regular file”, Access的第一位也是用來表明文件類型。
Inode:元數據位置索引,每個文件唯一
Links:有多少個文件名指向這個文件,后面介紹文件鏈接的時候還會提到
Access:訪問權限
atime:access time, 文件的最后訪問時間
mtime: modification time, 文件內容修改時間
ctime: change time, 文件屬性(meta data)修改時間
從上面的截圖可以看到,文件的三個時間屬性可能是不同的值,三者之間可能相互影響,關系如下:
- atime的變化不會影響mtime和ctime
- ctime的變化也不會影響mtime 和atime
- mtime的變化會同時影響到atime和mtime,因為修改文件必定會訪問文件,另外文件內容的大小是文件的元數據
對於文件系統來說,文件的類型也是非常重要的信息,而且操作系統的其他部分也需要知道文件的類型信息。在windows下,通過后綴名來區分文件類型,在unix下,會將文件的類型通過magic number記錄。linux下文件類型通過ls -l 命令第一行即可看到,可能得類型如下:
- 普通文件,第一個字段為-
- 目錄,第一個字段為d
- 管道(PIPE), 第一個字段為p, 用來做進程間通信(IPC)
- 套接字(socket),第一個字段是s,之前已經介紹過,unix domain socket,也是用作進程間通信
- 符號鏈接文件(symbol link), 第一個字段是l,后面會詳細介紹
- 塊設備(block), 第一個字段是b,即磁盤設備
- 字符設備(character), 第一個字段是c,指的是鼠標、鍵盤燈串行設備
其中。普通文件又分為純文本文件、二進制文件等,可通過file命令查看
訪問權限控制
我們都知道對於一個文件,用戶有三種權限:r(可讀)、w(可寫)、x(可執行),對於普通文件來說很好理解,但是對於目錄來說是是你們意思呢,特別是目錄的寫操作和執行?
r: 具有讀取目錄結構列表的權限,比如使用ls(list)
w:具有更改目錄結構列表的權限,不如新建文件、目錄;刪除文件、目錄;重命名等
x:用戶能否進入該目錄成為工作目錄, 比如使用cd
以前就出現過cd失敗的原因,原來是因此這個權限問題。
另外也能在用戶權限位看到s、t等情況,這個具體意義可以參考《Linux特殊權限:SUID、SGID、SBIT》。下面制作簡單總結
- 文件具有SUID的權限時,代表用戶執行此二進制程序的時候,執行過程中用戶文件所有者的權限
- 目錄具有SGID的權限時,代表用戶在這個目錄下新建的文件用戶組都會與該目錄的用戶組相同
- 目錄具有SBIT的權限時,代表用戶在這個目錄下新建的文件只有自己和root能夠刪除
atime & noatime
上面提到 atime是文件的最后訪問時間,我自己測試了一下,用cat訪問文件,但是訪問前后的時間戳並沒有發生變化,就是說,訪問文件並沒有改變atime。於是網上查了一下,原因如下:
相信對性能、優化這些關鍵字有興趣的朋友都知道在 Linux 下面掛載文件系統的時候設置 noatime 可以顯著提高文件系統的性能。默認情況下,Linux ext2/ext3 文件系統在文件被訪問、創建、修改等的時候記錄下了文件的一些時間戳,比如:文件創建時間、最近一次修改時間和最近一次訪問時間。因為系統運行的時候要訪 問大量文件,如果能減少一些動作(比如減少時間戳的記錄次數等)將會顯著提高磁盤 IO 的效率、提升文件系統的性能。Linux 提供了 noatime 這個參數來禁止記錄最近一次訪問時間戳。
在wiki stat上面也有對着問題的描述,稱之為“Criticism_of_atime”,提到關於atime的更新有以下幾種選項:
- strictatime (formerly atime, and formerly the default; strictatime as of 2.6.30) – always update atime, which conforms to the behavior defined by POSIX
- relatime ("relative atime", introduced in 2.6.20 and the default as of 2.6.30) – only update atime under certain circumstances: if the previous atime is older than the mtime or ctime, or the previous atime is over 24 hours in the past
- nodiratime – never update atime of directories, but do update atime of other files
- noatime – never update atime of any file or directory; implies nodiratime; highest performance, but least compatible
- lazytime – update atime according to specific circumstances laid out below
其中noatime表示永遠不更新任何文件和目錄的atime屬性。配置在/etc/fstab。for example:
硬鏈接與符號鏈接:
硬鏈接:多個文件項指同一個文件(同一個inode)
符號鏈接:鏈接文件與被鏈接文件的描述信息(元數據,meta data)各自獨立,只是說鏈接文件里面存儲另一個文件的完整路徑,以此實現文件別名,類似windows中的“快捷方式”。
linux下面使用指令ln src target 即可創建硬鏈接,target指向的是和src同樣的文件,擁有同樣的inode(即meta data)信息。也可以理解為,只是多了一個邏輯名指向某個文件。執行指令:ln Server.log Server_hardlink.log
結合上面介紹meta data時候的截圖,可以看到,創建硬鏈接之后唯一變化的就是Links的數值變成了2,因為現在Server_hardlink.log也指向了這個文件。硬鏈接的好處在於更改的安全性,當我們用rm刪除一個文件的時候,其實只是將文件的links數目減少1,只有當links數目減少到0之后才會真正的刪除文件。
linux下面使用指令ln -s src target 即可創建符號鏈接,如下圖所示,src和target指向的是不同的inode
關於硬鏈接和符號鏈接,《Linux軟鏈接和硬鏈接》這篇文章比較詳盡清楚。
總結:
本文記錄了關於文件系統的一些基礎知識,以及linux中文件系統相關的命令,文章中主要是筆者之前不太清晰的一些知識點,並不全面,感興趣的讀者可以看看《鳥哥私房菜》。