深入解析Mac OS X & iOS 操作系统 学习笔记(十七)

基于B树的HFS+文件系统

尽管如今的操作系统在驱动程序的帮助下支持任何的文件系统,但是每一个操作系统都会有一个自己“原生”的文件系统,DOS的原生文件系统是FAT。Windows的原生文件系统是NTFS。Linux的是Ext2/3/4。OS X 也不例外,HFS+ 是OS X 的原生文件系统。

HFS+文件系统概念

时间戳

HFS+采用一个无符号整数记录自格林治时间1904年1月1日零点到现在的秒数表示的时间。

访问控制表

传统UNIX 在inode层就提供了权限机制,然而这些权限局限性非常大,仅仅是遵循了用户/组/其他的简单模型。通过访问控制表(ACL)可以精确地设置系统上任何用户和任何组的具体权限,这种方式类似Windows 的权限系统,要注意的是,ACL实际上是一项VFS的特性,而不是HFS+的特性,然而为了能支持ACL,底层文件系统必须支持扩展属性(HFS+就支持)

扩展属性

文件除了由包含实际数据和权限信息的块组成之外,还有额外的属性信息。这些属性信息通常称为扩展属性(extended attribute)。扩展属性一般是透明的,任何人都可以设置扩展属性,OS X 采用了逆DNS的命名约定以确保属性名称的唯一性。

fork

fork 最初是由苹果发明的一项概念(在最早的HFS中),后来被微软用在NTFS中(在NTFS中称为“交替数据流(alternate data stream)”)。一个fork很像一个扩展属性,因为都可以用于表示额外的元数据,但是更适合于单独放在另一个相关文件中的数据。扩展属性有大小限制,而fork则没有这样的限制。OS X 中大量使用资源fork的一个地方是别名(alias)。别名很好地利用了资源fork。别名被创建(甚至重命名)时,会有一个Finder 扩展属性(com.apple.FinderInfo)指定alisMACS,还有一个资源fork指定原始文件的一些特性,还包括图标。有趣的是,别名文件往往比原文件占用的磁盘空间多。

压缩

文件压缩是HFS+最强大的特性之一。压缩的方式是将数据fork留空,然后将压缩后的数据放在资源fork中,并且通过一个扩展属性com.apple.decmpfs将文件标记为压缩。OS X的程序默默地对系统文件进行实时解压缩操作,而且扩展属性工具xattr(1)会自动忽略用于压缩的扩展属性com.apple.defcmpfs。内核通过特殊的AppleFSCompressionTypeZlib.kext扩展支持实时压缩功能。HFS+压缩的过程如下:

  • 文件被当成是64K大小数据块的数组
  • 小文件通过Typel 压缩,数据以未压缩的形式保存在扩展属性中
  • 较大的文件如果仍然在一个块内可以放进com.apple.decmpfs扩展属性,则保存在扩展属性中
  • 所有其他较大的文件都被压缩,并保存在文件的资源fork中。注意在这种情况下,文件自己本身可能没有资源fork
  • 扩展属性和资源fork被添加到文件中
  • 实际的文件大小重编码为0,然后通过chflags(2)将文件标记为压缩
Unicode 支持

HFS+ 通过Unicode解决了国际化的问题。Unicode有很多变体,HFS+采用的是UTF-16编码:双字节Unicode。文件名的长度最长可达255个字符(510个字节)。HFS+内部使用的数据结构为HFSUniStr255。

Finder 集成

HFS+ 和 OS X Finder结合很紧密。宗卷头和单个的编录条目中都有一个特殊的Finder信息字段,其中包含了由Finder使用的标志位。具体的内容取决于是文件还是文件夹。

大小写敏感(HFSX)

文件系统可以定义为大小写不敏感或大小写敏感,区别在于比较文件名时是否考虑字母的大小写。此外,尽管文件系统可以是大小写不敏感的,但是仍然可以保留大小写:即文件名保存时按照传入的大小写原样保存,而且在后续操作中依然保存原始的大小写状态。HFS+是大小写不敏感,但是保留大小写的文件系统。OS X 还支持一种新的变种 HFSX,可以设置为大小写敏感。OS X 默认使用 HFS+。iOS 使用启用了大小写敏感的HFSX。大小写保留(HFS+)和大小写敏感(HFSX)的决定只能在分区时进行一次(通过Disk Utility 应用程序或命令行程序diskutil(8)进行分区操作)。因为这个决定会影响编录树种的顺序

日志

