MIT 6.S081操作系统 之 文件系统详解

文件系统

通过调用link系统调用,为之前创建的文件“x/y”创建另一个名字“x/z”。我们还可能会在文件打开时,删除或者更新文件的命名空间。例如,用户可以通过unlink系统调用来删除特定的文件名。如果此时相应的文件描述符还是打开的状态,那我们还可以向文件写数据,并且这也能正常工作。所以,在文件系统内部,文件描述符必然与某个对象关联,而这个对象不依赖文件名。这样,即使文件名变化了,文件描述符仍然能够指向或者引用相同的文件对象。即inode。

文件描述符(File descriptor)

路径名(Pathname)

目录(Directory)

索引结点(Inode)

日志(Logging)

缓冲区高速缓存(Buffer cache)

磁盘(Disk)

图8.1 XV6文件系统的层级

文件储存(文件系统角度)

磁盘读写的最小单位是扇区,扇区的大小只有 512B 大小,很明显,如果每次读写都以这么小为单位,那这读写的效率会非常低。

所以,文件系统把多个扇区组成了一个逻辑块,每次读写的最小单位就是逻辑块(数据块),Linux 中的逻辑块大小为 4KB,也就是一次性读写 8 个扇区,这将大大提高了磁盘的读写的效率。

文件系统结构

位图法

位图是利用二进制的一位来表示磁盘中一个盘块的使用情况,磁盘上所有的盘块都有一个二进制位与之对应。

当值为 0 时,表示对应的盘块空闲,值为 1 时,表示对应的盘块已分配。它形式如下:

1111110011111110001110110111111100111 ...

文件系统都由大量块组组成,在硬盘上相继排布:

  • block0引导块要么没有用,要么被用作boot sector来启动操作系统。

  • block1通常被称为super block,包含的是文件系统的重要信息,比如 inode 总个数、块总个数、每个块组的 inode 个数、每个块组的块个数等等。

  • 块组描述符log。实际上log的大小可能不同,这里在super block中会定义log就是30个block。包含文件系统中各个块组的状态,比如块组中空闲块和 inode 的数目等,每个块组都包含了文件系统中「所有块组的组描述符信息」。

  • 数据位图bitmap和 inode 位图, 用于表示对应的数据块或 inode 是空闲的

  • inode 列表。多个inode会打包存在一个block中,一个inode是64字节。

  • 之后就全是数据block了,数据block存储了文件的内容和目录的内容。

假设inode是64字节,如果你想要读取inode10,那么你应该按照下面的公式去对应的block读取inode

32 + (10*64)/4096

