文件系统和虚拟文件系统交换
内核的一个重要职责就是管理数据,这些数据既包括用户数据也包括系统数据。为了实现这个目的,数据按照文件和目录的方式组织,文件和目录保存在各种类型的文件系统上。
XNU的BSD层负责文件系统的实现,BSD 文件系统使用了一个名为虚拟文件系统交互(VFS)的框架。这个框架成为了UNIX 中内核和各种文件系统实现(本地文件系统和各种文件系统)之间的标准借接口。
磁盘设备和分区
OS X 和 iOS 遵循BSD 将硬盘当做设备节点的命名约定。每一个磁盘都可以以块设备的形式(/dev/disk#)或字符设备(裸设备)的形式(/dev/rdisk#)访问。通常情况下,磁盘和分区都是块设备。系统通过mount(2)挂载文件系统时使用的是块设备的表示方式。裸设备模式主要被一些底层程序使用,例如fsck(8)和pdisk(8),这些程序需要直接访问磁盘上的块。
分区方案
文件系统并不是独立存在的,而是存在在磁盘分区上。每一个磁盘都至少有一个分区,而每一个分区都可以单独格式化为文件系统。在某些情况下,可以允许一个文件系统跨越多个分区。分区方案(partitioning scheme)定义了磁盘的布局,逻辑地将磁盘分隔为一个或多个区域(每一个区域为一个分区),每个分区中包含的是连续的扇区。通常情况下,磁盘的头几个扇区保存了磁盘的分区表,分区表列出了磁盘中的区域(起始扇区和扇区数)以及每一个分区的文件系统类型。OS X 传统上支持3种分区方案:
- 主引导记录(Master Boot Record,MBR)分区:MBR 是PC XT 和 AT 年代遗留下来的产物,现在依然广泛使用。这个分区方案依赖于 BIOS,局限性非常大,最多允许4个主分区,而且是32位的(最多支持约40亿个扇区),但是这个分区方案的优点在于所有的操作系统都支持这个方案
- Apple Partition Map:这个苹果独有的自定义分区方案。最初流行于基于PPC的Mac,这个分区方案也是32位的,而且是苹果私有的。现在这个方案基本上被下一个方案GPT替代了,但是在一些iPod 设备(例如Classic 和 Nano)上仍然使用
- GUID分区表(GUID Partition Table,GPT):这是一个64位的方案,可管理的磁盘大小超越了千兆兆字节,解决了所有最大分区的限制。这一点非常重要:MBR 和 APT 都是32位的,因此最大可寻址 232 个扇区。标准扇区大小为512字节,因此磁盘大小最大允许为2TB。而苹果的默认分区方案现在转移到64位架构。GPT 也是EFI标准的一部分,因此在苹果基于EFI的Intel硬件上可以工作得很好。
通用文件系统的概念
尽管不同的文件系统会采用完全不同的方式管理磁盘上的文件,但是所有的文件系统基本上都提供了同样的原语。内核对文件的接口称为虚拟文件系统交换(Virtual FileSystem Switch, VFS),VFS 就是基于这些概念构建的。
文件
文件系统中最基本的概念就是文件。文件是在底层媒体(磁盘、CD-ROM或其他设备)上的一组或多组块。在理想情况下,文件一改是单独一组连续的块。然而在大部分情况下,文件会占用多个块的范围。这些范围成为extent。HFS+ 还定义了clump的概念,clump 表示当前一个文件被分配或扩张时提供的默认分配的块。
尽管会有碎片化,但是文件系统必须将文件表现为连续的、可自由寻址(随机访问)的区域。事实上,有一些文件系统就是完全虚拟的(例如Linux的/proc),还有一些是通过网络映射的(例如 NFS 和 AFS)。文件请求者只得到一个文件描述符(通过open(2)返回的整形fd,或是通过fopen(3)返回的FILE *指针),将文件描述符当成一个不透明的句柄。内核在提供文件请求服务时,需要将这个句柄转换为文件系统中的标识符。
扩展属性
扩展属性指的是用户(或系统)定义的属性,可以包含应用程序所需要的信息,在很多情况下实际包含了系统本身所需要的信息。Darwin 中通过扩展属性支持很多高级特性,例如透明的压缩和fork,还支持访问控制列表(Access Control List)
权限
并不是所有的文件都是平等的。有一些文件包含敏感信息,因此每一个严肃的文件系统(除了 FAT 系列的文件系统)都必须支持权限。UNIX 的文件系统(Mac 原生的HFS+文件系统也属于一种UNIX 文件系统)支持传统的用户/组/其他的读/写/执行的权限模型。从OS X 10.4 开始,VFS 开始支持更精细的权限:Access Control List(ASL,访问控制列表),OS X 允许通过chmod(1) 设置和修改ACL。通过ls(1) -e 可以显示访问控制列表。在ls(1) -l 的输出列表中,带有ACL的文件会带有一个加号(+)标记。VFS 通过扩展属性支持ACL,而ACL的实施是通过一个独立的机制KAUTH完成的。
时间戳
文件系统需要为其包含的文件记录时间戳。UNIX 要求维护三个时间戳:创建时间、修改时间和访问时间。touch(1)命令的-acm 选项就对应了这3个时间戳。ls(1) 命令的-u 参数能显示访问时间,-U 参数显示创建时间,如果不提供参数则显示修改时间。
快捷方式和连接
大部分UNIX用户都对连接(link)的概念很熟悉,其中既包括软连接(也称为符号连接)也包括硬链接。软连接是通过ln(1) -s 创建的,硬连接则是通过不带-s参数的ln(1)创建的。从VFS 角度看,软连接是一个不同的文件(即另一个inode,类型为 1,包含其指向的文件的文件名。而硬连接则是目录中的另一个条目,指向同一个底层的文件(从VFS角度看,是同一个inode)。通过另一种方式描述,可以说硬连接存在于目录层次,而软连接存在于文件层次。
硬连接和软连接一样,都提供了一种文件快捷方式的机制。但是和软连接的不同之处在于,硬连接可以防止误删文件,因为只有最后一个指向文件的连接被删除了,文件系统才会删除文件。下表是硬连接和软连接的比较:
| 软连接 | 硬连接
---|---|---
inode|指向不同inode的不同目录项(dentry)包含文件名|指向同一个inode的不同dentry
作用范围|跨越文件系统|同一个文件系统
目录|可连接|规定不可连接(除了 “.”和“..”之外)。实际中取决于具体实现
目标 rm/mv|软连接被破坏|硬连接依然存在
目标重新创建|软连接被修复|硬连接指向“旧”的文件
查找|find - L -samefile <target>|find -samefile <target>
find -inum <targetinodenum>
苹果生态圈中的文件系统
OS X 和 iOS 都支持各种各样的文件系统。实际上由于内核的模块化设计,内核可以支持任意种类的文件系统,只要求系统服从内核VFS标准即可。
苹果原生的文件系统
- 层次文件系统(HFS):苹果在Mac OS 早期时开发的原生文件系统结构
- 层次文件系统 Plus(HFS+):用来替代HFS。HFS+ 对 HFS 增加了很多扩展以克服HFS的各种局限性
DOS/Windows 文件系统
- 文件分配表(FAT):最简单最原始的文件系统,最流行的FAT-32仍然局限于2TB的卷
- NT 文件系统(NTFS):FAT 缺少的主要功能是权限和限额。权限系统的作用是支持文件的选择性访问控制。限额机制的作用是防止用户创建过多文件导致共享文件系统的滥用,为了同时满足这两个 需求,NT 文件系统(NTFS)出现了。并且是现在Windows的原始文件系统
CD/DVD 文件系统
- CD 音频文件系统(CDDAFS)
- CD-ROM 文件系统(CDFS/ISO-9660)
- 通用磁盘格式(UDF)
基于网络的文件系统
网络文件系统的用途是将存储系统扩展到本地主机之外的远程主机,远程主机既可以在本地局域网中,也可以是通过Internet 连接的遥远主机。
- 苹果文件传输协议:Apple Filing Protocol,AFP是Mac OS 8 和 9 默认的网络文件协议,当时还称为 AppleShare。这是一个应用层协议,建立在苹果私有的AppleTalk协议之上(后来苹果投入了TCP/IP的怀抱)。目前AFP使用TCP端口427和528
- 网络文件系统协议:网络文件系统(Network File Systems,NFS)一个老牌的应用层协议
- 服务器消息块(SMB/CIFS/SMB2)协议
- 文件传输协议:FTP(对应RFC 959)是最老的互联网协议之一
- Web Distribute Authoring and Versioning(WebDAV):是一个推荐的HTTP扩展协议
伪文件系统
伪文件系统部署真正的文件系统。伪文件系统可以分为两类
- 内核数据结构和设备的文件系统接口:Linux 的/proc 和 /sys 文件系统,这两个文件系统提供了大量诊断数据和内核参数。UNIX 的 /dev 文件系统,内核通过/dev 文件系统暴露了各种设备的驱动程序
- 文件系统组件:这些根本不是文件系统,但是提供了处理特殊文件类型或特殊挂载选项的机制。BSD(和XNU)的deadfs、specfs、FIFOfs和unionfs都属于这一类
挂载文件系统(仅限于 OS X)
OS X 支持文件系统的动态挂载和卸载,并且这种支持是通过两种机制实现的:
- automount:内核的automount 组件是 autofs.kext 内核扩展,autofs.text 在 VFS 中注册了 autofs 文件系统。autofs.text 向用户态暴露了/dev/autofs。为了automount 操作的成功执行,用户态有几个辅助 automount 的守护程序:
- autofsd:由launchd 启动,辅助监听网络配置变化通知以及对automount 的调用
- autmount:查询/etc/auto_master 文件,请求特定的挂载操作,automountd 执行实际的挂载操作
- 磁盘仲裁:磁盘仲裁框架隐藏了内核驱动层I/O Kit 发送消息的果餐。如果不适用磁盘仲裁框架,也可以直接从I/O Kit 注册通知
磁盘镜像文件
OS X 使用了磁盘镜像(disk image),磁盘镜像文件的后缀名为.dmg。这些文件上是一个文件中包含了整个文件系统,通常是HFS+文件系统。这个文件的格式为UDIF(Universal Disk Image Format,通用磁盘镜像格式)。双击DMG文件格式时,OS X 的Finder 可以自动挂载 DMG 文件(通过调用CoreServices 的 DiskImageMounter.app程序):hdiutil(1) 命令也可以挂载DMG文件。DMG文件的挂载是由DiskImages.framework框架负责完成的。这是一个私有框架。
BSD 层在vnode磁盘驱动程序中对磁盘镜像提供了原生的支持,通过用户态的/usr/libexec/vndevice命令可以访问这个驱动程序。通过这条命令可以将磁盘镜像挂载到某个BSD /dev/vn*设备。
虚拟文件系统交换
和大部分UN*X 一样, OS X 使用虚拟文件系统(Virtual File System,VFS)交换作为所有文件系统的抽象层。VFS 的基本思想是定义一套适用于所有文件系统的公共接口,而不管文件系统的具体实现是什么,这套文件系统简化为基础结构:文件系统条目、挂载条目以及vnode(抽象的inode)。任何已知的文件系统都可以遵循这套接口实现。有了这套接口,内核就可以像各种POSIX 文件 I/O 调用提供完全一致的接口,从而可以将多种文件系统无缝地整合进同一个文件树种。
文件系统条目
在内核中,通过vfs_fsentry 结构体数组维护文件系统。用过vfs_fsadd和vfs_fsremove 可以分别从内核添加和删除文件系统
挂载条目
挂载条目(mount entry)是一个结构体mount,向用户态只暴露了一个不透明的类型,表示一个已挂载的文件系统示例。挂载条目大致对应的是文件系统的超级块。超级块是一个描述符,其中保存了全局的文件系统属性。挂载条目还保存了文件系统的操作。文件系统可以注册,但是不一定被挂载。此外,同一个文件系统类型可以被挂载多次。
vnode 对象
vnode 对象建立在传统的UNIX inode之上。vnode 表示一个“虚拟的inode”,其中包含从磁盘获取文件或目录的必要信息。vnode 结构体中有一个重要的字段是 ubc_info 结构体:通过这个结构体可以在同一缓冲区缓存(unified buffer cache,UBC)中找到这个 vnode 的对象消息。UBC 是 BSD 保存缓存的 vnode 数据的机制,vnode 数据是从磁盘和设备文件获取的。ubc_info 将 vnode 和 Mach 的 memory_object_t连接在一起。每一个文件系统都定义了自己的内部节点表示方式,但是应该支持vnode 的基本表现形式,还应该支持一组定义在 vnode 上的操作:创建、读取、写入和删除。
FUSE:用户空间的文件系统
FUSE 的内核态组件非常简单:注册一个VFS(通过 vfs_fsadd),并且导出一组/dev/fuserXX字符设备。针对这个文件系统实例的操作都被内核扩展截获,然后序列化为一条消息发给用户态的文件系统处理。
FUSE在用户态的实现:在fuse_operations 结构体中填充自己的文件操作回调函数,然后通过fuse_main( ) 完成其他的工作。
进程的文件 I/O 操作
BSD proc_t 结构体中包含了一个字段 struct filedesc *p_fd,这个字段的结构体保存了进程打开的所有文件。filedesc结构体中关键的字段是fd_ofiles 和 fs_ofileflags。这两个字段表示的都是数组,用户态的整型文件描述符就是这些数组的索引(0:stdin;1:stdout;2:stderr)。第一个数组保存了对应描述符的文件“对象”,第二个数组保存了文件打开传入的标志位。fp_lookup 函数可以根据给定的文件描述符查找fileproc。