OS- -文件系统(三)

OS- -文件系统(三)

一、文件系统的实现

1.共享文件

  • 当多个用户在同一个项目中工作时,他们通常需要共享文件。如果这个共享文件同时出现在多个用户目 录下,那么他们协同工作起来就很方便。

下面的这张图我们在上面提到过,但是有一个更改的地方,就 是C的一个文件也出现在了 B的目录下。
在这里插入图片描述

  • 如果按照如上图的这种组织方式而言,那么B的目录与该共享文件的联系称为 链接(link)。那么文 件系统现在就是一个有向无环图(Directed Acyclic Graph,简称DAG),而不是一棵树了
  • 在图论中,如果一个有向图从任意顶点出发无法经过若干条边回到该点,则这个图是一个有向无 环图
  • 将文件系统组织成为有向无环图会使得维护复杂化,但也是必须要付出的代价。 共享文件很方便,但这也会带来一些问题。
  • 如果目录中包含磁盘地址,则当链接文件时,必须把C目 录中的磁盘地址复制到B目录中
  • 如果B或者C随后又向文件中添加内容,则仅在执行追加的用户的 目录中显示新写入的数据块。这种变更将会对其他用户不可见,从而破坏了共享的目的。
    在这里插入图片描述
  • 有两种方案可以解决这种问题:
  • 第一种解决方案,磁盘块不列入目录中,而是会把磁盘块放在与文件本身相关联的小型数据结构中。目录将指向这个小型数据结构。这是UNIX中使用的方式(小型数据结构就是inode)
  • 在第二种解决方案中,通过让系统建立一个类型为LINK的新文件,并把该文件放在B的目录 下,使得B与C建立链接。新的文件中只包含了它所链接的文件的路径名。
  • B想要读取文件 时,操作系统会楼查B的目录下存在一个类型为LINK的文件,进而找到该链接的文件和路径名, 然后再去读文件,这种方式称为 符号链接(symbolic linking)
    在这里插入图片描述
  • 上面的每一种方法都有各自的缺点,在第一种方式中,B链接到共享文件时,inode记录文件的所有者 为C。

建立一个链接并不改变所有关系,如下图所示:
在这里插入图片描述

  • 第一开始的情况如图a所示,此时C的目录的所有者是C ,当目录B链接到共享文件时,并不会改变 C的所有者关系,只是把计数+ 1,所以此时系统知道目前有多少个目录指向这个文件。
  • 然后C尝试 删除这个文件,这个时候有个问题,如果C把文件移除并清除了 inode的话,那么B会有一个目录项 指向无效的节点
  • 如果inode以后分配给另一个文件,则B的链接指向一个错误的文件。系统通过 inode可知文件仍在被引用,但是没有办法找到该文件的全部目录项以删除它们
  • 指向目录的指针不能 存储在inode中,原因是有可能有无数个这样的目录。
  • 所以我们能做的就是删除C的目录项,但是将inode保留下来,并将计数设置为1,如上图c所示。 c表示的是只有B有指向该文件的目录项,而该文件的前者是C。如果系统进行记账操作的话,那么 C将继续为该文件付账直到B决定删除它,如果是这样的话,只有到计数变为0的时刻,才会删除该 文件
  • 对于符号链接,以上问题不会发生,只有真正的文件所有者才有一个指向inode的指针。链接到该文 件上的用户只有路径名,没有指向inode的指针
  • 文件所有者删除文件时,该文件被销毁。以后若试 图通过符号链接访问该文件将会失败,因为系统不能找到该文件。删除符号链接不会影响该文件
  • 符号链接的问题是需要额外的开销。必须读取包含路径的文件,然后要一个部分接一个部分地扫描路 径,直到找到inode。这些操作也许需要很多次额外的磁盘访问。
  • 此外,每个符号链接都需要额外的 inode,以及额外的一个磁盘块用于存储路径,虽然如果路径名很短,作为一种优化,系统可以将它存 储在inode中。
  • 符号链接有一个优势,即只要简单地提供一个机器的网络地址以及文件在该机器上驻留 的路径,就可以连接全球任何地方机器上的文件
  • 还有另一个由链接带来的问题,在符号链接和其他方式中都存在。如果允许链接,文件有两个或多个路 径。查找一指定目录及其子目录下的全部文件的程序将多次定位到被链接的文件
  • 例如,一个将某一目 录及其子目录下的文件转存到磁带上的程序有可能多次复制一个被链接的文件。
  • 进而,如果接着把磁带 读入另一台机器,除非转出程序具有智能,否则被链接的文件将被两次复制到磁盘上,而不是只是被链 接起来。

