文件系统
KEY:文件系统 文件系统是什么 系统论 信息论
文件系统理论
计算机世界里的[文件系统](filesystems)是一种存储和组织[计算数据](高层称“文件”)的方法 ,目的是为了便于用户(通过文件名)操纵{存取和检索}计算数据。
计算数据与文件
计算在OS术语里叫进程。是进程的数据的发展产生了文件及文件系统,而不是反之。产生文件系统的客观原因有三:
- 进程处理的数据越来越大,不便放进内存;比如大型数据库应用;
- 进程被载出主机后有持久化计算数据的需要;数据处理集中(与计算集中相对)的应用都有这个需要;
- 多个进程并发使用同一计算数据。
从以上定义中可看到,文件系统是完成操纵计算数据这一事务的[手段 ] ,那么这个手段具体步骤是怎么的情况呢?每一步的操作对象又是什么呢?这两问题的答案对不同的文件系统会有所不同,因为不同[文件系统 ]会引入不同的策略解决具体特殊的问题。不过在这个手段的两端,大部分文件系统有相同操作对象。一端是操作文件的接口{文件操作接口也可策略化,不过为了统一和兼容,已经被标准化 },另一端是数据存取的物理位置,看下图操作系统中[文件管理子系统 ]的逻辑分层结构(并参考这里 )。
从以上的逻辑分层结构中看到,文件系统这个[手段]中的每一步都实现了策略,比如文件与目录逻辑组织策略、文件目录访问控制策略、文件数据的物理组织策略(比如簇大小)和文件数据的物理访问策略(比如缓冲机制)等。
文件系统是一种手段?
常常会有局部地理解文件系统的认识,比如“某磁盘分区的文件系统是NTFS”。这种说法没有错,但从严格上说不全面。应该把文件系统作为一种完成事务的手段来理解。以手段的角度来理解文件系统也更好的理解为什么会有如此多的文件系统存在。看看如下一个比较完整的文件系统的各个逻辑层及其功能:
- 逻辑I/O层:顾名思义,本层提供的I/O是逻辑的(包括逻辑资源和逻辑操作),是对具体的I/O数据和控制进行包装,为用户进行提供简易接口,比如设备标识和设备操作命令open,close,read,write等。
- 目录管理层:在这一层中,文件名符号被转换为一个标识,这标识通过一个文件描述符或索引表直接或间接地引用一个文件。本层也会处理面向用户的一些文件目录操作,比如添加、删除和重组。
- 文件系统层:本层处理文件的逻辑结构和面向用户的一些文件操作,像open, close, read, write。文件访问权限管理也在这一层实现。
- 物理组织层:就像虚拟内存地址通过分段和分页结构转为物理内存地址一样,对文件或记录的逻辑引用也必须通过文件的物理轨道和扇区结构转换为外存的物理地址。外存空间管理以及外存缓冲管理功能也在这一层实现。
- 设备I/O层:来自逻辑层的数据(缓冲的字符和记录)和操作请求会在本层被转译成相应的I/O指令序列、通道命令(channel commands)和控制器命令(controller orders)。[缓冲技术]会在本层实现,用来提高性能。
- 调度与控制层(scheduling and control):操作系统对I/O操作的排队与调度发生在本层,也包括对操作的控制。因此,中断处理和I/O状态报告也发生在本层。本层是软件层中最底的一层,是与硬件进行实际交互的一层。
在不同的应用环境,上面各层都会不同的实现策略,实现不同的文件系统。比如光盘不会使用一般磁盘的文件系统格式,因为光盘是只读的。“某磁盘分区的文件系统是NTFS”只说明了文件系统的[物理组织层]层的策略。
手段与系统做功
手段、功能、机制和性质是一体多面的关系,从不同的角度解释[系统做功 ]。这里的手段强调功能和机制分层多步性。手段整体表现一个大粒度的功能,手段内各层分别实现小粒度功能,并以序列方式组合成大粒度的功能。每一层本身就是一个小系统,并以一定的策略做功 。分层目的是降低系统的复杂度,提高对复杂系统的可控性。
存储的物理位置
为了实现进程(计算)存取计算数据,文件系统必须构建于某种数据存储设备之上。首先,这些设备可是本地数据存储设备,比如磁盘和光盘;也可以在网络上;也可以在内存(通过虚拟访问);其次,这个设备必须组织成可访问的单元组(比如磁盘的扇区)。文件系统的软件部分负责组织[存储地址](比如磁盘的扇区)与文件(或目录)的映射关系,比如磁盘哪些扇区属于哪个文件和哪些扇区是空闲的。
设备的可寻址单元与[文件数据分配单元]可以是不同的,不同的文件系统在二者间的转换上实现了不同的策略,有比如簇(clusters)或块(blocks)的概念。一个簇由数个扇区组成。
磁盘存储地址
为了实现随机存取,存储设备都必须对存储空间进行编址,比如像我们所熟知的内存地址编址。磁盘的编址方式与内存有点不一样。第一,磁盘的物理编址单元是(512个字节的)扇区;第二,磁盘控制器不接受线性的地址,必需给出扇区所在的柱面号(cylinder)、磁头号(head 也叫盘面)和扇区号(sector),也就是所谓的CHS。文件系统(的磁盘驱动程序)负责将线性地址(可称为逻辑扇区号)转换为CHS。不过一般的文件系统还不是把[物理扇区 ]作为最小存储单元,而由数个[物理扇区 ]组成一个[逻辑扇区 ](4到8KB)作为最小存储单元。例如簇(clusters)或块(blocks)的概念。如果FAT32的簇大小为4K,32位地址空间,[理论上 ]FAT32可访问 4x2^10x2^32 = 4x2^42 = 4x2^2x2^40 = 16TB的空间。基于设计的原因,FAT表项高四位保留不用,所以FAT32只支持访问2TB;基于商业原因,FAT32实际只支持单盘32GB,看这里 。
文件与目录逻辑组织方式
文件系统一般把数据组织为文件及目录(目录是一种特殊的文件)的形式,并以目录包含文件形式组成层式结构,以一个文件名标识文件或目录。
- 目录可以是平板的结构,也可以是分层式结构,后者可以在目录中创建子目录。
- 有的文件系统中,文件名是结构化的,带有文件名扩展信息及版本号等;而另一些文件系统里,文件名只是一个简单的字符串,每个文件的属性信息保存在其它地方。
文件名在目录内必须是唯一的,但在整个目录树内不必唯一,因为整个文件路径才是文件在文件系统这个“数据库”中的键。
文件系统是数据库?
与传统的关系数据库相对照,从文件系统存取[文件]的方式看,它绝对是一个数据库。只不过这个数据库的记录只有两个数据域(假设没有文件的元信息)——文件名(键)与值,并且这个值的大小是可变的。使用的存取方式是[直接存取]中的[目录查找法],看这里 。 关于数据库存取方法,《操作系统精髓与设计原理》第12章也有讲述(p.507)
为了实现从文件名到[设备地址](比如磁盘的扇区)的转换,文件系统的软件部分必须建立某种[名值索引],比如MS-DOS的[目录表]和[文件分配表],UNIX系统中的inode结构。
文件系统实现:FAT
术语
- This can be quite confusing, so I'll clarify this at the start:
- FAT: File Allocation Table, a data structure present in all FAT volumes
- FAT12: FAT file system using 12-bit cluster addressing
- FAT16: FAT file system using 16-bit cluster addressing
- FAT32: FAT file system using 32-bit cluster addressing; Win95 SR2 and later
- VFAT: The 32-bit code used to operate the file system in Win9x GUI mode
- Cluster: Single unit of data storage at the FATxx file system logic level
- Sector: Single unit of storage at the physical disk level
- Physical sector address: Sector address in absolute physical hardware terms
- CHS sector address: As above, expressed in Cylinder, Head, Sector terms
- Logical sector address: Sector address relative to the FATxx volume
- Folder: A collection of named items as seen via Windows Explorer
- File Folder: 文件夹,对目录(directory)的现代称呼。
- Directory: 目录表,FAT文件系统的基本数据结构,是目录表项数据库
- Directory entry: 目录表项,表征单个文件或目录(元信息)
- Attributes: A collection of bits in a directory entry that describes it
FAT文件系统在磁盘上的结构
如下是FAT文件系统在磁盘上的大致结构:
文件系统节 | 引导扇区 | 文件系统元信息扇区 (只用于FAT32 ) | 其它保留扇区 (可选) | 文件分配表 #1 | 文件分配表 #2 | 根目录 (只用于FAT12/16) | 数据区 (一直到磁盘或分区的未尾) |
占用的扇区数 | (number of reserved sectors) | (文件分配表数)*(每个文件分配表占用扇区数) | (number of root entries*32)/Bytes per sector | NumberOfClusters*SectorsPerCluster |
FAT文件系统被设计成由四个不同的[逻辑节](sections)组成(KEMIN:因为这些section是线性结构的,所以个人觉得译为[节]比较好):
1. 保留扇区(Reserved sectors)
保留扇区位于(磁盘的)最开始的位置。第一个保留扇区(sector 0) 是引导扇区(boot sector),引导扇区保存BIOS参数块(包括一些文件系统的元信息,如类型和指向其它[逻辑节]区的指针)和操作系统引导代码。FAT文件系统可有多个保留扇区,数目由引导扇区中的一个域指定。引导扇区在操作系统中由数据结构[驱动器参数块]( Drive Parameter Block)表征。对于FAT32,还有两个保留扇区,分别是一个File System Information Sector (在sector 1)和一个Backup Boot Sector (在 Sector 6)。
2. 文件分配表区(FAT Region)
它包含有两份的文件分配表(可能不相同),这是出于系统冗余检查(redundancy checking)考虑的,尽管额外的[文件分配表]很少用,即使是磁盘修复工具也很少使用它。文件分配表是[数据区]的映射表,标示哪些簇被文件或目录使用了。
3. 根目录区(Root Directory Region)保存着根目录内项目(文件和子目录)的[目录表],只用于FAT12 和 FAT16,目的在于强加给根目一个固定的最大空间,这样可以在创建文件系统时预先分配好根目录空间。在FAT32,根目录和其它文件目录一样,放入数据区,没有大小限制。
4. 数据区(Data Region)
这是实际的文件和目录数据存储的区域,它占据了分区的绝大部分。通过简单地在FAT中添加文档链接的个数可以任意增加文档大小和子目录个数(只要有空簇存在)。
文件分配表(File Allocation Table)
文件分配表是一一对应于数据区簇号的列表(KEMIN:注意不是表项内容指示簇号,而是这个[表数组 ]的下标{索引}指示簇号)。FAT文件系统分配磁盘空间按簇来分配的。因此,文件占用磁盘空间时,基本单位不是字节而是簇,即使某个文件只有一个字节,操作系统也会给他分配一个最小单元——即一个簇。 FAT的表项的长度(大小)与FAT类型有关,FAT12的表项为12-bit,FAT16为16-bit,而FAT32则为32-bit。对于大文件,需要分配多个簇。同一个文件的数据并不一定完整地存放在磁盘中一个连续的区域内,而往往会分成若干段,像链子一样存放。这种存储方式称为文件的链式存储。为了实现文件的链式存储,文件系统必须准确地记录哪些簇已经被文件占用,还必须为每个已经占用的簇指明存储后继内容的下一个簇的簇号,对文件的[最后一簇],则要指明本簇无后继簇。
这些都是由FAT 表来保存的,FAT 表的对应表项中记录着它所代表的簇的有关信息:
- 一般表项,标识[文件簇链]中下一个簇的簇号
- 特殊的表项,保存簇链结束项EOC(end of clusterchain),标识簇链的结束
- 特殊的表项,标识坏簇用
- 特殊的表项,标识保留簇用
- 0来表示空闲簇
各种表项的具体值:
FAT12 | FAT16 | FAT32 | Description |
---|---|---|---|
0x000 | 0x0000 | 0x00000000 | 空闲簇 |
0x001 | 0x0001 | 0x00000001 | 保留簇 |
0x002–0xFEF | 0x0002–0xFFEF | 0x00000002–0x0FFFFFEF | 下一个簇的簇号 |
0xFF0–0xFF6 | 0xFFF0–0xFFF6 | 0x0FFFFFF0–0x0FFFFFF6 | 保留簇 |
0xFF7 | 0xFFF7 | 0x0FFFFFF7 | 坏簇 |
0xFF8–0xFFF | 0xFFF8–0xFFFF | 0x0FFFFFF8–0x0FFFFFFF | 簇链最后一簇 |
注意FAT32只使用32位中的28位。高4位通常是0,它们是保留位,不要更改它们。
簇大小
win95时代磁盘可支持的2G上限源于最大的簇大小为32KB。32KB 簇大小的限制是逻辑的,建议的大小。因为簇大小、磁盘大小、文件数目和系统性能之间有着制约关系。FAT支持磁盘大小与簇大小成正比关系;FAT的访问性能与FAT表项总数成反比。请看下表:
16位(最大文件数65536) | 32位(4G个文件) | ||
每簇512byte | DISK_MAX=32MB FAT表项总数=65536 FAT大小=128K | DISK_MAX=2T FAT表项总数=4G FAT大小=16G | |
每簇4K | DISK_MAX=256MB FAT表项总数=65536 FAT大小=128K | DISK=32G FAT表项总数=8M FAT大小=32M | |
每簇64K |
| DISK=32G FAT表项总数=512K FAT大小=2M | |
如果簇很小,比如512byte,16位地址空间(簇数最大为65535)的文件系统支持访问2^16*2^9 = 2^25 bit = 32MB的磁盘,显然太小了;32位地址空间的文件系统支持访问2T的空间,够大了,可是FAT表项总数和FAT也大得离谱,性能极其低下。
簇大小与文件数目之间制约关系
假设n位文件系统fs的簇大小为c,那fs最多支持访问2^n个文件。如果磁盘中有m个大于簇大小s的文件,磁盘能保存的实际文件数目[最多]是(2^n - m)个(实际数目还要取决m个大文件的具体大小)。因为文件系统支持访问2^n是假设每个文件小于等于c的。
在前32位时代,如果要支持更大的磁盘(KEMIN:我们“升级”系统的目标是一个大的模糊的目标,必须量化,比如保存更多的文件),你可自己扭捏(tweak)簇的大小,比如调为64KB。单方面的调高簇大小只能局部有效,对一些特殊的应用例子有效,比如文件数目不多,并且都是大文件。我们假设应用系统的文件不会超过 65535个,使用大文件簇依然存在浪费空间的问题。比如使用64KB大小时,有文件数M个,平均浪费32K*M;10,240个文件浪费 32*2^10*10*2^10 = 10*2^25 = 320M。单方面提高簇大小支持大磁盘只是一种“量变”升级,要产生“质变”必须提高簇地址空间。还有人发现提高族大小不仅对性能产生影响,还会产生错误,看这里 。
量级计数法
我们常见的十进制量级是十、百、千和万等,量级指数分别是1 | 2 | 3 | 4;而二进制的量级有KMGT(应该是kibi、mebi、gibi、tebi,常用的kilo,mega,giga等是混用了十进制的量级单位,看这里 ),量级指数却是10 |20 |30 |40 。在计算二进制数值时互转量级与具体数值会方便计算。比如上面的例子,16位转为2的16次方,64转为2的6次方;2的30次方转为gibi量级单位。
为了支持大磁盘,又不至于浪费空间和尽量提高性能,FAT32文件系统建议如下的参数策略:
- 磁盘(分区)小于8G,簇大小为4K
- 磁盘(分区)8G到16G之间,簇大小为8K
- 磁盘(分区)16G到32G之前,簇大小为16K
- 磁盘(分区)大于32G,簇大小为32K
关于[簇大小与文件数目之间制约关系]和对文件系统性能影响,我有了一些在系统设计上的思考,看这里。
目录表及目录表项
FAT 目录表其实是一个由 32-bytes 的线性表构成的“数据文件”,保存在[数据区] 。目录表是FAT文件系统的核心数据结构。“目录”这个名字有点误导,因为[目录表项 ]不但表征目录,还表征文件,表征长文件名数据等。看如下FAT32的短文件目录表项的定义:
表14 FAT32短文件目录表项32个字节的定义 | |||
字节偏移(16进制) | 字节数 | 定义 | |
0x0~0x7 | 8 | 文件名 | |
0x8~0xA | 3 | 扩展名 | |
0xB* | 1 | 属性字节 | 00000000(读写) |
00000001(只读) | |||
00000010(隐藏) | |||
00000100(系统) | |||
00001000(卷标) | |||
00010000(子目录) | |||
00100000(归档) | |||
0xC | 1 | 系统保留 | |
0xD | 1 | 创建时间的10毫秒位 | |
0xE~0xF | 2 | 文件创建时间 | |
0x10~0x11 | 2 | 文件创建日期 | |
0x12~0x13 | 2 | 文件最后访问日期 | |
0x14~0x15 | 2 | 文件起始簇号的高16位 | |
0x16~0x17 | 2 | 文件的最近修改时间 | |
0x18~0x19 | 2 | 文件的最近修改日期 | |
0x1A~0x1B | 2 | 文件起始簇号的低16位 | |
0x1C~0x1F | 4 | 表示文件的长度 |
* 此字段在短文件目录项中不可取值0FH,如果设值为0FH,目录表项为长文件名
说明:文件长度使用 4 个字节表示,这说明 FAT32 只支持小于 4GB 的文件 ( 目录 ) ,超过 4GB 的文件 ( 目录 ), 系统会截断处理。
目录表项最重要数据字段是[属性]字段和[文件起始簇号]字段。前者标识目录表项性质,后者指示第一个簇的位置。
目录表怎么实现分层目录结构?
创建一个目录时,该“文件”的目录表项的[属性 ]字段被置为目录属性,同时[文件长度]字段被设置为 0。每个目录项分配一个簇(除非是 FAT12/16 的根目录),方式是通过指定[文件起始簇号 ]字段,然后在FAT表中为该簇设置一个EOC标志,并把该簇的每一个字节设置为 0,如果这是根目录,那么你的工作就完成了(根目录没有‘.’和 ‘.. ’),否则,你必须在该目录空间(就是刚刚分配的那个簇)的头两个32-bytes 创建两个特殊的[目录表项]。
- 第一个目录项的[文件名]字段设为: “. “
- 第二个目录项的[文件名]字段设为: “.. “
“.”目录指向该目录本身,“..”目录指向该目录的上级目录。 这两个目录的FileSize均设置为0,同时两个目录的时 间和日期标志也设置为以包含它们本身的目录相同。”.”目录表项的[起始簇号]字段设置为与它自身所在的目录(包含这两个“.”和“..”目录项的目录)相同。“..”目录项的[起始簇号]字段设置为与刚刚创建的目录项所在目录的第一个簇号 (如果刚创建的目录在根目录则这些值为0,FAT32也是如此)。
那创建文件的情又如何呢?其实上面创建一个目录时并没有指明目录创建在哪(在根目录下还是其它子目录下),表征目录的目录表项必须保存在某一个簇上的,这也就是实现分层的目录结构的关键点。文件或目录的[目录表项]保存在哪个簇上,就是该簇所代表的目录 的文件或子目录,因为目录“文件”至少占用一个簇。因为一个目录表项占32字节,而一个簇才4K,当文件和子目录很多,并且使用长文件名时,目录“文件”常常占用多于一个簇。
长文件名
FAT32完全支持长文件名。长文件名依然是记录在目录项中的。为了低版本的OS或程序能正确读取长文件名文件,系统自动为所有长文件名文件创建了一个对应的短文件名,使 对应数据既可以用长文件名寻址,也可以用短文件名寻址。不支持长文件名的OS或程序会忽略它认为不合法的长文件名字段,而支持长文件名的OS或程序则会以长文件名为显式项来记录和编辑,并隐藏起短文件名。
当创建一个长文件名文件时,系统会自动加上对应的短文件名,其一般有的原则:
- 取长文件名的前6个字符加上"~1"形成短文件名,扩展名不变。
- 如果已存在这个文件名,则符号"~"后的数字递增,直到5。
- 如果文件名中"~"后面的数字达到5,则短文件名只使用长文件名的前两个字母。通过数学操纵长文件名的剩余字母生成短文件名的后四个字母,然后加后缀"~1"直到最后(如果有必要,或是其他数字以避免重复的文件名)。
- 如果存在老OS或程序无法读取的字符,换以"_"
长 文件名的实现有赖于目录项偏移为0xB的属性字节,当此字节的属性为:只读、隐藏、系统、卷标,即其值为0FH时,DOS和WIN3.1会认为其不合法而 忽略其存在。这正是长文件名存在的依据。将目录项的0xB置为0F,其他就任由系统定义了,Windows9x或Windows 2000、XP通常支持不超过255个字符的长文件名。
系统将长文件名以13个字符为单位进行切割,每一组占据一个目录项。所以可能一个文件需要多个目录表项,这时长文件名的各个目录表项按倒序排列在目录表中,以防与其他文件名混淆。
长文件名中的字符采用unicode形式编码,每个字符占据2字节的空间。其目录项定义如表15。
表15 FAT32长文件目录项32个字节的定义 | ||||
字节偏移 (16进制) | 字节数 | 定义 | ||
0x0 | 1 | 属性字节位意义 | 7 | 保留未用 |
6 | 1表示长文件最后一个目录项 | |||
5 | 保留未用 | |||
4 | 顺序号数值 | |||
3 | ||||
2 | ||||
1 | ||||
0 | ||||
0x1~0xA | 10 | 长文件名unicode码① | ||
0xB | 1 | 长文件名目录项标志,取值0FH | ||
0xC | 1 | 系统保留 | ||
0xD | 1 | 校验值(根据短文件名计算得出) | ||
0xE ~0x19 | 12 | 长文件名unicode码② | ||
0x1A~0x1B | 2 | 文件起始簇号(目前常置0) | ||
0x1C~0x1F | 4 | 长文件名unicode码③ |
系统在存储长文件名时,总是先按倒序填充长文件名目录项,然后紧跟其对应的短文件名。从表15可以看出,长文件名中并不存储对应文件的文件开始簇、文件大 小、各种时间和日期属性。文件的这些属性还是存放在短文件名目录项中;
一个长文件名总是和其相应的短文件名一一对应,短文件名没有了长文件名还可以读,但 长文件名如果没有对应的短文件名,不管什么系统都将忽略其存在。所以短文件名是至关重要的。
在不支持长文件名的环境中对短文件名中的文件名和扩展名字段作更改(包括删除,因为删除是对首字符改写E5H),都会使长文件名形同虚设。 长文件名和短文件名之间的联系光靠他们之间的位置关系维系显然远远不够。
其实,长文件名的0xD字节的校验和起很重要的作用,此校验和是用短文件名的11 个字符通过一种运算方式来得到的。系统根据相应的算法来确定相应的长文件名和短文件名是否匹配。这个算法不太容易用公式说明,我们用一段c程序来加以说明。
假设文件名11个字符组成字符串shortname[],校验和用chknum表示。得到过程如下:
如果通过短文件名计算出来的校验和与长文件名中的0xD偏移处数据不相等。系统无论如何都不会将它们配对的。
参考
- http://users.iafrica.com/c/cq/cquirke/fat.htm
- http://blog.csdn.net/qianjintianguo/archive/2006/05/11/725204.aspx