预计阅读本页时间:-
4.3.2 文件的实现
文件存储的实现的关键问题是记录各个文件分别用到哪些磁盘块。不同操作系统采用不同的方法。这一节,我们讨论其中的一些方法。
1.连续分配
最简单的分配方案是把每个文件作为一连串连续数据块存储在磁盘上。所以,在块大小为1KB的磁盘上,50KB的文件要分配50个连续的块。对于块大小为2KB的磁盘,将分配25个连续的块。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
在图4-10a中是一个连续分配的例子。这里列出了头40块,从左面从0块开始。初始状态下,磁盘是空的。接着,从磁盘开始处(块0)开始写入长度为4块的文件A。紧接着,在文件A的结尾开始写入一个3块的文件B。

请注意,每个文件都从一个新的块开始,这样如果文件A实际上只有31 /2 块,那么最后一块的结尾会浪费一些空间。在图4-10中,一共列出了7个文件,每一个都从前面文件结尾的后续块开始。加阴影是为了容易表示文件分隔,在存储中并没有实际的意义。
连续磁盘空间分配方案有两大优势。首先,实现简单,记录每个文件用到的磁盘块简化为只需记住两个数字即可:第一块的磁盘地址和文件的块数。给定了第一块的编号,一个简单的加法就可以找到任何其他块的编号。
其次,读操作性能较好,因为在单个操作中就可以从磁盘上读出整个文件。只需要一次寻找(对第一个块)。之后不再需要寻道和旋转延迟,所以,数据以磁盘全带宽的速率输入。可见连续分配实现简单且具有高的性能。
但是,连续分配方案也同样有相当明显的不足之处:随着时间的推移,磁盘会变得零碎。为了了解这是如何发生的,请考察图4-10b。这里有两个文件(D和F)被删除了。当删除一个文件时,它占用的块自然就释放了,在磁盘上留下一堆空闲块。磁盘不会在这个位置挤压掉这个空洞,因为这样会涉及复制空洞之后的所有文件,可能会有上百万的块。结果是,磁盘上最终既包括文件也有空洞,如图4-10中所描述的那样。
开始时,碎片并不是问题,因为每个新的文件都在先前文件的磁盘结尾写入。但是,磁盘最终会被充满,所以要么压缩磁盘,要么重新使用空洞中的空闲空间。前者由于代价太高而不可行;后者需要维护一个空洞列表,这是可行的。但是,当创建一个新的文件时,为了挑选合适大小的空洞存入文件,就有必要知道该文件的最终大小。
设想这样一种设计的结果:为了录入一个文档,用户启动了文本编辑器或字处理软件。程序首先询问最终文件的大小会是多少。这个问题必须回答,否则程序就不能继续。如果给出的数字最后被证明小于文件的实际大小,该程序会终止,因为所使用的磁盘空洞已经满了,没有地方放置文件的剩余部分。如果用户为了避免这个问题而给出不实际的较大的数字作为最后文件的大小,比如,100 MB,编辑器可能找不到如此大的空洞,从而宣布无法创建该文件。当然,用户有权下一次使用比如50MB的数字再次启动编辑器,如此进行下去,直到找到一个合适的空洞为止。不过,这种方式看来不会使用户高兴。
然而,存在着一种情形,使得连续分配方案是可行的,而且,实际上这个办法在CD-ROM上被广泛使用着。在这里所有文件的大小都事先知道,并且在CD-ROM文件系统的后续使用中,这些文件的大小也不再改变。在本章的后面,我们将讨论最常见的CD-ROM文件系统。
DVD的情况有些复杂。原则上,一个90分钟的电影可以编码成一个独立的、大约4.5GB的文件。但是文件系统所使用的UDF(Universal Disk Format)格式,使用了一个30位的数来代表文件长度,从而把文件大小限制在1GB。其结果是,DVD电影一般存储在3个或4个1GB的连续文件中。这些构成一个逻辑文件(电影)的物理文件块被称作extents。
正如第1章中所提到的,在计算机科学中,随着新一代技术的出现,历史往往重复着自己。多年前,连续分配由于其简单和高性能(没有过多考虑用户友好性)被实际用在磁盘文件系统中。后来由于讨厌在文件创建时不得不指定最终文件的大小,这个想法被放弃了。但是,随着CD-ROM、DVD以及其他一次性写光学介质的出现,突然间连续分配又成为一个好主意。所以研究那些具有清晰和简洁概念的老式系统和思想是很重要的,因为它们有可能以一种令人吃惊的方式在未来系统中获得应用。
2.链表分配
存储文件的第二种方法是为每个文件构造磁盘块链表,如图4-11所示。每个块的第一个字作为指向下一块的指针,块的其他部分存放数据。