2.日志结构文件系统

  • 技术的改变会给当前的文件系统带来压力。这种情况下,CPU会变得越来越快,磁盘会变得越来越大 并且越来越便宜(但不会越来越快)。
  • 内存容量也是以指数级增长。但是磁盘的寻道时间(除了固态 盘,因为固态盘没有寻道时间)并没有获得提高
  • 这些因素结合起来意味着许多系统文件中出现性能瓶颈。为此,Berkeley设计了一种全新的文件系 统,试图缓解这个问题,这个文件系统就是 日志结构文件系统(Log-structured File System, LFS)
  • 日志结构文件系统由Rosenblum和Ousterhout于90年代初引入,旨在解决以下问题。
  • 不断增长的系统内存
  • •顺序I/O性能胜过随机I/O性能
  • 现有低效率的文件系统
  • •文件系统不支持RAID (虚拟化)
  • 另一方面,当时的文件系统不论是UNIX还是FFS,都有大量的随机读写(在FFS中创建一个新文件至少需要5次随机写),因此成为整个系统的性能瓶颈。
  • 同时因为Page cache的存在,作者认为随 机读不是主要问题:随着越来越大的内存,大部分的读操作都能被cache,因此LFS主要要解决的是 减少对硬盘的随机写操作
  • 在这种设计中,inode甚至具有与UNIX中相同的结构,但是现在它们分散在整个日志中,而不是位于 磁盘上的固定位置。
  • 所以,inode很定位。为了能够找到inode ,维护了一个由inode索引的inode map(inode映射)。表项i指向磁盘中的第i个inode o这个映射保存在磁盘中,但是也保存在缓存 中,因此,使用最频繁的部分大部分时间都在内存中
  • 结构文件系统主要使用四种数据结构:Inode、Inode Map、Segment. Segment Usage Tableo

在这里插入图片描述

  • 到目前为止,所有写入最初都缓存在内存中,并且追加在日志末尾,所有缓存的写入都定期在单个段中写入磁盘。所以,现在打开文件也就意味着用映射定位文件的索引节点。
  • 一旦inode被定位后,磁盘 块的地址就能够被找到。所有这些块本身都将位于日志中某处的分段中。
  • 真实情况下的磁盘容量是有限的,所以最终日志会占满整个磁盘空间,这种情况下就会出现没有新的磁 盘块被写入到日志中
  • 幸运的是,许多现有段可能具有不再需要的块。例如,如果一个文件被覆盖了, 那么它的inode将被指向新的块,但是旧的磁盘块仍在先前写入的段中占据着空间。
  • 为了处理这个问题,LFS有一个清理(clean)线程,它会循环扫描日志并对日志进行压缩。
  • 首先,通 过查看日志中第一部分的信息来查看其中存在哪些索引节点和文件。它会检查当前inode的映射来查看 inode否在在当前块中,是否仍在被使用。
  • 如果不是,该信息将被丢弃。如果仍然在使用,那么inode 和块就会进入内存等待写回到下一个段中
  • 然后原来的段被标记为空闲,以便日志可以用来存放新的数 据。
  • 用这种方法,清理线程遍历日志,从后面移走旧的段,然后将有效的数据放入内存等待写到下一个 段中。
  • 由此一来整个磁盘会形成一个大的环形缓冲区,写线程将新的段写在前面,而清理线程则清理 后面的段
    在这里插入图片描述