文件事务可以异常复杂,特别是写操作可能会跨越多个数据块。在断电或其他崩溃的情况下,如果一个写操作事务中只有部分数据写入了底层媒体,那么这种写操作会导致数据损坏。日志(journaling)是一项试图解决这个问题的技术。日记是磁盘中一块特殊的区域,用户看不见这个区域,文件系统在向磁盘提交事务之前会将事务记录在这个区域中。如果修改事务被成功提交,那么这些事务就会在日志中删除。但是如果发生了崩溃,文件系统可以快速恢复到一致的状态:要么重放日志(即提交所有记录的事务),要么回滚日志(如果包含未完成的事务)。日志并不能完美解决数据丢失,但是可以显著地减少系统崩溃导致文件系统无法使用的情况。现代的文件系统,例如Linux 的Ext3 和微软的NTFS都是支持日志的。HFS+挂载时可以选中支持或不支持日志,记录日志是默认选项,不过基于SSD的Mac可能会通过禁用日志获得一些好处(因为擦除日志的操作会缩短底层闪存芯片的寿命)

动态大小调节

HFS+宗卷的大小可以动态调整,即使是在宗卷处于挂载的状态。这是一项高级功能,有一些文件系统不支持这项功能(例如XFS只支持增大但是不支持缩小)。HFS+的大小调整是通过hfs_extendfs( )完成的,在用户态可以通过ioctl(2)传入HFS_RESIZE_VOLUME操作、sysctl(2)传入HFS_EXTEND_FS操作以及在Disk Unility图形界面中调整HFS+分区右下角的把手对HFS+分区大小进行调节

元数据区域

元数据区域(metadata zone)是OS X 10.3引入的。元数据区域在系统卷后面,包含了文件系统的内部结构(靠着热文件区域)。这个区域故意安排在宗卷开头处,这样可以减少定位时间。在满足以下条件时,hfs_metadatazone_init( )函数会开启元数据区域:

  • 宗卷大小至少为10GB
  • 宗卷开启了日志
  • 调用者没有显式地要求禁用这个区域(通过fsctl)

元数据区域内禁止分配普通的文件(除非系统中数据块异常短缺)。这个区域包含了文件系统使用的文件和数据结构。其中hfs_virturalmetafile( )函数的作用是查找一个文件是否属于元数据区域。

热文件

HFS+有一项很有意思很特别的特性是能够动态适应频繁访问的文件。HFS+为每一个文件维护一个热度值(temperature)。热度值是通过文件被读取的字节数除以文件大小得到(向下转换为unit32_t值)。这个计算得到的值和文件大小成反比,因此热度更倾向于小文件,小文件的内容经常被频繁访问。热度值超过了HFC_MINIMUM_TEMPERATURE的文件成为“热文件”,会被添加到元数据区域中的一个特殊B树中,这个B树维护了最多HFC_MAXIMUM_FILE_COUNT个条目,这些热文件的数据块也被转移到了元数据区域。热文件B树是一个普通的文件,由hfc_create( )创建,这个文件设置FndrFileInfo标志位(kIsInvisible+kNameLocked),因此这个文件的文件名无法被修改,而且在Finder中看不见。

动态碎片整理

文件碎片化是所有文件系统的噩梦:随着系统不断创建、修改和删除文件,文件被删除的位置开始出现“空洞”,当文件需要扩大而没有连续的空间时,就会产生文件碎片。尽管文件系统中存在足够的空间,但是如果这些空间都被分割为零散小空间,那么文件分配就不会特别高效。HFS+能够在工作时进行碎片整理工作。hfs_relocate( )函数会处理这些情况。hfs_vnop( )尝试重新安置被认为是严重碎片化的文件。将热文件移入或移出元数据区域也能够帮助碎片整理,因为文件的移动是通过调用hfs_relocate( )完成的。

HFS+的设计概念

HFS+中的“+”意味着HFS+是前一代(层次文件系统(HFS))的增强版。HFS的设计在HFS+中并没有重大改变。这两个文件系统底层的思想是一致的。HFS+的主要改进是增加了字段和记录的大小,从而支持更多的文件,支持的文件大小也更大。

B树基础知识

B树(B-Tree)是一些文件系统构建的基础。例如NTFS(Windows)、Ext4(Linux)以及苹果的NFS以及NFS+。

采用B树的动机

任何文件系统中最基本的概念就是用于保存和取得文件的机制。文件系统需要提供一种机制能够满足一下运行时的需求:

  • 搜索
  • 插入
  • 更新
  • 随机访问

