操作系统概念_第十一章_文件系统实现
概述
文件系统驻留在外存上,在外存上,如何存储和访问文件?如何组织文件,分配磁盘空间,恢复空闲空间,跟踪数据位置,以及相关的性能?
这些在文件系统中,如何实现?
文件系统结构
磁盘称为存储多个文件的方便介质的两个特点:
- 可以原地重写
- 可以直接访问磁盘的任意一处位置,无论是按顺序还是随机读取文件都可以通过移动磁头直接完成。
另外,与内存管理的部分方式相同,磁盘同样是以块为单位进行转移的。每块为一个或多个扇区。
扇区大小从32-4096B不等,一般为512B
文件系统由许多不同的层组成,每层利用较低层的功能创建新功能为更高层服务。
- IO控制为最底层,提供设备驱动程序和中断处理程序。
-
基本文件系统发送命令,对磁盘上的物理块进行读写。
- 每个块通过磁盘地址标识(驱动器,柱面,磁道,扇区)
- 文件组织模块将逻辑地址转换为物理地址,管理文件的逻辑块。同时含有空闲空间管理器,跟踪未分配的块,并根据要求提供给文件组织模块。
- 逻辑文件系统管理元数据,管理目录结构,提供给文件组织模块必要的信息。以及通过文件控制块(file control block,FCB)维护文件结构。
文件系统实现
(每个卷的)引导控制块(boot control block):包含系统从该卷引导操作系统所需要的信息,通常位于卷的第一块。
当然,只有在该磁盘上存在操作系统时才有引导控制块。
也称为引导块(boot block,UFS下)或分区引导扇区(partition boot setor,NTFS下)
(每个卷的)卷控制块(volume control block):包括卷(或分区)的详细信息。如分区块数,大小,空闲块等。
也称为超级块(下)或主控文件表(NTFS下)
每个文件系统的目录结构:用于组织文件。UFS
文件控制块FCB包含了许多信息。UFS称为索引节点,NTFS将其存储在主控文件表中。
创建一个文件的过程
为了创建一个新文件,应用程序调用逻辑文件系统(其有目录结构形式)。逻辑文件系统分配一个新的FCB。系统把目录信息读入内存,用新的文件名更新该目录和FCB,并写回磁盘。
打开一个文件
系统调用open()
会首先搜索系统范围内的打开文件表来确定某个文件是否已经被其他进程使用,如果是,则在单个进程的打开文件表中创建一项,并指向现有的系统范围内的打开文件表。
当打开文件时,根据给定文件名来搜索目录结构。部分目录结构通常缓存在目录中加快读写速度。
分区与安装
虚拟文件系统
现代操作系统必须支持多个文件系统类型,因此操作系统必须把多个文件系统整合为一个目录结构。
一个简单但不是很好的方法是为每个文件系统编写目录和文件程序。但这不面向对象。
因此可以将文件系统分为三个层次。
- 文件系统接口:包括open(),read(),write(),close()调用及文件描述符。
-
虚拟文件系统层(VFS层):区分本地文件和远程文件,并根据文件系统类型进一步区分不同本地文件。
- VFS定义了一个清晰的VFS接口,将文件系统的通用操作和具体实现分开。
- 提供网络上唯一标识一个文件的机制。(基于vnode的文件表示结构)
- 各个类型的本地文件系统:通过VFS结合在一起,并由文件系统接口调用。
VFS根据文件系统类型调用特定文件类型操作以处理本地请求,通过调用NFS协议程序来处理远程请求。
VFS层有两个目的:
- VFS层通过定义一个清晰的VFS接口,以将文件系统的通用操作和具体实现分开。多个VFS接口的实现可以共存在同一台机器上,他允许访问已安装在本地的多个类型的文件系统。
- VFS提供了在网络上唯一标识一个文件的机制。VFS基于称为vnode的文件表示结构。UNIX内核中为每个活动节点(文件或目录)保存一个vnode结构
目录实现
关于目录实现,主要需要讨论的是目录分配和目录管理的算法。
线性列表(最简单的实现)
使用存储文件名和数据块指针的线性列表。实现非常简单。
但是执行费时费力。
创建文件时,需要搜索是否有同名文件;删除文件时候,需要搜索整个目录找到文件,然后释放空间…
这些都是线性查找。
哈希列表
哈希表根据文件名得到一个值,并返回一个指向线性列表中元素的指针。因此它大大减少了目录搜索时间。插入和删除也比较简单。
哈希表的数据结构需要采取措施来避免冲突(两个文件名哈希到相同的位置)。
总体来说,哈希表实现比线性搜索整个目录要快很多
分配方法
磁盘是直接访问的,非常灵活,其可以存储多个文件,所以一个问题是如何为这些文件分配空间,以便有效的访问和索引这些文件。
主要的分配方法有连续、链接、索引,每种方法有其优缺点。
常见的一个系统只支持一种分配方法,但也有系统支持多种分配方法
连续分配
连续分配(contiguous allocation)要求每个文件在磁盘上占有一系列连续的块。
优点:
- 在访问块b后访问块b+1通常不需要移动磁头,当需要移动时(读到当前磁道末),只需要移动一个磁道。因此访问连续分配文件需要的寻道数最小。性能较好。
- 访问容易,连续分配支持顺序访问和直接访问。
缺点:
- 如何为新文件找到空间,这是一个动态存储分配问题(第八章提到过),相关的算法会产生外部碎片问题
外部碎片的一个解决方案是合并(compact),即将小的空闲空间合并起来,而将其他存储的数据变成连续数据。显而易见这种方式的主要开销是时间,因为需要很多的IO操作。
另一方面,这种方式还需要确定一个文件占用多少空间。文件的大小有时候可能比较好确定,但通常比较难以确定。
链接分配
采用链接分配(linked allocation),每个文件是磁盘块的链表。目录包括文件第一块的指针和最后一块的指针。每一块都有指向下一块的指针。
采用链接分配,每一个目录条目都有一个指向文件首块的指针。这些指针一开始均为nil
(代表空指针,表示空文件)。
- 在写文件时就可以通过空闲空间管理系统找到一个空闲块。
- 在读文件时,通过块到块的指针就可以简答的读块。
优点:
- 没有外部碎片,空闲空间的任何一块都可以满足要求。
- 创建文件时,不需要说明文件大小。
- 不需要合并磁盘空间
可以说链接分配解决了连续分配的所有问题。
缺点:
- 只能用于顺序访问,要找到中间位置,必须跟随指针一块一块的移动。
- 指针需要空间。
- 可靠性较低。如果硬盘损坏,若损坏的是指针,那么这可能导致链接到错误的位置。
第一个问题的一个解决方案是采用链接分配方式的变种:文件分配表(FAT),具体参考P365
第二个问题的一个解决方式是将块合并为族,指针按族分配而不是按块分配,一个族包含多个块。这样就减少了指针的使用。但这样的问题就是会增加内部碎片。
第三个问题的解决方式是增加双向链表或在每个块中存文件名和相对块数,不过这也将导致开销增大。
索引分配
链接分配解决了外部碎片和大小的声明问题,但如果不使用FAT,那么就没有办法有效的支持直接访问。
索引分配(index allocation)把所有的指针存放在一起,通过索引块解决这一问题。
索引块是一个磁盘块地址的数组。
每个文件都有索引块,其第i块指向文件的第i个块。目录条目包含索引块的地址。要读第i块只需要通过索引块的第i个条目的指针查找和读取即可。
创建文件时,索引块所有指针都设置为nil
,开始写第i块时,即从空闲空间管理系统中获取一块,并将指针设置为该地址。
优点:
- 没有外部碎片,并且支持直接访问。因为磁盘上任何一个位置都可以通过索引获取。
缺点:
- 浪费空间。索引块的开销比指针要大很多。
针对缺点的解决方案:
链接方案:一个索引块通常为一个磁盘块,因此,它本身能直接读写。为了处理大文件,可以将多个索引块链接起来
多层索引:用第一层索引块指向一组第二层的索引块,第二层索引块再指向文件块,这是链接表示的一种变种。
组合方案:将索引块的头15个指针存在文件的inode中。这其中的前12个指针指向直接块。其他的3个指针指向间接块。第一个间接块为一级间接块的地址,第二个间接块为二级间接块的指针,第三个间接块为三级间接块指针
性能*
空闲空间管理
系统需要维护一个空闲空间链表(free-space list),该链表记录了所有的空闲磁盘空间,并在创建文件时,能够从该链表搜索并返回一段空闲空间。
虽然名字称为链表,但实现形式不一定表现为链表。这一点要注意
位图/位向量
采用位图(bit map)或位向量(bit vector),每块用一位表示,分配表示1,未分配表示0
优点:
- 查找空闲块和n个连续空闲块相对简单和高效。
缺点:
- 除非将整个位图都放在内存中方便及时查询,否则其效率就不是很高。这对于小型磁盘是完全可以的,但对大型磁盘,就需要相对较多的内存。
链表
将空闲磁盘块用链表连接起来,并将指向第一个空闲磁盘块的指针保存在磁盘的特殊位置,并同时放置到内存中。
这种方案的效率不高,因为遍历一遍链表需要大量的IO,但通常分配空闲空间不需要遍历,只需要将第一块分配即可。
组
组是对链表的一个改进,组将n个空闲块的地址存在第一个空闲块中。这n个空闲块的最后一个包含了另外n个空闲块的地址。
采用这种方式,大量的空闲块可以很快的找到。
计数
计数方法是基于一个事实:通常释放空间时会有多个连续分配的空间等待释放。
通过记录连续空闲磁盘块中第一块的地址和连续空闲块的数量,可以节省一定的空间。
效率和性能
效率
要考虑的方向:
- 磁盘分配
- 目录管理算法
- 保留在文件目录条目内的数据类型(如最近访问时间等)
设计文件系统时,一般与文件相关的所有设计项都要考虑
一些实例参考p371
性能
为了提高性能可以采用的措施有:
- 使用一块独立内存作为缓冲缓存
-
采用页面缓存(page cache)缓存文件数据
页面缓存采用虚拟内存技术,这相比于直接采用物理地址而言,更高效。这称为统一虚拟内存(unified virtual memory)
-
统一缓冲缓存(unified buffer memory)
- 双重缓冲
异步写,马上释放(free-behind),预读取(read-ahead)
恢复
一致性检查
一致性检查程序将 结构数据与磁盘数据块相比较,并试图纠正所发现的不一致。分配算法和空闲空间管理算法决定了检查程序能发现什么类型的问题,及其如何成功地纠正问题。
如果使用链接分配,那么每一块都指向下一块
备份和恢复
磁盘有时候会出错,为此,可以利用系统程序将磁盘数据备份到另一个存储设备。恢复单个文件或整个磁盘时,只需要从备份中进行恢复就可以了。
备份有两种形式:
- 完全备份
- 增量备份
基于日志的文件系统
所有元数据按顺序写到日志上,执行一个任务的一组操作称为事务(transaction),这些修改一旦写到这个日志上之后,就可认为已经提交,系统调用就可返回到用户进程,以允许它继续执行。
当一个完整提交事务已完成,那么就可从日志文件中删除
如果系统崩溃,日志文件可能有零个或多个事务。此时日志文件上的事务已经提交到日志,但是没有在文件系统上完成,所以必须要完成。
如果一个事务被中断,即在系统崩溃之前,它还没有被提交,那么这些事务对文件系统的修改就必须被撤销,以恢复文件系统的一致性
采用日志的优点:
- 通过日志更新比直接在磁盘上进行更新要快,这种改善原因是顺序IO比随机IO的性能更好。
- 一致性检查比较浪费系统时间,并且可能不能恢复结构从而导致文件甚至整个目录的丢失。
NFS
NFS是用于通过局域网(或者广域网)访问远程文件的软件系统的实现和规范。
概述
NFS将一组互连工作站作为具有独立文件系统的机器组合。
NFS规范区分两种服务:
- 由安装机制所提供的服务
- 真正远程文件访问服务
相应地,有两种协议用于这两种服务:一种是安装协议,另一种是远程文件访问协议,即NFS协议。
安装协议
安装协议(mount protocol)在客户机和服务器之间建立初始逻辑连接。
安装操作包括要安装的远程目录的名称和存储它的服务器的名称。
输出文件系统的任何目录可以被授权机器远程安装。组件单元就是这样一个目录。当服务器收到满足其输出列表的安装请求时,它就返回客户机一个文件句柄(file handle),为进一步访问安装文件系统内的文件所用。
该文件句柄包括服务器区分其所存储单个文件的所有信息
服务器也维护一个客户机及相应当前安装目录的列表。
NFS协议
NFS协议提供了一组RPC以供远程文件操作,包括:
- 搜索目录内的文件
- 读一组目录条目
- 访问文件属性
- 读和写文件
只有在远程目录的句柄已经建立之后,才可以进行这些操作。
NFS服务器的一个显著特点就是无状态(stateless)。服务器不维护客户机的每一步访问信息。服务器端没有像UNIX中的打开文件表和文件结构。因此,每个请求必须提供完整参数。
这种设计比较稳定,不需要采用特别措施以从崩溃状态中恢复过来。
客户机通过普通系统调用来启动操作。操作系统层将这个调用映射成相应vnode的VFS操作。VFS层发现这个文件是远程的,因此调用适当NFS程序。一个RPC调用发送到远程的NFS程序层。该调用又插入到远程系统的VFS层,远程系统发现这个是本地的并调用适当文件系统操作。计算结果再按这个步骤返回到客户机。
这种结构的特点是客户机和服务器是对等的,一个机器可以是客户机、服务器,或两个都是。
路径名转换
路径名转换将路径分成组成名称,并为每个组成名称和目录虚拟节点对执行独立的NFS lookup调用。
如将
/usr/local/dir1/file.txt
转换为usr
、local
、dir1
为了加快查找,客户端的路径转换缓存可保存远程目录名称的虚拟节点。这种缓存加快了引用具有同样初始路径名称文件的速度。当从服务器所返回的属性与缓存内的属性不匹配时,目录缓存就要加以更新。
远程操作
除了打开和关闭文件,在普通UNIX文件操作系统调用和NFS协议RPC之间,几乎有一个一对一的对应关系。因此,远程文件操作可直接转换成相应的RPC。
NFS坚持远程服务形式,但是实际上采用了缓冲和缓存技术以提高性能。
有两种缓存:文件属性(索引节点信息)缓存和文件块缓存。在打开文件时,内核检查远程服务器以确定是否要获取信息或使缓存信息重新生效。
只有缓存属性为最新时,才使用相应文件数据块。