与连续分配方案不同,这一方法可以充分利用每个磁盘块。不会因为磁盘碎片(除了最后一块中的内部碎片)而浪费存储空间。同样,在目录项中,只需要存放第一块的磁盘地址,文件的其他块就可以从这个首块地址查找到。
另一方面,在链表分配方案中,尽管顺序读文件非常方便,但是随机存取却相当缓慢。要获得块n,操作系统每一次都必须从头开始,并且要先读前面的n-1块。显然,进行如此多的读操作太慢了。
而且,由于指针占去了一些字节,每个磁盘块存储数据的字节数不再是2的整数次幂。虽然这个问题并不是非常严重,但是怪异的大小确实降低了系统的运行效率,因为许多程序都是以长度为2的整数次幂来读写磁盘块的。由于每个块的前几个字节被指向下一个块的指针所占据,所以要读出完整的一个块,就需要从两个磁盘块中获得和拼接信息,这就因复制引发了额外的开销。
3.在内存中采用表的链表分配
如果取出每个磁盘块的指针字,把它放在内存的一个表中,就可以解决上述链表的两个不足。图4-12表示了图4-11所示例子的内存中表的内容。这两个图中有两个文件,文件A依次使用了磁盘块4、7、2、10和12,文件B依次使用了磁盘块6、3、11和14。利用图4-12中的表,可以从第4块开始,顺着链走到最后,找到文件A的全部磁盘块。同样,从第6块开始,顺着链走到最后,也能够找出文件B的全部磁盘块。这两个链都以一个不属于有效磁盘编号的特殊标记(如-1)结束。内存中的这样一个表格称为文件分配表(File Allocation Table,FAT)。

按这类方式组织,整个块都可以存放数据。进而,随机存取也容易得多。虽然仍要顺着链在文件中查找给定的偏移量,但是整个链表都存放在内存中,所以不需要任何磁盘引用。与前面的方法相同,不管文件有多大,在目录项中只需记录一个整数(起始块号),按照它就可以找到文件的全部块。
这种方法的主要缺点是必须把整个表都存放在内存中。对于200 GB的磁盘和1KB大小的块,这张表需要有2亿项,每一项对应于这2亿个磁盘块中的一个块。每项至少3个字节,为了提高查找速度,有时需要4个字节。根据系统对空间或时间的优化方案,这张表要占用600MB或800MB内存,不太实用。很显然FAT方案对于大磁盘而言不太合适。
4.i节点
最后一个记录各个文件分别包含哪些磁盘块的方法是给每个文件赋予一个称为i节点(index-node)的数据结构,其中列出了文件属性和文件块的磁盘地址。图4-13中是一个简单例子的描述。给定i节点,就有可能找到文件的所有块。相对于在内存中采用表的方式而言,这种机制具有很大的优势,即只有在对应文件打开时,其i节点才在内存中。如果每个i节点占有n个字节,最多k个文件同时打开,那么为了打开文件而保留i节点的数组所占据的全部内存仅仅是kn个字节。只需要提前保留少量的空间。
这个数组通常比上一节中叙述的文件分配表(FAT)所占据的空间要小。其原因很简单,保留所有磁盘块的链接表的表大小正比于磁盘自身的大小。如果磁盘有n块,该表需要n个表项。由于磁盘变得更大,该表格也线性随之增加。相反,i节点机制需要在内存中有一个数组,其大小正比于可能要同时打开的最大文件个数。它与磁盘是10GB、100GB还是1000GB无关。
i节点的一个问题是,如果每个i节点只能存储固定数量的磁盘地址,那么当一个文件所含的磁盘块的数目超出了i节点所能容纳的数目怎么办?一个解决方案是最后一个“磁盘地址”不指向数据块,而是指向一个包含磁盘块地址的块的地址,如图4-13所示。更高级的解决方案是:可以有两个或更多个包含磁盘地址的块,或者指向其他存放地址的磁盘块的磁盘块。在后面讨论UNIX时,我们还将涉及i节点。