尽管有一些文件系统依然采用基于分配表的方式(例如FAT、FAT32以及最近的ExFat都是采用“文件分配表”的文件系统),但是大部分文件系统都 采用了基于树的方案。
B树可以看出是二叉树的扩展,相似的地方在于都采用树形结构,而不同的地方在于B书的节点可以有任意数据的子节点。这种结构可以帮助限制树的深度。

B树节点

B树由节点(node)组成,B树的节点可以有具体的子类型或称为kind。不同的节点类型可以保存不同的数据,但是所有类型的节点都来源于一个基本类型(基类),所有节点类型都采用同一套基本结构:一个节点描述符,后面在跟着0个或多个其他记录。所有节点类型的描述符都是完全相同的。记录本身的内容取决于包含它们的节点类型。内部节点包含索引记录,索引记录指向子节点,而叶子节点包含实际的数据,然而这两种节点的记录都是键值记录,采用了相同的一般性记录格式:首先是一个键,然后紧跟着数据。键必须以递增的顺序保存,而且必须唯一。

B树头节点

HFS+的B树起点并不是根节点,而是一个特殊的节点:头节点(header node),这个节点的节点类型为kBTHeaderNode(1)。头节点一直存在,即使树本身为空。头节点刚好包含了3条记录,这些记录是不通过键索引的记录。头记录(header record)包含了整个树的元数据。由于头记录紧跟在描述符后面,因此其第一个字段(treeDepth,表示树种的层次树)是一个16位的值。HFS+的B树总是有一个固定的深度。也就是说,所有的叶子节点都在同一层上。深度由treeDepth字段定义的,通过ID可以快速查询节点。头记录之后是用户数据记录(User Data Record):也是128字节长,目前这个记录是预留的记录,唯一用到这个记录的B树是热文件B树。头节点中最后一条记录是映射记录(Map Record)。映射记录占用了节点剩下的所有空间。

组件

HFS+使用了6个特殊的文件来维护自己的数据。其中有四个文件是B树:

  • 编录(catalog)B树:包含文件系统中的所有文件
  • 属性B树:HFS+中新增的,用于支持文件扩展属性
  • extent 溢出(overflow)B树:用于超过8个碎片(或extent)的文件(一个extent表示一组连续的分配块)
  • 热文件B树:用于频繁访问的小文件
  • 分配文件:包含一个记录文件系统中所有数据块使用情况的位图
  • 启动文件:一个简单的可执行文件,用于引导操作系统。OS X 基本忽略这个文件,但是其他操作系统可以使用。

当HFS+挂载时启用了日志功能,那么还会启用一个日志文件。所有这些组件(包括日志文件,但除去启动文件)都保存在元数据区域中。如果在宗卷上启用了磁盘配额,那么还会在元数据区域中保存用于支持磁盘配额的文件。

HFS+宗卷头

系统在开始对各种B树操作之前,必须能够找到这些B树在什么位置,并且还要识别HFS+文件系统本身的身份。为此,在一个固定的位置(从分区(即“宗卷”))开头器1024字节的位置有一个巨大的数据结构(512字节),这个数据结构(即宗卷头)包含了操作系统加载操作初始化所需要的所有必要的细节信息。
这个宗卷头目前也是HFS+和HFSX的主要区别所在:两个系统的宗卷头就一致,只有一下3点不同:

  • HFSX使用签名HX,而HFS+使用的是H+
  • HFSX将版本号设置为5,而HFS+的版本号设置为4
  • HFSX的B树提供了一个选项用于选中比较键的方法:二进制比较或大写转换后比较

HFS+宗卷头编录非常重要,因此在宗卷头尾部的替补宗卷头(Alternate Volume Header)中也有备份,替补宗卷头在最尾部向前1024字节处,刚好占用了512字节,因此宗卷的最后512字节是没有使用且保存预留的。

编录文件

HFS+文件系统中最主要的B树就是编录文件了。编录文件包含文件系统中所有的文件和文件夹的条目。系统的所有文件操作都会使用这个B树:列出文件、搜索文件、读取文件、写入文件以及删除文件。
由于编录文件是一个B树,因此编录文件集成了HFS+ B树的结构和所有属性。编录文件还有一些新的属性:

  • Catalog Node ID(编录节点ID,即CNID):是文件或文件夹唯一的32位标识符
  • 编录文件键定义一个结构体
  • 编录文件中可能包含的4种不同的记录类别
    • kHFSPlusFileRecord类型以HFSPlusCatalogFolder的形式保存文件夹数据
    • kHFSPlusFileRecord类型以HFSPlusCatalogFile的形式保存文件数据
    • kHFSPlusFolderThreadRecord以及kHFSPlusFileThreadRecored用于保存“thread(线索)”这两种请求下,线索是类型都为HFSPlusCatalogThread
