Skip to content

Latest commit

 

History

History
68 lines (42 loc) · 6.69 KB

14.2-file-system-api.md

File metadata and controls

68 lines (42 loc) · 6.69 KB

14.2 File system实现概述

为了理解文件系统必须提供什么能力,让我们再看一下一些与文件系统相关的基础系统调用。从这些系统调用接口,我们将可以推断出有关文件系统实现的一些细节,这些系统调用我们在之前的课程已经看过了。首先让我们来看一个简单的场景,假设我们创建了文件“x/y”,或者说在目录x中创建了文件y,同时我们需要提供一些标志位,现在我们还不太关心标志位所以我会忽略它。

上面的系统调用会创建文件,并返回文件描述符给调用者。调用者也就是用户应用程序可以对文件描述符调用write,有关write我们在之前已经看过很多次了,这里我们向文件写入“abc”三个字符。

从这两个调用已经可以看出一些信息了:

  • 首先出现在接口中的路径名是可读的名字,而不是一串数字,它是由用户选择的字符串。
  • write系统调用并没有使用offset作为参数,所以写入到文件的哪个位置是隐式包含在文件系统中,文件系统在某个位置必然保存了文件的offset。因为如果你再调用write系统调用,新写入的数据会从第4个字节开始。

除此之外,还有一些我们之前没有看过的有趣的系统调用。例如XV6和所有的Unix文件系统都支持通过系统调用创建链接,给同一个文件指定多个名字。你可以通过调用link系统调用,为之前创建的文件“x/y”创建另一个名字“x/z”。

所以文件系统内部需要以某种方式跟踪指向同一个文件的多个文件名。

我们还可能会在文件打开时,删除或者更新文件的命名空间。例如,用户可以通过unlink系统调用来删除特定的文件名。如果此时相应的文件描述符还是打开的状态,那我们还可以向文件写数据,并且这也能正常工作。

所以,在文件系统内部,文件描述符必然与某个对象关联,而这个对象不依赖文件名。这样,即使文件名变化了,文件描述符仍然能够指向或者引用相同的文件对象。所以,实际上操作系统内部需要对于文件有内部的表现形式,并且这种表现形式与文件名无关。

除此之外,我还想提一点。文件系统的目的是实现上面描述的API,也即是典型的文件系统API。但是,这并不是唯一构建一个存储系统的方式。如果只是在磁盘上存储数据,你可以想出一个完全不同的API。举个例子,数据库也能持久化的存储数据,但是数据库就提供了一个与文件系统完全不一样的API。所以记住这一点很重要:还存在其他的方式能组织存储系统。我们这节课关注在文件系统,文件系统通常由操作系统提供,而数据库如果没有直接访问磁盘的权限的话,通常是在文件系统之上实现的(注,早期数据库通常直接基于磁盘构建自己的文件系统,因为早期操作系统自带的文件系统在性能上较差,且写入不是同步的,进而导致数据库的ACID不能保证。不过现代操作系统自带的文件系统已经足够好,所以现代的数据库大部分构建在操作系统自带的文件系统之上)。

学生提问:link增加了了对于文件的一个引用,unlink减少了一个引用?

Frans教授:是的。我们稍后会介绍更多相关的内容。

学生提问:能介绍一下soft link和hard link吗?

Frans教授:我今天不会讨论这些内容。但是你们将会在下一个File system lab中实现soft link。所以XV6本身实现了hard link,需要你们来实现soft link。

学生提问:link是对inode做操作,而不是对文件描述符做操作,对吧?

Frans教授:是的,link是对inode做操作,我们接下来介绍这部分内容。

接下来我们看一下文件系统的结构。文件系统究竟维护了什么样的结构来实现前面介绍的API呢?

首先,最重要的可能就是inode,这是代表一个文件的对象,并且它不依赖于文件名。实际上,inode是通过自身的编号来进行区分的,这里的编号就是个整数。所以文件系统内部通过一个数字,而不是通过文件路径名引用inode。同时,基于之前的讨论,inode必须有一个link count来跟踪指向这个inode的文件名的数量。一个文件(inode)只能在link count为0的时候被删除。实际的过程可能会更加复杂,实际中还有一个openfd count,也就是当前打开了文件的文件描述符计数。一个文件只能在这两个计数器都为0的时候才能被删除。

同时基于之前的讨论,我们也知道write和read都没有针对文件的offset参数,所以文件描述符必然自己悄悄维护了对于文件的offset。

文件系统中核心的数据结构就是inode和file descriptor。后者主要与用户进程进行交互。

尽管文件系统的API很相近并且内部实现可能非常不一样。但是很多文件系统都有类似的结构。因为文件系统还挺复杂的,所以最好按照分层的方式进行理解。可以这样看:

  • 在最底层是磁盘,也就是一些实际保存数据的存储设备,正是这些设备提供了持久化存储。
  • 在这之上是buffer cache或者说block cache,这些cache可以避免频繁的读写磁盘。这里我们将磁盘中的数据保存在了内存中。
  • 为了保证持久性,再往上通常会有一个logging层。许多文件系统都有某种形式的logging,我们下节课会讨论这部分内容,所以今天我就跳过它的介绍。
  • 在logging层之上,XV6有inode cache,这主要是为了同步(synchronization),我们稍后会介绍。inode通常小于一个disk block,所以多个inode通常会打包存储在一个disk block中。为了向单个inode提供同步操作,XV6维护了inode cache。
  • 再往上就是inode本身了。它实现了read/write。
  • 再往上,就是文件名,和文件描述符操作。

不同的文件系统组织方式和每一层可能都略有不同,有的时候分层也没有那么严格,即使在XV6中分层也不是很严格,但是从概念上来说这里的结构对于理解文件系统还是有帮助的。实际上所有的文件系统都有组件对应这里不同的分层,例如buffer cache,logging,inode和路径名。