文件系统的实现

  • 作者: 雪山肥鱼
  • 时间:20210628 06:59
  • 目的:初探文件系统(简单版)的实现
# 思考方式
# 文件结构总览
# Inode
# Directory
# 空闲空间管理
# 文件的访问
  ## 读取流程
  ## 写入流程
# cache and buffer
  ## 绕过cache 和 buffer的方式

文件系统是纯软件的,并不像CPU和内存有硬件属性。所以文件系统的工作机制不必关心硬件特性。

  • 如何实现一个简单的文件系统
  • 需要什么样的磁盘结构
  • 文件系统需要跟踪什么
  • 如何被访问的

思考的方式

  • 数据结构
    用什么样子的数据结构去保存 文件的 metadata 和 文件的内容
  • 访问的方式
    open、read、write 函数是如何访问数据的。这些步骤执行的效率如何?

以上的思考要变成一种 mental model.

结构总览

blocks.png

假设我们这个简单的文件系统有 64个 blocks,每个 block 4kb:


数据.png

8 -> 63 blocks 存储数据 即 data region.


inode table.png

需要用inode去追踪每一个文件。假设每个inode 占256个字节,那么每个block 可以存储16个 inode,即16个文件的信息。那么,依照上图可知,5个 blocks 可以管办理16个 inode。
这个5个blocks 成为 inodetable

那么引出问题,如何查看哪些inode可用,哪些data可用呢?
可以用 free-list 管理,当然最流行的是 bitmap 管理。

bitmap.png

  • data bimap
  • inode bitmap
    位表 ,bit 1 则已经占用,bit 0 则未占用。


    超块引入.png

    超块内容:包含了文件系统的信息,如:

  • inode 信息, 可以管理多少个文件
  • data block 信息,有多少个data block
  • inode table 和 data table 从哪里开始
  • 文件系统的魔法数
    当挂在文件系统的适合,OS 会首先读 超块去初始化不同的参数。然后将该卷挂在文件系统树上。当卷中的文件被访问时,系统就会知道在哪里查找所需磁盘上的结构。

Inode

文件系统最终要的磁盘结构之一就是 inode, 即 index node. 从名字可以看出来,所有的node都放在一个数组中,取index,即可拿到node。
Inode 是一个 文件的low-level name,当给OS一个inode,OS 就能直接算出来 inode在disk的相应位置。
举例以下结构:


vsfs.png
  1. inodes block: 20KB (12KB -> 32Kb)-> per inode 256bytes -> 80个inode -> 管理80个文件。
  2. Super block :0-4kb 1个block
  3. i bmap : 4 kb -> 8kb 1个 block
  4. d bmap: 8kb -> 12kb 1 个block
    我们只要知道一点,如果在这个vsfs 中,我给出inode 32, 那么我就知道这个inode 处于哪个 扇区。从而找到inode 信息就完事儿了。

为了支持更多更大的文件,inode 结构体中会有一个 indirect pointer 的特殊指针。指向包含更多指针的块。(可以理解为一个4kb 的block 全是指针,指向数据。)

目录组织

目录自己在底层的标识 是 {条目名称, inode号}


目录内容.png

上图是 dir 在磁盘的数据,即在 data blocks 中的数据,每个条目的inode, 长度,字符串长度等。


direct.png

万物皆文件,只不过文件系统将目录视为特殊的文件,并非 regular files 啦,而是directory 类型
寻找目录流程:
  1. 目录必有indoe, 位于inode表中某处
  2. inode表中 inode, 被标记为directroy 类型。
  3. 数据区的数据块存储 目录中的结构。

空闲空间管理

在我们上述讨论的vsfs 中,用过两个简单的位图来管理空闲空间。

  1. 创建文件,分配inode
  2. 所搜空闲inode位图,分配给该文件。
  3. 在 inode 位图中打上标记,1
  4. 更新data bimap,分配数据块

文件的访问: 读取和写入

这一小节让你彻底明白为什么读写文件时相当耗时的。

从磁盘中读取文件

操作,打开/foo/bar,读取它,然后关闭它。


文件的读入.png
  1. open("/foo/bar", O_RDONLY); 系统调用,首先要找到inode,那就必须从跟路径开始找
    1.1 root inode 是已知的,找inode后 取 root 的 data block里读取数据。
    1.2 查到foo的inode
  2. 读foo 的 inode
    2.1读foo目录中的 data block 数据,找到 bar的inode
    3.读bar 的 inode。
  3. 因为在step3中已经拿到了bar的inode,通过数据块指针找到对应的block 继续read.
  4. 因为文件占了 3个 数据块,所以read 3次。

读取不会访问分配结构
整个过程不会用到位图哟,位图的使用只会在分配的适合才会查询。

写文件流程

写文件流程会相对复杂一些,因为涉及目录的update.
创建新文件并写入内容:


write to disks.png

写文件会涉及块的分配,不仅要写数据,还要决定哪个块分配给文件。
总体上会涉及5次I/O

  • 读data map 哪些bit 可以写数据
  • 写data map
  • read inode 找到哪个inode可以用
  • 写入 inode(新块的位置更新)
  • 写数据本身

流程总结如下:

  1. 通过读写 找到 /root/foo
  2. 读inode bitmap , 找到空闲inode
  3. 写 inode bitmap 表示已经被bar文件使用
  4. 写/root/foo 在 data block 中的 bar inode信息,
  5. 读bar 的inode,
  6. 写bar 的 inode(更新 自己的 block信息等)
  7. 因为创建了 bar, 所以要 update foo 的inode.
  8. 这时候才真正的写文件,写之前要查data 的 bitmap, 进行三次读写。

由此可以看出,即便是最简单的操作,一会产生大量I/O操作,分散在磁盘上,文件系统可以做些什么来降低执行I/O 的高成本呢?

cache and buffer

由上述可知,I/O 是昂贵的。会引发很多磁盘I/O,是一个很严重的性能问题。大多数文件系统会使用 DRAM来缓存重要的block。
可以对目录层次进行缓存。下次就不用遍历查找啦。也只会在第一次打开文件会产生I/O,随后打开同一个文件会命中缓存。

缓存并不会减少写入的流量。可以将内容先写入缓冲buffer, 通过延迟写入,文件可以将buffer内容放入一组较小的I/O中。由OS决定何时写入。
如果想要直接写入文件呢?方法可以有以下几种

  1. fsync()
  2. 绕过缓存,通过 direct I/O 接口直接写, 避开 缓冲
  3. 使用 原始磁盘 raw disk 接口。 完全避免使用文件系统。

你可能感兴趣的:(文件系统的实现)