第四章 文件系统
4.1 文件
文件是进程创建的信息逻辑单元。 一个磁盘一般含有几千甚至几百万个文件, 每个文件是独立于其他文件的。 文件不仅仅被用来对磁盘建模, 以替代对随机存储器( RAM) 的建模, 事实上, 如果能把每个文件看成一种地址空间, 那么读者就离理解文件的本质不远了。
4.1.1 文件命名
文件的具体命名规则在各个系统中是不同的, 不过所有的现代操作系统都允许用1至8个字母组成的字符串作为合法的文件名。
有的文件系统区分大小写字母, 有的则不区分。 UNIX是前一类, MS-DOS是后一类。
4.1.2 文件结构
文件可以有多种构造方式, 下图列出了常用的三种方式。 下图中的文件是一种无结构的字节序列, 操作系统事实上不知道也不关心文件内容是什么, 操作系统所见到的就是字节, 其任何含义只在用户程序中解释。 在UNIX和Windows中都采用这种方法。
4.1.3 文件类型
很多操作系统支持多种文件类型。 如UNIX和Windows中都有普通文件和目录, UNIX还有字符特殊文件(character special file) 和块特殊文件(block special file) 。
4.1.4 文件访问
早期操作系统只有一种文件存取方式: 顺序存取(sequential access)。 进程在这些系统中可从头顺序读取文件的全部字节或记录, 但不能跳过某一些内容, 也不能不按顺序读取。 顺序存取文件是可以返回到起点的, 需要时可多次读取该文件。 在存储介质是磁带而不是磁盘时, 顺序存取文件是很方便的。
当用磁盘来存储文件时, 我们可以不按顺序地读取文件中的字节或记录, 或者按照关键字而不是位置来存取记录。 这种能够以任何次序读取其中字节或记录的文件称作随机存取文件(random access file) 。 许多应用程序需要这种类型的文件。
4.1.5 文件属性
文件都有文件名和数据。 另外, 所有的操作系统还会保存其他与文件相关的信息, 如文件创建的日期和时间、 文件大小等。 这些附加信息称为文件属性(attribute) , 有些人称之为元数据(metadata) 。
4.1.6 文件操作
使用文件的目的是存储信息并方便以后的检索。对于存储和检索,不同系统提供了不同的操作。以下是与文件有关的最常用的一些系统调用:
Create:创建不包含任何数据的文件。该调用的目的是表明文件即将建立,并设置文件的一些属性。
Delete:当不再需要某个文件时,必须删除该文件以释放磁盘空间。任何文件系统总有一个系统调用用来删除文件。
Open:在使用文件之前,必须先打开文件。open调用的目的是:把文件属性和磁盘地址表装人内存,便于后续调用的快速访问。
Close:访问结束后,不再需要文件属性和磁盘地址,这时应该关闭文件以释放内部表空间。很多系统限制进程打开文件的个数,以鼓励用户关闭不再使用的文件。磁盘以块为单位写人,关闭文件时,写人该文件的最后一块,即使这个块还没有满。
Read:在文件中读取数据。一般地,读取的数据来自文件的当前位置。调用者必须指明需要读取多少数据,并且提供存放这些数据的缓冲区。
Write:向文件写数据,写操作一般也是从文件当前位置开始。如果当前位置是文件末尾,文件长度增加。如果当前位置在文件中间,则现有数据被覆盖,并且永远丢失。
Append:此调用是write的限制形式,它只能在文件末尾添加数据。若系统只提供最小系统调用集合,则通常没有append。很多系统对同一操作提供了多种实现方法,这些系统中有时有append调用。
Seek:对于随机访问文件,要指定从何处开始获取数据,通常的方法是用seek系统调用把当前位置指针指向文件中特定位置。seek调用结束后,就可以从该位置开始读写数据了。
get attributes:进程运行常需要读取文件属性。例如,UNIX中make程序通常用于管理由多个源文件组成的软件开发项目。在调用make时,它会检查全部源文件和目标文件的修改时间,实现最小编译,使得全部文件都为最新版本。为达到此目的,需要查找文件的某一些属性,即修改时间。
set attributes:某些属性是可由用户设置的,甚至是在文件创建之后,实现该功能的是set attributes系统调用。保护模式信息是一个典型的例子,大多数标志也属于此类属性。
Rename:用户常常要改变已有文件的名字,rename系统调用用于这一目的。严格地说,rename系统调用不是必需的,因为先把文件复制到一个新文件中,然后删除原来的文件,就可以达到同样的目的。
4.2 目录
文件系统通常提供目录或文件夹用于记录文件, 在很多系统中目录本身也是文件。 本节讨论目录、 目录的组成、 目录的特性和可以对目录进行的操作。
4.2.1 一级目录系统
目录系统的最简单形式是在一个目录中包含所有的文件。 这有时称为根目录, 但是由于只有一个目录,所以其名称并不重要。
4.2.2 层次目录系统
所需要的是层次结构(即, 一个目录树)。 通过这种方式, 可以用很多目录把文件以自然的方式分组。
4.2.3 路径名
用目录树组织文件系统时, 需要有某种方法指明文件名。 常用的方法有两种。 第一种是, 每个文件都赋予一个绝对路径名(absolute path name) ,它由从根目录到文件的路径组成。
另一种指定文件名的方法是使用相对路径名(relative path name)。 它常和工作目录(working directory)(也称作当前目录(current directory) ) 一起使用。 这时, 所有的不从根目录开始的路径名都是相对于工作目录的。
4.2.4 目录操作
不同系统中管理目录的系统调用的差别比管理文件的系统调用的差别大。为了了解这些系统调用有哪些及它们怎样工作,下面给出一个例子(取自 UNIX)。
Create:创建目录。除了目录项“.”和“…”外,目录内容为空。目录项“.”和“…”是系统自动放在目录中的。
Delete:删除目录。只有空目录可删除。只包含目录项“.”和“…”的目录被认为是空目录,这两个目录项通常不能删除。
Opendir:目录内容可被读取。例如,为列出目录中全部文件,程序必须先打开该目录,然后读其中全部文件的文件名。与打开和读文件相同,在读目录前,必须打开目录。
Closedir:读目录结束后,应关闭目录以释放内部表空间。
Readdir:系统调用readdir返回打开目录的下一个目录项。以前也采用read系统调用来读目录,但这方法有一个缺点:程序员必须了解和处理目录的内部结构。相反,不论采用哪一种目录结构,readdir总是以标准格式返回一个目录项。
Rename:在很多方面目录和文件都相似。文件可换名,目录也可以。
Link:链接技术允许在多个目录中出现同一个文件。这个系统调用指定一个存在的文件和一个路径名,并建立从该文件到路径所指名字的链接。这样,可以在多个目录中出现同一个文件。这种类型的链接增加了该文件的i节点计数器的计数(记录含有该文件的目录项数目),有时称为硬链接。
Unlink:删除目录项。如果被解除连接的文件只出现在一个目录中(通常情况),则将它从文件系统中删除。如果它出现在多个目录中,则只删除指定路径名的连接,依然保留其他路径名的连接。在UNIX中,用于删除文件的系统调用(前面已有论述)实际上就是unlink。
4.3 文件系统的实现
4.3.1 文件系统布局
4.3.2 文件的实现
1.连续分配
最简单的分配方案是把每个文件作为一连串连续数据块存储在磁盘上。
连续磁盘空间分配方案有两大优势。
首先, 实现简单, 记录每个文件用到的磁盘块简化为只需记住两个数字即可: 第一块的磁盘地址和文件的块数。 给定了第一块的编号, 一个简单的加法就可以找到任何其他块的编号.
其次, 读操作性能较好, 因为在单个操作中就可以从磁盘上读出整个文件。 只需要一次寻找(对第一个块)。
连续分配方案也同样有相当明显的不足之处: 随着时间的推移, 磁盘会变得零碎。
2.链表分配
存储文件的第二种方法是为每个文件构造磁盘块链表, 如上图所示。 每个块的第一个字作为指向下一块的指针, 块的其他部分存放数据。
与连续分配方案不同, 这一方法可以充分利用每个磁盘块。 不会因为磁盘碎片(除了最后一块中的内部碎片) 而浪费存储空间。
3.在内存中采用表的链表分配
如果取出每个磁盘块的指针字, 把它放在内存的一个表中, 就可以解决上述链表的两个不足。
4.i节点
最后一个记录各个文件分别包含哪些磁盘块的方法是给每个文件赋予一个称为i节点(index-node) 的数据结构, 其中列出了文件属性和文件块的磁盘地址。
4.3.3 目录的实现
在读文件前, 必须先打开文件。 打开文件时, 操作系统利用用户给出的路径名找到相应目录项。 目录项中提供了查找文件磁盘块所需要的信息。
4.3.4 共享文件
当几个用户同在一个项目里工作时, 他们常常需要共享文件。 其结果是, 如果一个共享文件同时出现在属于不同用户的不同目录下, 工作起来就很方便。
共享文件是方便的, 但也带来一些问题。 如果目录中包含磁盘地址, 则当连接文件时, 必须把C目录中的磁盘地址复制到B目录中。 如果B或C随后又往该文件中添加内容, 则新的数据块将只列入进行添加工作的用户的目录中。 其他的用户对此改变是不知道的。 所以违背了共享的目的。
有两种方法可以解决这一问题。 在第一种解决方案中, 磁盘块不列入目录, 而是列入一个与文件本身关联的小型数据结构中。 目录将指向这个小型数据结构。 这是UNIX系统中所采用的方法(小型数据结构即是i节点) 。
在第二种解决方案中, 通过让系统建立一个类型为LINK的新文件, 并把该文件放在B的目录下, 使得B与C的一个文件存在连接。 新的文件中只包含了它所连接的文件的路径名。 当B读该连接文件时, 操作系统查看到要读的文件是LINK类型, 则找到该文件所连接的文件的名字, 并且去读那个文件。 与传统(硬) 连接相对比起来, 这一方法称为符号连接(symbolic linking) 。
4.3.5 日志结构文件系统
LFS的设计者决定重新实现一种UNIX文件系统, 该系统即使对于一个大部分由零碎的随机写操作组成的任务, 同样能够充分利用磁盘的带宽。 其基本思想是将整个磁盘结构化为一个日志。 每隔一段时间, 或是有特殊需要时, 被缓冲在内存中的所有未决的写操作都被放到一个单独的段中, 作为在日志末尾的一个邻接段写入磁盘。
4.3.6 日志文件系统
虽然基于日志结构的文件系统是一个很吸引人的想法, 但是由于它们和现有的文件系统不相匹配, 所以还没有被广泛应用。 尽管如此, 它们内在的一个思想, 即面对出错的鲁棒性, 却可以被其他文件系统所借鉴。 这里的基本想法是保存一个用于记录系统下一步将要做什么的日志。 这样当系统在完成们即将完成的任务前崩溃时, 重新启动后, 可以通过查看日志, 获取崩溃前计划完成的任务, 并完成它们。 这样的文件系统被称为日志文件系统, 并已经被实际应用。
4.3.7 虚拟文件系统
所有和文件相关的系统调用在最初的处理上都指向虚拟文件系统。 这些来自用户进程的调用, 都是标准的POSIX系统调用, 比如open、 read write和lseek等。 因此, 虚拟文件系统对用户进程有一个“更高层”接口,它就是著名的POSIX接口。
4.4 文件系统管理和优化
4.4.1 磁盘空间管理
文件通常存放在磁盘上, 所以对磁盘空间的管理是系统设计者要考虑的一个主要问题。 存储n个字节的文件可以有两种策略: 分配n个字节的连续磁盘空间, 或者把文件分成很多个连续(或并不一定连续) 的块。 在存储管理系统中, 分段处理和分页处理之间也要进行同样的权衡。
正如我们已经见到的, 按连续字节序列存储文件有一个明显问题, 当文件扩大时, 有可能需要在磁盘上移动文件。 内存中分段也有同样的问题。 不同的是, 相对于把文件从磁盘的一个位置移动到另一个位置, 内存中段的移动操作要快得多。 因此, 几乎所有的文件系统都把文件分割成固定大小的块来存储, 各块之间不一定相邻。
-
块大小
分配单位很小意味着每个文件由很多块组成, 每读一块都有寻道和旋转延迟时间, 所以, 读取由很多小块组成的文件会非常慢。
-
记录空闲块
第一种
方法是采用磁盘块链表, 每个块中包含尽可能多的空闲磁盘块号。
第二种
另一种空闲磁盘空间管理的方法是采用位图。n个块的磁盘需要n位位图。
-
磁盘配额
为了防止人们贪心而占有太多的磁盘空间, 多用户操作系统常常提供一种强制性磁盘配额机制。 其思想是系统管理员分给每个用户拥有文件和块的最大数量, 操作系统确保每个用户不超过分给他们的配额。
4.4.2 文件系统备份
-
物理转储
物理转储的主要优点是简单、 极为快速(基本上是以磁盘的速度运行) 。 主要缺点是, 既不能跳过选定的目录, 也无法增量转储, 还不能满足恢复个人文件的请求。 正因如此, 绝大多数配置都使用逻辑转储。
-
逻辑转储
逻辑转储从一个或几个指定的目录开始, 递归地转储其自给定基准日期(例如, 最近一次增量转储或全面系统转储的日期) 后有所更改的全部文件和目录。
4.4.3 文件系统的一致性
影响文件系统可靠性的另一个问题是文件系统的一致性。 很多文件系统读取磁盘块, 进行修改后, 再写回磁盘。 如果在修改过的磁盘块全部写回之前系统崩溃, 则文件系统有可能处于不一致状态。
4.4.4 文件系统性能
-
高速缓存
常用的减少磁盘访问次数技术是块高速缓存(block cache) 或者缓冲区高速缓存(buffer cache) 。
常用的算法是: 检查全部的读请求, 查看在高速缓存中是否有所需要的块。 如果存在, 可执行读操作而无须访问磁盘。 如果该块不在高速缓存中, 首先要把它读到高速缓存, 再复制到所需地方。
-
块提前读
第二个明显提高文件系统性能的技术是: 在需要用到块之前, 试图提前将其写入高速缓存, 从而提高命中率。
-
减少磁盘臂运动
另一种重要技术是把有可能顺序存取的块放在一起, 当然最好是在同一个柱面上, 从而减少磁盘臂的移动次数。
4.4.5 磁盘碎片整理
所有的空闲磁盘空间放在一个单独的、 与被安装的文件邻近的单元里。 但随着时间的流逝, 文件被不断地创建与删除, 于是磁盘会产生很多碎片, 文件与空穴到处都是。 结果是, 当创建一个新文件时, 它使用的块会散布在整个磁盘上, 造成性能的降低。
4.5 文件系统实例
4.5.1 MS-DOS文件系统
MS-DOS按32位的数字存储文件的大小, 所以理论上文件大小能够大至4GB。
4.5.2 UNIXV7文件系统
- UNIX的i节点含文件大小,三个时间,所有者,所在组,保护信息,计数等信息
- 大文件使用多个连接块
4.5.3 CD-ROM文件系统
-
ISO 9660文件系统
最普遍的一种CD-ROM文件系统的标准是1988年被采纳的名为ISO 9660的国际标准。 实际上现在市场上的所有CD-ROM都支持这个标准, 有的则带有一些扩展(下面会对此进行讨论) 。 这个标准的一个目标就是使CD-ROM独立于机器所采用的字节顺序和使用的操作系统, 即在所有的机器上都是可读的。
-
Rock Ridge扩展
-
Joliet扩展