OS- -文件系统(二)
OS- -文件系统(二)
文章目录
一、文件系统的实现
在对文件有了基本认识之后,现在是时候把目光转移到文件系统的实现上了。
之前用户关心的一直都 是文件是怎样命名的、可以进行哪些操作、目录树
是什么,如何找到正确的文件路径等问题。
而设计人 员关心的是文件和目录是怎样存储的、磁盘空间是如何管理的、如何使文件系统得以流畅运行
的问题, 下面我们就来一起讨论一下这些问题。
1.文件系统布局
-
文件系统存储在磁盘中。大部分的磁盘能够划分出一到多个分区,叫做磁盘分区(disk partitioning)或者是磁盘分片(disk slicing)
-
每个分区都有独立的文件系统,每块分区的文 件系统可以不同
。磁盘的0号分区称为 主引导记录(Master Boot Record, MBR),用来引导 (boot)计算机。 -
在
MBR的结尾是分区表(partition table) ,每个分区表给出每个分区由开始到 结束的地址
。 - 系统管理员使用一个称为分区编辑器的程序来创建,调整大小,删除和操作分区。
- 这种方 式的一个缺点是很难适当调整分区的大小,导致一个分区具有很多可用空间,而另一个分区几乎完全被 分配。
- MBR可以用在DOS、Microsoft Windows和Linux操作系统中。
- 从2010年代中期开始,大多数新计算机都改用GUID分区表(GPT)分区方案。
下面是一个用GParted进行分区的磁盘,表中的分区都被认为是 活动的(active)
引导块
-
MBR做的第一件事就是确定活动分区,
读入它的第一个块,称为引导块(boot block)并执行。引导块中的程序将加载分区中的操作系统
。 -
为了一致性,每个分区都会从引导块开始,即使引导块不包含 操作系统
。引导块占据文件系统的前4096个字节,从磁盘上的字节偏移量0开始。引导块可用于启动 操作系统。 - 在计算机中,引导就是启动计算机的过程,它可以通过硬件(例如按下电源按钮)或者软件命令 的方式来启动。
- 开机后,电脑的CPU还不能执行指令,因为此时没有软件在主存中,所以一些软 件必须先被加载到内存中,然后才能让CPU开始执行。也就是计算机开机后,首先会进行软件的 装载过程。
- 重启电脑的过程称为重新引导(rebooting),从休眠或睡眠状态返回计算机的过程不涉及启动。
- 除了从引导块开始之外,磁盘分区的布局是随着文件系统的不同而变化的。
通常文件系统会包含一些属 性,如下:
超级块
-
紧跟在引导块后面的是 超级块(Superblock),超级块的大小为4096字节,从磁盘上的字节偏移 4096开始。
- 超级块包含文件系统的所有关键参数
- •文件系统的大小
- •文件系统中的数据块数
- •指示文件系统状态的标志
- •分配组大小
在计算机启动或者文件系统首次使用时,超级块会被读入内存
。
空闲空间块
接着是文件系统中空闲块的信息,例如,可以用位图或者指针列表的形式给出。
-
BitMap位图或者Bit vector位向量
-
位图或位向量是一系列位或位的集合,其中每个位对应一个磁盘块,该位可以采用两个值:0和1, 0表 示已分配该块,而1表示一个空闲块。
下图中的磁盘上给定的磁盘块实例(分配了绿色块)可以用16位 的位图表示为:0000111000000110。
-
使用链表进行管理
-
在这种方法中,
空闲磁盘块链接在一起,即一个空闲块包含指向下一个空闲块的指针
。第一个磁盘块的 块号存储在磁盘上的单独位置,也缓存在内存中
碎片
-
这里不得不提一个叫做碎片(fragment)的概念,也称为片段。一般零散的单个数据通常称为片段。
-
磁盘块可以进一步分为固定大小的分配单元,片段只是在驱动器上彼此不相邻的文件片段
。
如果你不理 解这个概念就给你举个例子。
- 比如你用Windows电脑创建了一个文件,你会发现这个文件可以存储在 任何地方,比如存在桌面上,存在磁盘中的文件夹中或者其他地方。
- 你可以打开文件,编辑文件,删除文件等等。
- 你可能以为这些都在一个地方发生,但是
实际上并不是,你的硬盘驱动器可能会将文件中的 一部分存储在一个区域内,另一部分存储在另外一个区域,在你打开文件时,硬盘驱动器会迅速的将文件的所有部分汇总在一起,以便其他计算机系统可以使用它
inode
- 然后在后面是一个
inode(index node),也称作索引节点。它是一个数组的结构,每个文件有一个 inode, inode非常重要,它说明了文件的方方面面。
- 每个索引节点都存储对象数据的属性和磁盘块位置有一种简单的方法可以找到它们Is -lai命令。
让我们看一下根文件系统:
- inode节点主要包括了以下信息
- •模式/权限(保护)
- •所有者ID
- •组ID
- •文件大小
- •文件的硬链接数
- •上次访问时间
- •最后修改时间
- • inode上次修改时间
-
文件分为两部分,索引节点和块。一旦创建后,每种类型的块数是固定的。你不能增加分区上inode的 数量,也不能增加磁盘块的数量。
-
紧跟在inode后面的是根目录,它存放的是文件系统目录树的根部。最后,磁盘的其他部分存放了其他 所有的目录和文件
。
2.文件的实现
- 最重要的问题是记录各个文件分别用到了哪些磁盘块。不同的系统采用了不同的方法。
-
背后的主要思想是有效利用文件空间和快速访问文件
,主要有三种分配方案 - •
连续分配
- •
链表分配
- •
索引分配
连续分配
-
最简单的分配方案是把每个文件作为一连串连续数据块存储在磁盘上
因此,在具有1KB块的磁盘上,将为50 KB文件分配50个连续块。
- 上面展示了 40个连续的内存块。从最左侧的0块开始。初始状态下,还没有装载文件,因此磁盘是空 的。
- 接着,从磁盘开始处(块0 )处开始写入占用4块长度的内存A。然后是一个占用6块长度的内 存B,会直接在A的末尾开始写。
- 注意每个文件都会在新的文件块开始写,所以如果文件A只占用了 3又1/2个块,那么最后一个块 的部分内存会被浪费。
- 在上面这幅图中,总共展示了 7个文件,每个文件都会从上个文件的末尾块开始 写新的文件块。
- 连续的磁盘空间分配有两个优点:
- •第一,
连续文件存储实现起来比较简单
,只需要记住两个数字就可以:一个是第一个块的文件地址 和文件的块数量
。给定第一个块的编号,可以通过简单的加法找到任何其他块的编号。 - •第二点是
读取性能比较强,可以通过一次操作从文件中读取整个文件
。只需要一次寻找第一个块。 - 后面就不再需要寻道时间和旋转延迟,所以数据会以全带宽进入磁盘。
因此,连续的空间分配具有实现简单、高性能的特点
。
不幸的是,连续空间分配也有很明显的不足。随着时间的推移,磁盘会变得很零碎。
下图解释了这种现 象
- 这里有两个文件D和F被删除了。
当删除一个文件时,此文件所占用的块也随之释放,就会在磁盘空 间中留下一些空闲块
。 -
磁盘并不会在这个位置挤压掉空闲块
,因为这会复制空闲块之后的所有文件,可 能会有上百万的块,这个量级就太大了。 - 刚开始的时候,这个碎片不是问题,因为每个新文件都会在之前文件的结尾处进行写入。
- 然而,磁盘最 终会被填满,
因此要么压缩磁盘、要么重新使用空闲块的空间
。压缩磁盘的开销太大,因此不可行;后 者会维护一个空闲列表,这个是可行的
。但是这种情况又存在一个问题,为空闲块匹配合适大小的文 件,需要知道该文件的最终大小
。 - 想象一下这种设计的结果会是怎样的。用户启动word进程创建文档。应用程序首先会询问最终创建的 文档会有多大。这个问题必须回答,否则应用程序就不会继续执行。
- 如果空闲块的大小要比文件的大小 小,程序就会终止。因为所使用的磁盘空间已经满了。那么现实生活中,有没有使用连续分配内存的介 质出现呢?
- CD-ROM就广泛的使用了连续分配方式。
- CD-ROM (Compact Disc Read-Only Memory)即只读光盘,也称作只读存储器。
- 是一种在电脑上使用的光碟。
- 这种光碟只能写入数据一次,信息将永久保存在光碟上,使用时通过光碟驱动 器读出信息。
- 然而DVD的情况会更加复杂一些。原则上,一个90分钟的电影能够被编码成一个独立的、大约4.5 GB的文件。
- 但是文件系统所使用的UDF(Universal Disk Format)格式,使用一个30位的数来 代表文件长度,从而把文件大小限制在1 GB。
- 所以,DVD电影一般存储在3、4个连续的1 GB空间 内。这些构成单个电影中的文件块称为扩展区(extends)。
- 就像我们反复提到的,历史总是惊人的相似,许多年前,
连续分配由于其简单和高性能被实际使用 在磁盘文件系统中。后来由于用户不希望在创建文件时指定文件的大小,于是放弃了这种想法
。 - 但是随 着CD-ROM、DVD、蓝光光盘等光学介质的出现,连续分配又流行起来。从而得出结论,技术永远没 有过时性,现在看似很老的技术,在未来某个阶段可能又会流行起来。
链表分配
-
第二种存储文件的方式是为每个文件构造磁盘块链表,每个文件都是磁盘块的链接列表
就像下面所示:
-
每个块的第一个字作为指向下一块的指针,块的其他部分存放数据
。 - 如果上面这张图你看的不是很清楚 的话,可以看看整个的链表分配方案
-
与连续分配方案不同,
这一方法可以充分利用每个磁盘块。除了最后一个磁盘块外,不会因为磁盘碎片 而浪费存储空间
。同样,在目录项中,只要存储了第一个文件块,那么其他文件块也能够被找到。 -
另一方面,在链表的分配方案中,尽管顺序读取非常方便,但是
随机访问却很困难(这也是数组和链表 数据结构的一大区别)
。 -
还有一个问题是,
由于指针会占用一些字节,每个磁盘块实际存储数据的字节数并不再是2的整数次 幂
。虽然这个问题并不会很严重,但是这种方式降低了程序运行效率。 -
许多程序都是以长度为2的整数 次幕来读写磁盘,
由于每个块的前几个字节被指针所使用,所以要读出一个完成的块大小信息,就需要 当前块的信息和下一块的信息拼凑而成,因此就引发了查找和拼接的开销
。
使用内存表进行链表分配
由于连续分配和链表分配都有其不可忽视的缺点。
-
所以提出了使用内存中的表来解决分配问题。取出每 个磁盘块的指针字,把它们放在内存的一个表中,就可以解决上述链表的两个不足之处。
下面是一个例 子:
- 上图表示了链表形成的磁盘块的内容。这两个图中都有两个文件,文件A依次使用了磁盘块地址4、 7、2、10、12,文件B使用了6、3、11和14。
- 也就是说,文件A从地址4处开始,顺着链表走 就能找到文件A的全部磁盘块。同样,从第6块开始,顺着链走到最后,也能够找到文件B的全部磁 盘块。
- 你会发现,
这两个链表都以不属于有效磁盘编号的特殊标记(-1)结束。内存中的这种表格称为文件分配表(File Application Table,FAT)
。 - 使用
这种组织方式,整个块都可以存放数据。进而,随机访问也容易
很多。虽然仍要顺着链在内存中查 找给定的偏移量,但是整个链都存放在内存中,所以不需要任何磁盘引用。 - 与前面的方法相同,不
管文 件有多大,在目录项中只需记录一个整数(起始块号),按照它就可以找到文件的全部块
。 - 这种方式存在缺点,那就是必须要把整个链表放在内存中。对于1TB的磁盘和1KB的大小的块,那么 这张表需要有10亿项。
- 每一项对应于这10亿个磁盘块中的一块。每项至少3个字节,
为了提高 查找速度,有时需要4个字节。根据系统对空间或时间的优化方案
,这张表要占用3GB或2.4GB的内 存。 - FAT的管理方式不能较好地扩展并应用于大型磁盘中。而这正是最初MS-DOS文件比较实用,并 仍被各个Windows版本所安全支持。
inode
-
最后一个
记录各个文件分别包含哪些磁盘块的方法是给每个文件赋予一个称为inode(索引节点)的数 据结构
,每个文件都与一个iode进行关联,inode由整数进行标识。
下面是一个简单例子的描述:
- 相对于在内存中使用表的方式而言,这种机制具有很大的优势。
即只有在文件打开时,其inode才会在 内存中
。 -
如果每个inode需要n个字节,最多k个文件同时打开,那么inode占有总共打开的文件是 kn字节。仅需预留这么多空间
。 - 这个数组要比我们上面描述的FAT(文件分配表)占用的空间小的多。
原因是用于保存所有磁盘块的链 接列表的表的大小与磁盘本身成正比
。 - 如果磁盘有n个块,那么这个表也需要n项。随着磁盘空间的 变大,那么该表也随之线性增长。
- 相反,inode需要节点中的数组,其大小和可能需要打开的最大文 件个数成正比。它与磁盘是100GB. 4000GB还是10000GB无关。
-
inode的一个问题是如果每个节点都会有固定大小的磁盘地址,那么文件增长到所能允许的最大容量外 会发生什么
? - 一个解决方案
是最后一个磁盘地址不指向数据块,而是指向一个包含额外磁盘块地址的地 址
,如上图所示。 - 一个更高级的解决方案是:
有两个或者更多包含磁盘地址的块,或者指向其他存放地 址的磁盘块的磁盘块
。 - Windows的NTFS文件系统采用了相似的方法,所不同的仅仅是大的inode也 可以表示小的文件。
NTFS的全称是New Technology File System ,是微软公司开发的专用系统文件,NTFS取
代FAT(文件分配表)和HPFS(高性能文件系统),并在此基础上进一步改进。例如增强对元数据
的支持,使用更高级的数据结构以提升性能、可靠性和磁盘空间利用率等。
3.目录的实现
-
文件只有打开后才能够被读取。在文件打开后,操作系统会使用用户提供的路径名来定位磁盘中的目 录
。 -
目录项提供了查找文件磁盘块所需要的信息。根据系统的不同,提供的信息也不同,可能提供的信 息是整个文件的磁盘地址,或者是第一个块的数量(两个链表方案)或inode的数量。
-
不过不管用那种 情况,
目录系统的主要功能就是将文件的ASCII码的名称映射到定位数据所需的信息上
。 - 与此关系密切的问题是属性应该存放在哪里。每个文件系统包含不同的文件属性,例如文件的所有者和 创建时间,需要存储的位置。
-
一种显而易见的方法是直接把文件属性存放在目录中。有一些系统恰好是 这么做的,如下:
- 在这种简单的设计中,
目录有一个固定大小的目录项列表,每个文件对应一项,其中包含一个固定长度 的文件名,文件属性的结构体以及用以说明磁盘块位置的一个或多个磁盘地址
。 -
对于采用inode的系统,会把inode存储在属性中而不是目录项中。在这种情况下,目录项会更短:仅 仅只有文件名称和inode数量
。
这种方式如下所示:
- 到目前为止,我们已经
假设文件具有较短的、固定长度的名字
。在MS-DOS中,具有1 - 8个字符的 基本名称和1 一 3个字符的可拓展名称。 - 在UNIX版本7中,文件有1 - 14个字符,包括任何拓展。然 而,几乎所有的现代操作系统都支持可变长度的扩展名。这是如何实现的呢?
-
最简单的方式是给予文件名一个长度限制,比如255个字符
,然后使用上图中的设计,并为每个文件名 保留255个字符空间。 - 这种
处理很简单,但是浪费了大量的目录空间,因为只有很少的文件会有那么长 的文件名称。所以,需要一种其他的结构来处理
。 - 一种可选择的方式是
放弃所有目录顼大小相同的想法
。在这种方法中,每个目录项都包含一个固定部 分,这个固定部分通常以目录项的长度开始
,后面是固定格式的数据,通常包括所有者、创建时间、保 护信息和其他属性
。这个固定长度的头的后面是一个任意长度的实际文件名
如下图所示:
-
处理机中的一串字符存放的顺序有正序(big-endian)和逆序(little-endian)之分。正序 存放的就是高字节在前低字节在后,而逆序存放的就是低字节在前高字节在后
。 - 这个例子中,有三个文件,分别是project-budget、personnel和foo ,每个文件名以一个特 殊字符(通常是0)结束,用矩形中的叉进行表示。
- 为了使每个目录项从字的边界开始,每个文件名被 填充成整数个字
如下图所示:
-
这个
方法的缺点是当文件被移除后,就会留下一块固定长度的空间,而新添加进来的文件大小不一定和 空闲空间大小一致
。 -
这个问题与我们上面探讨的连续磁盘文件的问题是一样的,
由于整个目录在内存中,所以只有对目录进 行紧凑拼接操作才可节省空间
。 -
另一个问题是,一
个目录项可能会分布在多个页上,在读取文件名时 可能发生缺页中断
。 -
处理可变长度文件名字的另外一种方法是,使目录项自身具有固定长度,而将文件名放在目录末尾的堆 栈中。
-
如上图所示的这种方式。
这种方法的优点是当目录项被移除后,下一个文件将能够正常匹配移除 文件的空间。当然,必须要对堆进行管理,因为在处理文件名的时候也会发生缺贞异常
。 -
到目前为止的所有设计中,在需要查找文件名时,
所有的方案都是线性的从头到尾对目录进行搜索。对 于特别长的目录,线性搜索的效率很低
。 -
提高文件检索效率的一种方式是
在每个目录上使用哈希表
(hash table),也叫做散列表。 -
我们假设表的大小为n,在输入文件名时,文件名被散列在。和n - 1之间,例如,它被n除,并取余数。或者对构成文件名字的字求和或类似某种方法。
-
无论采用哪种方式,
在添加一个文件时都要对与散列值相对应的散列表进行检查
。 -
如果没有使用过,就会将一个指向目录项的指针指向这里。文件目录项紧跟着哈希表后面。
-
如果已经使用过,就会构造一 个链表
,链表的表头指针存放在表项中, 并通过哈希值将所有的表项相连。 -
查找文件的过程和添加类似,首先对文件名进行哈希处理,在哈希表中查找是否有这个哈希值,如果有 的话,就检查这条链上所有的哈希项,查看文件名是否存在。如果哈希不在链上,那么文件就不在目录 中。
-
使用
哈希表的优势是查找非常迅速,缺点是管理起来非常复杂
。只有在系统中会有成千上万个目录项 存在时,才会考虑使用散列表作为解决方案。 -
另外一种在大量目录中加快查找指令目录的方法是使用缓存,缓存查找的结果。在开始查找之前,会 首先检查文件名是否在缓存中。如果在缓存中,那么文件就能立刻定位。当然,只有在较少的文件下进 行多次查找,缓存才会发挥最大功效。