3.日志文件系统

  • 虽然日志结构系统的设计很优雅,但是由于它们和现有的文件系统不相匹配,因此还没有广泛使用。
  • 不 过,从日志文件结构系统衍生出来一种新的日志系统,叫做日志文件系统,它会记录系统下一步将要 做什么的日志。
  • 微软的NTFS文件系统、Linux的ext3就使用了此日志。OS X将日志系统作为 可供选项。
  • 为了看清它是如何工作的,我们下面讨论一个例子,比如移除文件,这个操作在UNIX中 需要三个步骤完成:
  • 在目录中删除文件
  • 释放inode到空闲inode池
  • 将所有磁盘块归还给空闲磁盘池
  • 在Windows中,也存在类似的步骤。不存在系统崩溃时,这些步骤的执行顺序不会带来问题。但是一 旦系统崩溃,就会带来问题。
  • 假如在第一步完成后系统崩溃。
  • inode和文件块将不会被任何文件获得,也不会再分配;它们只存在于废物池中的某个地方,并因此减少了可利用的资源。
  • 如果崩溃发生在第二步后,那么只有磁盘块会丢失。
  • 日志文件系统保留磁盘写入期间对文件系统所做的更改的日志或日志,该日志可用于快速重建可能由于系统崩溃或断电等事件而发生的损坏。
  • 一般文件系统崩溃后必须运行fsck (文件系统一致性检查)实用程序
  • 为了让日志能够正确工作,被写入的日志操作必须是 慕等的(idempotent),它意味着只要有必要,它们就可以重复执行很多次,并不会带来破坏。
  • 像操作更新位表并标记inode k或者块n是空闲的可 以重复执行任意次。同样地,查找一个目录并且删除所有叫foobar的项也是幕等的。
  • 相反,把从 inode k新释放的块加入空闲表的末端不是幕等的,因为它们可能已经被释放并存放在那里了。
    在这里插入图片描述
  • 为了增加可靠性,一个文件系统可以引入数据库中 原子事务(atomic transaction)的概念。
  • 使用 这个概念,一组动作可以被界定在开始事务和结束事务操作之间。这样,文件系统就会知道它必须完成 所有的动作,要么就一个不做

4.虚拟文件系统

即使在同一台计算机上或者在同一个操作系统下,都会使用很多不同的文件系统。

  • Windows中的主要 文件系统是NTFS文件系统,但不是说Windows只有NTFS操作系统,它还有一些其他的例如旧的 FAT -32或FAT -16驱动器或分区,其中包含仍需要的数据,闪存驱动器,旧的CD-ROM或 DVD (每个都有自己的独特文件系统)。
  • Windows通过指定不同的盘符来处理这些不同的文件系统, 比如C: , D:等。盘符可以显示存在也可以隐式存在,如果你想找指定位置的文件,那么盘符是显 示存在;如果当一个进程打开一个文件时,此时盘符是隐式存在,所以Windows知道向哪个文件系统 传递请求
  • 相比之下,UNIX采用了一种不同的方式,即UNIX把多种文件系统整合到一个统一的结构中。
  • 一个 Linux系统可以使用ext2作为根文件系统,ext3分区装载在/usr下,另一块采用Reiser FS文件系统的硬盘装载到/home下,以及一个ISO 9660的CD - ROM临时装载到/mnt下
  • 从 用户的观点来看,只有一个文件系统层级,但是事实上它们是由多个文件系统组合而成,对于用户和进 程是不可见的。
  • UNIX操作系统使用一种虚拟文件系统(Virtual File System, VFS)来尝试将多种文件系统构成一 个有序的结构
  • 关键的思想是抽象出所有文件系统都共有的部分,并将这部分代码放在一层,这一层再 调用具体文件系统来管理数据。