Linux 文件系统会为每个文件分配两个数据结构:索引节点(index node)和目录项(directory entry,它们主要用来记录文件的元信息和目录层次结构。

  • 索引节点,也就是 inode,用来记录文件的元信息,比如 inode 编号、文件大小、访问权限、创建时间、修改时间、数据在磁盘的位置等等。索引节点是文件的唯一标识,它们之间一一对应,也同样都会被存储在硬盘中,所以索引节点同样占用磁盘空间

  • 目录项,也就是 directory entry,用来记录文件的名字、索引节点指针以及与其他目录项的层级关联关系。多个目录项关联起来,就会形成目录结构,但它与索引节点不同的是,目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存

目录项和目录是一个东西吗?

虽然名字很相近,但是它们不是一个东西,目录是个文件,持久化存储在磁盘,而目录项是内核一个数据结构,缓存在内存

inode索引方式

磁盘上存储的inode数据结构:

  • 通常来说它有一个type字段,表明inode是文件还是目录。

  • nlink字段,也就是link计数器,用来跟踪究竟有多少文件名指向了当前的inode。

  • size字段,表明了文件数据有多少个字节。

  • 一系列block的编号,例如编号0,编号1,等等。linux的inode中总共有10个block编号。这些被称为direct block number。这12个block编号指向了构成文件的前12个block(一个文件可能储存在多个block中)。此编号就是指向文件第几个数据块的指针。

  • 之后还有若干个indirect block number,用于解决大文件储存问题,指向下一级的索引数据块

多级索引

它是根据文件的大小,存放的方式会有所变化:

  • 如果存放文件所需的数据块小于 10 块,则采用直接查找的方式;

  • 如果存放文件所需的数据块超过 10 块,则采用一级间接索引方式;

  • 如果前面两种方式都不够存放大文件,则采用二级间接索引方式;

  • 如果二级间接索引也不够存放大文件,这采用三级间接索引方式;

那么,文件头(Inode)就需要包含 13 个指针:

  • 10 个指向数据块的指针;

  • 第 11 个指向索引块的指针;

  • 第 12 个指向二级索引块的指针(每个二级索引块包含n个一级索引指针);

  • 第 13 个指向三级索引块的指针;

假设我们需要读取文件的第8000个字节,那么你该读取哪个block的哪个字节呢?

首先8000整除block大小,得到大概是1。这意味着第1个block就包含了第8000个字节。所以直接在inode的direct block number中,就包含了第8000个字节的block。为了找到这个字节在第1个block的哪个位置,用8000对block大小求余数。得到,第8000个字节在第一个block的第3904个字节。所以为了读取文件的第8000个字节,文件系统查看inode,先用8000除以4096得到block number,然后再用8000对4096求余读取block中对应的字节。

假设我们要查找路径名“/y/x”,我们该怎么做呢?

从路径名我们知道,应该从root inode开始查找。通常root inode会有固定的inode编号,假设是0。从前一节我们可以知道,inode从block 32开始,如果是inode1,那么必然在block 32中的0到64字节的位置(一个inode64字节)。所以文件系统可以直接读到root inode的内容。接下来就是扫描root inode包含的所有direct block number和indirect block number,以找到“y”。如果找到了,那么目录y也会有一个inode编号,假设是251,同理,找到“x”并得到文件x对应的inode编号,最后将其作为路径名查找的结果返回。

Crash safety

文件系统崩溃

指在操作文件系统的过程中,由于电力故障或者其他问题,导致操作系统crash,使得文件系统的属性被破坏。这里的属性是指,每一个磁盘block要么是空闲的,要么是只分配给了一个文件。即使故障出现在磁盘操作的过程中,我们期望这个属性仍然能够保持。如果这个属性被破坏了,那么重启系统之后程序可能会运行出错,比如:

  • 操作系统可能又立刻crash了,因为文件系统中的一些数据结构现在可能处于一种文件系统无法处理的状态。

  • 或者,更可能的是操作系统没有crash,但是数据丢失了或者读写了错误的数据。

例子:向文件写内容。文件系统操作流程

  • 首先有两个write 33,第一个是为了标记inode将要被使用。在XV6中,使用inode中的type字段来标识inode是否空闲,这个字段同时也会用来表示inode是一个文件还是一个目录。所以这里将inode的type从空闲改成了文件,并写入磁盘表示这个inode已经被使用了。第二个write 33就是实际的写入inode的内容。inode的内容会包含linkcount为1以及其他内容

  • write 46向根目录增加了一个新的entry,其中包含了文件名x,以及我们刚刚分配的inode编号。

  • 根目录的大小变了,因为我们刚刚添加了16个字节的entry来代表文件x的信息。更新根目录的inode

  • 再次更新了文件x的inode

  • write 45,这是更新bitmap。文件系统首先会扫描bitmap来找到一个还没有使用的data block,未被使用的data block对应bit 0。找到之后,文件系统需要将该bit设置为1,表示对应的data block已经被使用了

  • 接下来的两次write 595表明,文件系统挑选了data block 595。所以在文件x的inode中,第一个direct block number是595。因为写入了两个字符,所以write 595被调用了两次

  • 最后的write 33是更新文件x对应的inode中的size字段,因为现在文件x中有了两个字符。

crash情况:

从上面可以看出,创建一个文件涉及到了多个操作:

  • 首先是分配inode,因为首先写的是block 33

  • 之后inode被初始化,然后又写了一次block 33

  • 之后是写block 46,是将文件x的inode编号写入到x所在目录的inode的data block中

  • 之后是更新root inode,因为文件x创建在根目录,所以需要更新根目录的inode的size字段,以包含这里新创建的文件x

  • 最后再次更新了文件x的inode

在这个位置,我们先写了block 33表明inode已被使用,之后出现了电力故障,然后计算机又重启了。这时,我们丢失了刚刚分配给文件x的inode。这个inode虽然被标记为已被分配,但是它并没有放到任何目录中,所以也就没有出现在任何目录中,因此我们也就没办法删除这个inode。所以在这个位置发生电力故障会导致我们丢失inode。

猜你喜欢

转载自blog.csdn.net/qq_52353238/article/details/129439940