基于B樹的HFS+文件系統(tǒng)
盡管如今的操作系統(tǒng)在驅(qū)動(dòng)程序的幫助下支持任何的文件系統(tǒng),但是每一個(gè)操作系統(tǒng)都會(huì)有一個(gè)自己“原生”的文件系統(tǒng),DOS的原生文件系統(tǒng)是FAT。Windows的原生文件系統(tǒng)是NTFS。Linux的是Ext2/3/4。OS X 也不例外,HFS+ 是OS X 的原生文件系統(tǒng)。
HFS+文件系統(tǒng)概念
時(shí)間戳
HFS+采用一個(gè)無符號(hào)整數(shù)記錄自格林治時(shí)間1904年1月1日零點(diǎn)到現(xiàn)在的秒數(shù)表示的時(shí)間。
訪問控制表
傳統(tǒng)UNIX 在inode層就提供了權(quán)限機(jī)制,然而這些權(quán)限局限性非常大,僅僅是遵循了用戶/組/其他的簡(jiǎn)單模型。通過訪問控制表(ACL)可以精確地設(shè)置系統(tǒng)上任何用戶和任何組的具體權(quán)限,這種方式類似Windows 的權(quán)限系統(tǒng),要注意的是,ACL實(shí)際上是一項(xiàng)VFS的特性,而不是HFS+的特性,然而為了能支持ACL,底層文件系統(tǒng)必須支持?jǐn)U展屬性(HFS+就支持)
擴(kuò)展屬性
文件除了由包含實(shí)際數(shù)據(jù)和權(quán)限信息的塊組成之外,還有額外的屬性信息。這些屬性信息通常稱為擴(kuò)展屬性(extended attribute)。擴(kuò)展屬性一般是透明的,任何人都可以設(shè)置擴(kuò)展屬性,OS X 采用了逆DNS的命名約定以確保屬性名稱的唯一性。
fork
fork 最初是由蘋果發(fā)明的一項(xiàng)概念(在最早的HFS中),后來被微軟用在NTFS中(在NTFS中稱為“交替數(shù)據(jù)流(alternate data stream)”)。一個(gè)fork很像一個(gè)擴(kuò)展屬性,因?yàn)槎伎梢杂糜诒硎绢~外的元數(shù)據(jù),但是更適合于單獨(dú)放在另一個(gè)相關(guān)文件中的數(shù)據(jù)。擴(kuò)展屬性有大小限制,而fork則沒有這樣的限制。OS X 中大量使用資源fork的一個(gè)地方是別名(alias)。別名很好地利用了資源fork。別名被創(chuàng)建(甚至重命名)時(shí),會(huì)有一個(gè)Finder 擴(kuò)展屬性(com.apple.FinderInfo)指定alisMACS,還有一個(gè)資源fork指定原始文件的一些特性,還包括圖標(biāo)。有趣的是,別名文件往往比原文件占用的磁盤空間多。
壓縮
文件壓縮是HFS+最強(qiáng)大的特性之一。壓縮的方式是將數(shù)據(jù)fork留空,然后將壓縮后的數(shù)據(jù)放在資源fork中,并且通過一個(gè)擴(kuò)展屬性com.apple.decmpfs將文件標(biāo)記為壓縮。OS X的程序默默地對(duì)系統(tǒng)文件進(jìn)行實(shí)時(shí)解壓縮操作,而且擴(kuò)展屬性工具xattr(1)會(huì)自動(dòng)忽略用于壓縮的擴(kuò)展屬性com.apple.defcmpfs。內(nèi)核通過特殊的AppleFSCompressionTypeZlib.kext擴(kuò)展支持實(shí)時(shí)壓縮功能。HFS+壓縮的過程如下:
- 文件被當(dāng)成是64K大小數(shù)據(jù)塊的數(shù)組
- 小文件通過Typel 壓縮,數(shù)據(jù)以未壓縮的形式保存在擴(kuò)展屬性中
- 較大的文件如果仍然在一個(gè)塊內(nèi)可以放進(jìn)com.apple.decmpfs擴(kuò)展屬性,則保存在擴(kuò)展屬性中
- 所有其他較大的文件都被壓縮,并保存在文件的資源fork中。注意在這種情況下,文件自己本身可能沒有資源fork
- 擴(kuò)展屬性和資源fork被添加到文件中
- 實(shí)際的文件大小重編碼為0,然后通過chflags(2)將文件標(biāo)記為壓縮
Unicode 支持
HFS+ 通過Unicode解決了國(guó)際化的問題。Unicode有很多變體,HFS+采用的是UTF-16編碼:雙字節(jié)Unicode。文件名的長(zhǎng)度最長(zhǎng)可達(dá)255個(gè)字符(510個(gè)字節(jié))。HFS+內(nèi)部使用的數(shù)據(jù)結(jié)構(gòu)為HFSUniStr255。
Finder 集成
HFS+ 和 OS X Finder結(jié)合很緊密。宗卷頭和單個(gè)的編錄條目中都有一個(gè)特殊的Finder信息字段,其中包含了由Finder使用的標(biāo)志位。具體的內(nèi)容取決于是文件還是文件夾。
大小寫敏感(HFSX)
文件系統(tǒng)可以定義為大小寫不敏感或大小寫敏感,區(qū)別在于比較文件名時(shí)是否考慮字母的大小寫。此外,盡管文件系統(tǒng)可以是大小寫不敏感的,但是仍然可以保留大小寫:即文件名保存時(shí)按照傳入的大小寫原樣保存,而且在后續(xù)操作中依然保存原始的大小寫狀態(tài)。HFS+是大小寫不敏感,但是保留大小寫的文件系統(tǒng)。OS X 還支持一種新的變種 HFSX,可以設(shè)置為大小寫敏感。OS X 默認(rèn)使用 HFS+。iOS 使用啟用了大小寫敏感的HFSX。大小寫保留(HFS+)和大小寫敏感(HFSX)的決定只能在分區(qū)時(shí)進(jìn)行一次(通過Disk Utility 應(yīng)用程序或命令行程序diskutil(8)進(jìn)行分區(qū)操作)。因?yàn)檫@個(gè)決定會(huì)影響編錄樹種的順序
日志
文件事務(wù)可以異常復(fù)雜,特別是寫操作可能會(huì)跨越多個(gè)數(shù)據(jù)塊。在斷電或其他崩潰的情況下,如果一個(gè)寫操作事務(wù)中只有部分?jǐn)?shù)據(jù)寫入了底層媒體,那么這種寫操作會(huì)導(dǎo)致數(shù)據(jù)損壞。日志(journaling)是一項(xiàng)試圖解決這個(gè)問題的技術(shù)。日記是磁盤中一塊特殊的區(qū)域,用戶看不見這個(gè)區(qū)域,文件系統(tǒng)在向磁盤提交事務(wù)之前會(huì)將事務(wù)記錄在這個(gè)區(qū)域中。如果修改事務(wù)被成功提交,那么這些事務(wù)就會(huì)在日志中刪除。但是如果發(fā)生了崩潰,文件系統(tǒng)可以快速恢復(fù)到一致的狀態(tài):要么重放日志(即提交所有記錄的事務(wù)),要么回滾日志(如果包含未完成的事務(wù))。日志并不能完美解決數(shù)據(jù)丟失,但是可以顯著地減少系統(tǒng)崩潰導(dǎo)致文件系統(tǒng)無法使用的情況。現(xiàn)代的文件系統(tǒng),例如Linux 的Ext3 和微軟的NTFS都是支持日志的。HFS+掛載時(shí)可以選中支持或不支持日志,記錄日志是默認(rèn)選項(xiàng),不過基于SSD的Mac可能會(huì)通過禁用日志獲得一些好處(因?yàn)椴脸罩镜牟僮鲿?huì)縮短底層閃存芯片的壽命)
動(dòng)態(tài)大小調(diào)節(jié)
HFS+宗卷的大小可以動(dòng)態(tài)調(diào)整,即使是在宗卷處于掛載的狀態(tài)。這是一項(xiàng)高級(jí)功能,有一些文件系統(tǒng)不支持這項(xiàng)功能(例如XFS只支持增大但是不支持縮小)。HFS+的大小調(diào)整是通過hfs_extendfs( )完成的,在用戶態(tài)可以通過ioctl(2)傳入HFS_RESIZE_VOLUME操作、sysctl(2)傳入HFS_EXTEND_FS操作以及在Disk Unility圖形界面中調(diào)整HFS+分區(qū)右下角的把手對(duì)HFS+分區(qū)大小進(jìn)行調(diào)節(jié)
元數(shù)據(jù)區(qū)域
元數(shù)據(jù)區(qū)域(metadata zone)是OS X 10.3引入的。元數(shù)據(jù)區(qū)域在系統(tǒng)卷后面,包含了文件系統(tǒng)的內(nèi)部結(jié)構(gòu)(靠著熱文件區(qū)域)。這個(gè)區(qū)域故意安排在宗卷開頭處,這樣可以減少定位時(shí)間。在滿足以下條件時(shí),hfs_metadatazone_init( )函數(shù)會(huì)開啟元數(shù)據(jù)區(qū)域:
- 宗卷大小至少為10GB
- 宗卷開啟了日志
- 調(diào)用者沒有顯式地要求禁用這個(gè)區(qū)域(通過fsctl)
元數(shù)據(jù)區(qū)域內(nèi)禁止分配普通的文件(除非系統(tǒng)中數(shù)據(jù)塊異常短缺)。這個(gè)區(qū)域包含了文件系統(tǒng)使用的文件和數(shù)據(jù)結(jié)構(gòu)。其中hfs_virturalmetafile( )函數(shù)的作用是查找一個(gè)文件是否屬于元數(shù)據(jù)區(qū)域。
熱文件
HFS+有一項(xiàng)很有意思很特別的特性是能夠動(dòng)態(tài)適應(yīng)頻繁訪問的文件。HFS+為每一個(gè)文件維護(hù)一個(gè)熱度值(temperature)。熱度值是通過文件被讀取的字節(jié)數(shù)除以文件大小得到(向下轉(zhuǎn)換為unit32_t值)。這個(gè)計(jì)算得到的值和文件大小成反比,因此熱度更傾向于小文件,小文件的內(nèi)容經(jīng)常被頻繁訪問。熱度值超過了HFC_MINIMUM_TEMPERATURE的文件成為“熱文件”,會(huì)被添加到元數(shù)據(jù)區(qū)域中的一個(gè)特殊B樹中,這個(gè)B樹維護(hù)了最多HFC_MAXIMUM_FILE_COUNT個(gè)條目,這些熱文件的數(shù)據(jù)塊也被轉(zhuǎn)移到了元數(shù)據(jù)區(qū)域。熱文件B樹是一個(gè)普通的文件,由hfc_create( )創(chuàng)建,這個(gè)文件設(shè)置FndrFileInfo標(biāo)志位(kIsInvisible+kNameLocked),因此這個(gè)文件的文件名無法被修改,而且在Finder中看不見。
動(dòng)態(tài)碎片整理
文件碎片化是所有文件系統(tǒng)的噩夢(mèng):隨著系統(tǒng)不斷創(chuàng)建、修改和刪除文件,文件被刪除的位置開始出現(xiàn)“空洞”,當(dāng)文件需要擴(kuò)大而沒有連續(xù)的空間時(shí),就會(huì)產(chǎn)生文件碎片。盡管文件系統(tǒng)中存在足夠的空間,但是如果這些空間都被分割為零散小空間,那么文件分配就不會(huì)特別高效。HFS+能夠在工作時(shí)進(jìn)行碎片整理工作。hfs_relocate( )函數(shù)會(huì)處理這些情況。hfs_vnop( )嘗試重新安置被認(rèn)為是嚴(yán)重碎片化的文件。將熱文件移入或移出元數(shù)據(jù)區(qū)域也能夠幫助碎片整理,因?yàn)槲募囊苿?dòng)是通過調(diào)用hfs_relocate( )完成的。
HFS+的設(shè)計(jì)概念
HFS+中的“+”意味著HFS+是前一代(層次文件系統(tǒng)(HFS))的增強(qiáng)版。HFS的設(shè)計(jì)在HFS+中并沒有重大改變。這兩個(gè)文件系統(tǒng)底層的思想是一致的。HFS+的主要改進(jìn)是增加了字段和記錄的大小,從而支持更多的文件,支持的文件大小也更大。
B樹基礎(chǔ)知識(shí)
B樹(B-Tree)是一些文件系統(tǒng)構(gòu)建的基礎(chǔ)。例如NTFS(Windows)、Ext4(Linux)以及蘋果的NFS以及NFS+。
采用B樹的動(dòng)機(jī)
任何文件系統(tǒng)中最基本的概念就是用于保存和取得文件的機(jī)制。文件系統(tǒng)需要提供一種機(jī)制能夠滿足一下運(yùn)行時(shí)的需求:
- 搜索
- 插入
- 更新
- 隨機(jī)訪問
盡管有一些文件系統(tǒng)依然采用基于分配表的方式(例如FAT、FAT32以及最近的ExFat都是采用“文件分配表”的文件系統(tǒng)),但是大部分文件系統(tǒng)都 采用了基于樹的方案。
B樹可以看出是二叉樹的擴(kuò)展,相似的地方在于都采用樹形結(jié)構(gòu),而不同的地方在于B書的節(jié)點(diǎn)可以有任意數(shù)據(jù)的子節(jié)點(diǎn)。這種結(jié)構(gòu)可以幫助限制樹的深度。
B樹節(jié)點(diǎn)
B樹由節(jié)點(diǎn)(node)組成,B樹的節(jié)點(diǎn)可以有具體的子類型或稱為kind。不同的節(jié)點(diǎn)類型可以保存不同的數(shù)據(jù),但是所有類型的節(jié)點(diǎn)都來源于一個(gè)基本類型(基類),所有節(jié)點(diǎn)類型都采用同一套基本結(jié)構(gòu):一個(gè)節(jié)點(diǎn)描述符,后面在跟著0個(gè)或多個(gè)其他記錄。所有節(jié)點(diǎn)類型的描述符都是完全相同的。記錄本身的內(nèi)容取決于包含它們的節(jié)點(diǎn)類型。內(nèi)部節(jié)點(diǎn)包含索引記錄,索引記錄指向子節(jié)點(diǎn),而葉子節(jié)點(diǎn)包含實(shí)際的數(shù)據(jù),然而這兩種節(jié)點(diǎn)的記錄都是鍵值記錄,采用了相同的一般性記錄格式:首先是一個(gè)鍵,然后緊跟著數(shù)據(jù)。鍵必須以遞增的順序保存,而且必須唯一。
B樹頭節(jié)點(diǎn)
HFS+的B樹起點(diǎn)并不是根節(jié)點(diǎn),而是一個(gè)特殊的節(jié)點(diǎn):頭節(jié)點(diǎn)(header node),這個(gè)節(jié)點(diǎn)的節(jié)點(diǎn)類型為kBTHeaderNode(1)。頭節(jié)點(diǎn)一直存在,即使樹本身為空。頭節(jié)點(diǎn)剛好包含了3條記錄,這些記錄是不通過鍵索引的記錄。頭記錄(header record)包含了整個(gè)樹的元數(shù)據(jù)。由于頭記錄緊跟在描述符后面,因此其第一個(gè)字段(treeDepth,表示樹種的層次樹)是一個(gè)16位的值。HFS+的B樹總是有一個(gè)固定的深度。也就是說,所有的葉子節(jié)點(diǎn)都在同一層上。深度由treeDepth字段定義的,通過ID可以快速查詢節(jié)點(diǎn)。頭記錄之后是用戶數(shù)據(jù)記錄(User Data Record):也是128字節(jié)長(zhǎng),目前這個(gè)記錄是預(yù)留的記錄,唯一用到這個(gè)記錄的B樹是熱文件B樹。頭節(jié)點(diǎn)中最后一條記錄是映射記錄(Map Record)。映射記錄占用了節(jié)點(diǎn)剩下的所有空間。
組件
HFS+使用了6個(gè)特殊的文件來維護(hù)自己的數(shù)據(jù)。其中有四個(gè)文件是B樹:
- 編錄(catalog)B樹:包含文件系統(tǒng)中的所有文件
- 屬性B樹:HFS+中新增的,用于支持文件擴(kuò)展屬性
- extent 溢出(overflow)B樹:用于超過8個(gè)碎片(或extent)的文件(一個(gè)extent表示一組連續(xù)的分配塊)
- 熱文件B樹:用于頻繁訪問的小文件
- 分配文件:包含一個(gè)記錄文件系統(tǒng)中所有數(shù)據(jù)塊使用情況的位圖
- 啟動(dòng)文件:一個(gè)簡(jiǎn)單的可執(zhí)行文件,用于引導(dǎo)操作系統(tǒng)。OS X 基本忽略這個(gè)文件,但是其他操作系統(tǒng)可以使用。
當(dāng)HFS+掛載時(shí)啟用了日志功能,那么還會(huì)啟用一個(gè)日志文件。所有這些組件(包括日志文件,但除去啟動(dòng)文件)都保存在元數(shù)據(jù)區(qū)域中。如果在宗卷上啟用了磁盤配額,那么還會(huì)在元數(shù)據(jù)區(qū)域中保存用于支持磁盤配額的文件。
HFS+宗卷頭
系統(tǒng)在開始對(duì)各種B樹操作之前,必須能夠找到這些B樹在什么位置,并且還要識(shí)別HFS+文件系統(tǒng)本身的身份。為此,在一個(gè)固定的位置(從分區(qū)(即“宗卷”))開頭器1024字節(jié)的位置有一個(gè)巨大的數(shù)據(jù)結(jié)構(gòu)(512字節(jié)),這個(gè)數(shù)據(jù)結(jié)構(gòu)(即宗卷頭)包含了操作系統(tǒng)加載操作初始化所需要的所有必要的細(xì)節(jié)信息。
這個(gè)宗卷頭目前也是HFS+和HFSX的主要區(qū)別所在:兩個(gè)系統(tǒng)的宗卷頭就一致,只有一下3點(diǎn)不同:
- HFSX使用簽名HX,而HFS+使用的是H+
- HFSX將版本號(hào)設(shè)置為5,而HFS+的版本號(hào)設(shè)置為4
- HFSX的B樹提供了一個(gè)選項(xiàng)用于選中比較鍵的方法:二進(jìn)制比較或大寫轉(zhuǎn)換后比較
HFS+宗卷頭編錄非常重要,因此在宗卷頭尾部的替補(bǔ)宗卷頭(Alternate Volume Header)中也有備份,替補(bǔ)宗卷頭在最尾部向前1024字節(jié)處,剛好占用了512字節(jié),因此宗卷的最后512字節(jié)是沒有使用且保存預(yù)留的。
編錄文件
HFS+文件系統(tǒng)中最主要的B樹就是編錄文件了。編錄文件包含文件系統(tǒng)中所有的文件和文件夾的條目。系統(tǒng)的所有文件操作都會(huì)使用這個(gè)B樹:列出文件、搜索文件、讀取文件、寫入文件以及刪除文件。
由于編錄文件是一個(gè)B樹,因此編錄文件集成了HFS+ B樹的結(jié)構(gòu)和所有屬性。編錄文件還有一些新的屬性:
- Catalog Node ID(編錄節(jié)點(diǎn)ID,即CNID):是文件或文件夾唯一的32位標(biāo)識(shí)符
- 編錄文件鍵定義一個(gè)結(jié)構(gòu)體
-
編錄文件中可能包含的4種不同的記錄類別:
- kHFSPlusFileRecord類型以HFSPlusCatalogFolder的形式保存文件夾數(shù)據(jù)
- kHFSPlusFileRecord類型以HFSPlusCatalogFile的形式保存文件數(shù)據(jù)
- kHFSPlusFolderThreadRecord以及kHFSPlusFileThreadRecored用于保存“thread(線索)”這兩種請(qǐng)求下,線索是類型都為HFSPlusCatalogThread
編錄查找
編錄查找分為兩種類型:
- 根據(jù)文件或文件夾名查找
- 根據(jù)CNID查找
硬連接和軟連接
與其他任何UNIX文件系統(tǒng)一樣,HFS+支持硬連接和軟連接。然而其底層實(shí)現(xiàn)卻很特別。
硬連接和軟連接是通過userInfo字段中的fdType字段區(qū)分的。對(duì)于硬連接,這個(gè)字段的值是一個(gè)魔數(shù)0x686c6e6b(hlnk),對(duì)于軟連接,這個(gè)字段的值是另一個(gè)魔數(shù)0x736c6e6b(slnk)。在這兩個(gè)情況下,fdCreator代碼都是hfs+。
對(duì)于軟連接,特殊處理到此為止:軟連接也不過是普通文件,只是文件內(nèi)容中包含了文件系統(tǒng)中另一個(gè)文件的名字。
對(duì)于硬連接,系統(tǒng)則需要特殊處理。創(chuàng)建硬連接時(shí),底層文件的fork被重新安置了(甚至可以說是隱藏在文件系統(tǒng)中,某個(gè)私有隱秘的位置中。HFS+努力保持這個(gè)文件夾的隱藏和不可訪問狀態(tài)。通過UNIX工具無法看到這個(gè)文件夾(因?yàn)檫@個(gè)文件夾名字以NULL開頭,NULL終止了C字符串)),F(xiàn)inder 也無法看到這個(gè)文件夾(Finder 要遵循kIsInvisible和kNameLocked標(biāo)志位的約束)
硬連接的dentry和普通文件一樣保存在對(duì)應(yīng)的位置,但是這些文件的資源fork被設(shè)置為0。相反,bsdInfo中有一個(gè)“special”字段被設(shè)置為文件的inode編號(hào),而這個(gè)編號(hào)可以從\0\0\0\0HFS+ Priviate Data 文件夾中獲得。
fork分配
文件記錄提供了兩個(gè)HFSPlusForData結(jié)構(gòu)體:一個(gè)用于資源fork; 另一個(gè)用于數(shù)據(jù)fork。根據(jù)前文的描述,HFS+可以支持任意數(shù)目的fork(通過后文要描述的屬性樹),不過如果真的使用了fork,一般只有數(shù)據(jù)fork被使用了。
extent 溢出文件
大部分文件都剛好適合不超過8個(gè)extent。超過8個(gè)extent的文件被認(rèn)為是嚴(yán)重碎片化,但是文件系統(tǒng)依然應(yīng)該為這種文件提供服務(wù)。為此,文件系統(tǒng)維護(hù)了另外一個(gè)B樹:extent溢出B樹。extent 溢出B樹比編錄文件的B樹要簡(jiǎn)單得多。和編錄文件的不同之處在于,extent 溢出B樹不含多個(gè)索引的記錄:只包含葉子節(jié)點(diǎn)。
屬性B樹
屬性B樹是HFS+使用的另一個(gè)B樹。HFS+通過屬性B樹保存各種擴(kuò)展屬性。大部分情況下,用戶態(tài)應(yīng)用程序都不需要關(guān)系這個(gè)B樹,因?yàn)橥ㄟ^系統(tǒng)調(diào)用listattr(2)、getattr(2)和setattr(2)可以分別列出、獲得和設(shè)置屬性。hfsleuth工具可以直接讀取屬性B樹所有的屬性
熱文件B樹
熱文件B樹的記錄是通過temperature 和 fileID(即目標(biāo)熱文件的CNID)索引的。由于temperature值是系統(tǒng)需要非常頻繁查詢的值,因此系統(tǒng)可以將搜索鍵的temperature設(shè)置為HFC_LOOKUPTAG。
分配文件
分配文件是一個(gè)非常大,而且無法訪問的文件,負(fù)責(zé)跟蹤卷中所有塊的分配情況。由于分配文件本身也是一個(gè)文件,因此可能會(huì)碎片化。如果宗卷被擴(kuò)大了,那么分配文件也會(huì)增長(zhǎng),因此實(shí)現(xiàn)了非常可擴(kuò)展的方案。分配文件通常是連續(xù)的,而且包含在單獨(dú)一個(gè)extern中,因?yàn)榉峙湮募窃趧?chuàng)建文件系統(tǒng)時(shí)通過mkfs呈現(xiàn)創(chuàng)建的。此外文件系統(tǒng)分配塊大小的動(dòng)態(tài)變化也是比較容易實(shí)現(xiàn)的。
HFS 日志
在HFS+ 中,日志是一項(xiàng)可以隨意開關(guān)的特性,不過默認(rèn)情況下是開啟的。在加載一個(gè)文件系統(tǒng)時(shí),HFS+檢查宗卷頭中l(wèi)astMountedVersion 字段的值。