下面是一个VFS的系统结构:
在这里插入图片描述

  • 还是那句经典的话,在计算机世界中,任何解决不了的问题都可以加个代理来解决。所有和文件相关 的系统调用在最初的处理上都指向虚拟文件系统
  • 这些来自用户进程的调用,都是标准的POSIX系统 调用,比如open、read、write和seek等。VFS对用户进程有一个 上层 接口,这个接口就是著名 的 POSIX 接口。
  • VFS也有一个对于实际文件的 下层 接口,就是上图中标记为VFS的接口。这个接口包含许多功能调 用,这样VFS可以使每一个文件系统完成任务。
  • 因此,要创建一个可以与VFS-起使用的新文件系 统,新文件系统的设计者必须确保它提供了 VFS要求的功能
  • 一个明显的例子是从磁盘读取特定的 块,然后将其放入文件系统的缓冲区高速缓存中,然后返回指向该块的指针的函数。因此,VFS具有两 个不同的接口:上一个到用户进程,下一个到具体文件系统
  • 当系统启动时,根文件系统在VFS中注册。另外,当装载其他文件时,不管在启动时还是在操作过程 中,它们也必须在VFS中注册
  • 当一个文件系统注册时,根文件系统注册到VFS。另外,在引导时或 操作期间挂载其他文件系统时,它们也必须向VFS注册。
  • 当文件系统注册时,其基本作用是提供VFS 所需功能的地址列表、调用向量表、或者VFS对象。因此一旦文件系统注册到VFS,它就知道从哪里开始读取数据块。装载文件系统后就可以使用它了

比如,如果一个文件系统装载到/usr并且一个进程调用它:

open("/usr/include/unistd.h",O_RDONLY)
  • 当解析路径时,VFS看到新的文件系统被挂载到/usr ,并且通过搜索已经装载文件系统的超级块来 确定它的超块。
  • 然后它找到它所转载的文件的根目录,在那里查找路径include/unistd.h。然后 VFS创建一个vnode并调用实际文件系统,以返回所有的在文件inode中的信息。
  • 这个信息和其他信 息一起复制到vnode (内存中)。而这些其他信息中最重要的是指向包含调用vnode操作的函数表的 指针,比如reads write和close等。
  • 当vnode被创建后,为了进程调用,VFS在文件描述符表中创建一个表项,并将它指向新的vnode, 最后,VFS向调用者返回文件描述符,所以调用者可以用它去read、write或者close文件。
  • 当进程用文件描述符进行一个读操作时,VFS通过进程表和文件描述符确定vnode的位置,并跟随指 针指向函数表,这样就调用了处理read函数,运行在实际系统中的代码并得到所请求的块。
  • VFS不知 道请求时来源于本地硬盘、还是来源于网络中的远程文件系统、CD-ROM . USB或者其他介质,所有 相关的数据结构欧如下图所示:
    在这里插入图片描述
  • 从调用者进程号和文件描述符开始,进而是vnode,读函数指针,然后是对实际文件系统的访问函数定 位。

二、文件系统的管理和优化

能够使文件系统工作是一回事,能够使文件系统高效、稳定的工作是另一回事,下面我们就来探讨一下 文件系统的管理和优化。

1.磁盘空间管理

文件通常存在磁盘中,所以如何管理磁盘空间是一个操作系统的设计者需要考虑的问题。

  • 在文件上进行 存储有两种策略:分配n个字节的连续磁盘空间;或者把文件拆分成多个并不一定连续的块。在存储管理系统中,主要有分段管理和分页管理两种方式
  • 正如我们所看到的,按连续字节序列存储文件有一个明显的问题,当文件扩大时,有可能需要在磁盘 上移动文件。内存中分段也有同样的问题。
  • 不同的是,相对于把文件从磁盘的一个位置移动到另一个位 置,内存中段的移动操作要快很多。因此,几乎所有的文件系统都把文件分割成固定大小的块来存储。

块大小

  • 一旦把文件分为固定大小的块来存储,就会出现问题,块的大小是多少?
  • 按照磁盘组织方式,扇区、磁 道和柱面显然都可以作为分配单位。在分页系统中,分页大小也是主要因素
  • 拥有大的块尺寸意味着每个文件,甚至1字节文件,都要占用一个柱面空间,也就是说小文件浪费了大 量的磁盘空间。
  • 另一方面,小块意味着大部分文件将会跨越多个块,因此需要多次搜索和旋转延退才能 读取它们,从而降低了性能。
  • 因此,如果分配的块太大会浪费空间;分配的块太小会浪费时间

