inode descriptor
文件系统中核心的数据结构就是inode和file descriptor。后者主要与用户进程进行交互。
inode,这是代表一个文件的对象,并且它不依赖于文件名。实际上,inode是通过自身的编号来进行区分的,这里的编号就是个整数。所以文件系统内部通过一个数字,而不是通过文件路径名引用inode。
inode必须有一个link count来跟踪指向这个inode的文件名的数量和当前打开了文件的文件描述符计数。一个文件只能在这两个计数器都为0的时候才能被删除。
因为文件系统还挺复杂的,所以最好按照分层的方式进行理解。可以这样看:
-
在最底层是磁盘,也就是一些实际保存数据的存储设备,正是这些设备提供了持久化存储。
-
在这之上是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。
-
再往上,就是文件名,和文件描述符操作。
sector通常是磁盘驱动可以读写的最小单元,它过去通常是512字节。
block通常是操作系统或者文件系统视角的数据。它由文件系统定义,在XV6中它是1024字节。所以XV6中一个block对应两个sector。通常来说一个block对应了一个或者多个sector。
文件系统布局结构
而文件系统的工作就是将所有的数据结构以一种能够在重启之后重新构建文件系统的方式,存放在磁盘上。虽然有不同的方式,但是XV6使用了一种非常简单,但是还挺常见的布局结构。
通常来说:
-
block0要么没有用,要么被用作boot sector来启动操作系统。
-
block1通常被称为super block,它描述了文件系统。它可能包含磁盘上有多少个block共同构成了文件系统这样的信息。我们之后会看到XV6在里面会存更多的信息,你可以通过block1构造出大部分的文件系统信息。
-
在XV6中,log从block2开始,到block32结束。实际上log的大小可能不同,这里在super block中会定义log就是30个block。
-
接下来在block32到block45之间,XV6存储了inode。我之前说过多个inode会打包存在一个block中,一个inode是64字节。
-
之后是bitmap block,这是我们构建文件系统的默认方法,它只占据一个block。它记录了数据block是否空闲。
-
之后就全是数据block了,数据block存储了文件的内容和目录的内容。
假设inode是64字节,如果你想要读取inode10,那么你应该按照下面的公式去对应的block读取inode。
所以inode0在block32,inode17会在block33。只要有inode的编号,我们总是可以找到inode在磁盘上存储的位置。
inode
- 通常来说它有一个type字段,表明inode是文件还是目录。
- nlink字段,也就是link计数器,用来跟踪究竟有多少文件名指向了当前的inode。
- size字段,表明了文件数据有多少个字节。
- 不同文件系统中的表达方式可能不一样,不过在XV6中接下来是一些block的编号,例如编号0,编号1,等等。XV6的inode中总共有12个block编号。这些被称为direct block number。这12个block编号指向了构成文件的前12个block。举个例子,如果文件只有2个字节,那么只会有一个block编号0,它包含的数字是磁盘上文件前2个字节的block的位置。
- 之后还有一个indirect block number,它对应了磁盘上一个block,这个block包含了256个block number,这256个block number包含了文件的数据。所以inode中block number 0到block number 11都是direct block number,而block number 12保存的indirect block number指向了另一个block。
目录(directory)
大部分Unix文件系统有趣的点在于,一个目录本质上是一个文件加上一些文件系统能够理解的结构。在XV6中,这里的结构极其简单。每一个目录包含了directory entries,每一条entry都有固定的格式:
- 前2个字节包含了目录中文件或者子目录的inode编号,
- 接下来的14个字节包含了文件或者子目录名。
对于实现路径名查找,这里的信息就足够了。假设我们要查找路径名“/y/x”,我们该怎么做呢?
从路径名我们知道,应该从root inode开始查找。通常root inode会有固定的inode编号,在XV6中,这个编号是1。我们该如何根据编号找到root inode呢?从前一节我们可以知道,inode从block 32开始,如果是inode1,那么必然在block 32中的64到128字节的位置。所以文件系统可以直接读到root inode的内容。
对于路径名查找程序,接下来就是扫描root inode包含的所有block,以找到“y”。该怎么找到root inode所有对应的block呢?根据前一节的内容就是读取所有的direct block number和indirect block number。
结果可能是找到了,也可能是没有找到。如果找到了,那么目录y也会有一个inode编号,假设是251。
我们可以继续从inode 251查找,先读取inode 251的内容,之后再扫描inode所有对应的block,找到“x”并得到文件x对应的inode编号,最后将其作为路径名查找的结果返回。