编录查找

编录查找分为两种类型:

  • 根据文件或文件夹名查找
  • 根据CNID查找
硬连接和软连接

与其他任何UNIX文件系统一样,HFS+支持硬连接和软连接。然而其底层实现却很特别。
硬连接和软连接是通过userInfo字段中的fdType字段区分的。对于硬连接,这个字段的值是一个魔数0x686c6e6b(hlnk),对于软连接,这个字段的值是另一个魔数0x736c6e6b(slnk)。在这两个情况下,fdCreator代码都是hfs+。
对于软连接,特殊处理到此为止:软连接也不过是普通文件,只是文件内容中包含了文件系统中另一个文件的名字。
对于硬连接,系统则需要特殊处理。创建硬连接时,底层文件的fork被重新安置了(甚至可以说是隐藏在文件系统中,某个私有隐秘的位置中。HFS+努力保持这个文件夹的隐藏和不可访问状态。通过UNIX工具无法看到这个文件夹(因为这个文件夹名字以NULL开头,NULL终止了C字符串)),Finder 也无法看到这个文件夹(Finder 要遵循kIsInvisible和kNameLocked标志位的约束)
硬连接的dentry和普通文件一样保存在对应的位置,但是这些文件的资源fork被设置为0。相反,bsdInfo中有一个“special”字段被设置为文件的inode编号,而这个编号可以从\0\0\0\0HFS+ Priviate Data 文件夹中获得。

fork分配

文件记录提供了两个HFSPlusForData结构体:一个用于资源fork; 另一个用于数据fork。根据前文的描述,HFS+可以支持任意数目的fork(通过后文要描述的属性树),不过如果真的使用了fork,一般只有数据fork被使用了。

extent 溢出文件

大部分文件都刚好适合不超过8个extent。超过8个extent的文件被认为是严重碎片化,但是文件系统依然应该为这种文件提供服务。为此,文件系统维护了另外一个B树:extent溢出B树。extent 溢出B树比编录文件的B树要简单得多。和编录文件的不同之处在于,extent 溢出B树不含多个索引的记录:只包含叶子节点。

属性B树

属性B树是HFS+使用的另一个B树。HFS+通过属性B树保存各种扩展属性。大部分情况下,用户态应用程序都不需要关系这个B树,因为通过系统调用listattr(2)、getattr(2)和setattr(2)可以分别列出、获得和设置属性。hfsleuth工具可以直接读取属性B树所有的属性

热文件B树

热文件B树的记录是通过temperature 和 fileID(即目标热文件的CNID)索引的。由于temperature值是系统需要非常频繁查询的值,因此系统可以将搜索键的temperature设置为HFC_LOOKUPTAG。

分配文件

分配文件是一个非常大,而且无法访问的文件,负责跟踪卷中所有块的分配情况。由于分配文件本身也是一个文件,因此可能会碎片化。如果宗卷被扩大了,那么分配文件也会增长,因此实现了非常可扩展的方案。分配文件通常是连续的,而且包含在单独一个extern中,因为分配文件是在创建文件系统时通过mkfs呈现创建的。此外文件系统分配块大小的动态变化也是比较容易实现的。

HFS 日志

在HFS+ 中,日志是一项可以随意开关的特性,不过默认情况下是开启的。在加载一个文件系统时,HFS+检查宗卷头中lastMountedVersion 字段的值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容

  • 文件系统和虚拟文件系统交换 内核的一个重要职责就是管理数据,这些数据既包括用户数据也包括系统数据。为了实现这个目的...
    CoderKo1o阅读 2,522评论 0 2
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,545评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,112评论 18 139
  • 今天六点五十起床,比计划晚了一点,有点惭愧,在群里打卡的时候都觉得有些不好意思。上午在家里准备女儿上幼儿园报名的资...
    星铄阅读 163评论 0 1
  • 本文参加#见字如面·寸草春晖#征文活动,文章为作者原创 亲情是什么?亲情是世界上最灿烂的阳光,无论我们走出多...
    Reid111阅读 246评论 0 0