记录空闲块

  • 一旦指定了块大小,下一个问题就是怎样跟踪空闲块。

  • 有两种方法被广泛采用,如下图所示:
    在这里插入图片描述

  • 第一种方法是采用磁盘块链表,链表的每个块中包含极可能多的空闲磁盘块号。

  • 对于1 KB的块和32 位的磁盘块号,空闲表中每个块包含有255个空闲的块号。

  • 考虑1TB的硬盘,拥有大概十亿个磁盘 块。

  • 为了存储全部地址块号,如果每块可以保存255个块号,贝蠕要将近400万个块。通常,空闲块 用于保存空闲列表,因此存储基本上是空闲的

  • 另一种空闲空间管理的技术是位图(bitmap) , n个块的磁盘需要n位位图。

  • 在位图中,空闲块用1 表示,已分配的块用0表示。

  • 对于1 TB硬盘的例子,需要10亿位表示,即需要大约130 000个1 KB块存储。很明显,和32位链表模型相比,位图需要的空间更少,因为每个块使用1位。只有当磁 盘快满的时候,链表需要的块才会比位图少

  • 如果空闲块是长期连续的话,那么空闲列表可以改成记录连续分块而不是单个的块。

  • 每个块都会使用8 位、16位、32位的计数来与每个块相联,来记录连续空闲块的数量。

  • 最好的情况是一个空闲块可以用 两个数字来表示:第一个空闲块的地址和空闲块的计数

  • 另一方面,如果磁盘严重碎片化,那么跟踪连 续分块要比跟踪单个分块运行效率低,因为不仅要存储地址,还要存储数量

  • 这种情况说明了一个操作系统设计者经常遇到的一个问题。有许多数据结构和算法可以用来解决 问题,但是选择一个最好的方案需要数据的支持,而这些数据是设计者无法预先拥有的。只有在 系统部署完毕真正使用使用后才会获得。

  • 现在,回到空闲链表的方法,只有一个指针块保存在内存中。创建文件时,所需要的块从指针块中取 出。当它用完时,将从磁盘中读取一个新的指针块

  • 类似地,删除文件时,文件的块将被释放并添加到 主存中的指针块中。当块被填满时,写回磁盘

在某些特定的情况下,这个方法导致了不必要的磁盘IO,如下图所示:
在这里插入图片描述

  • 上面内存中的指针块仅有两个空闲块,如果释放了一个含有三个磁盘块的文件,那么该指针块就会溢 出,必须将其写入磁盘,那么就会产生如下图的这种情况。
    在这里插入图片描述
  • 如果现在写入含有三个块的文件,已满的指针不得不再次读入,这将会回到上图a中的情况。
  • 如果有三 个块的文件只是作为临时文件被写入,在释放它时,需要进行另一次磁盘写操作以将完整的指针块写回 到磁盘。
  • 简而言之,当指针块几乎为空时,一系列短暂的临时文件可能会导致大量磁盘I/O。
  • 避免大部分磁盘I/O的另一种方法是拆分完整的指针块。这样,当释放三个块时,变化不再是从a - b,而是从a - c,如下图所示:
    在这里插入图片描述
  • 现在,系统可以处理一系列临时文件,而不需要进行任何磁盘I/O。
  • 如果内存中指针块满了,就写入磁 盘,半满的指针块从磁盘中读入。
  • 这里的思想是:要保持磁盘上的大多数指针块为满的状态(减少磁盘 的使用),但是在内存中保留了一个半满的指针块。这样,就可以既处理文件的创建又同时可以处理文 件的删除操作,而不会为空闲表进行磁盘I/O。
  • 对于位图,会在内存中只保留一个块,只有在该块满了或空了的情形下,才到磁盘上取另一个块。
  • 通过 在位图的单一块上进行所有的分配操作,磁盘块会紧密的聚集在一起,从而减少了磁盘臂的移动。由于 位图是一种固定大小的数据结构,所以如果内核是分页的,就可以把位图放在虚拟内存中,在需要时 将位图的页面调入。

猜你喜欢

转载自blog.csdn.net/wolfGuiDao/article/details/107753944