Linux 的文件系统

Linux 通过 VFS 建立了一个抽象层,并在之上提供了通用的文件系统模型,使得 Linux 能够支持多种文件系统。

文件系统的原理

文件系统的管理方式,可以分为 bitmap 和 tree 两大类。

在支持大容量文件系统时,bitmap 存在问题:

  1. bitmap 本身占用空间太大,占用内存空间,并且回写磁盘更慢,对于掉电故障更不友好。
  2. 如果已分配空间较为零散,那么后续新的分配需要遍历很多的位。并且产生较多的随机写。

Linux VFS

索引节点,即 index node, inode,存储了文件的元信息。Unix中,文件和目录其实都是文件。
装载点即 mount pointer,Unix 把每个文件系统 mount 到一个特定的 mount 点上,称为命名空间 namespace。

VFS 为了封装这些抽象使用了四个主要的对象类型:

  1. 超级块对象,代表一个具体的已安装文件系统
  2. 索引节点对象,代表一个具体文件
  3. 目录项对象,代表一个目录项
    注意目录项不等同于目录,Linux 中目录属于文件。
    这里的目录项描述了一个路径的性质,例如说在基于路径名的查找时需要访问整个路径,所以将路径名从文件中抽象出来简化了过程。
  4. 文件对象,代表一个由进程打开的文件

write() 调用为例,它首先经过了 VFS 层中的 sys_write() 函数,接着调用具体文件系统的实现,以写入物理介质。

超级块

Linux 中使用了 struct super_block 来描述超级块,使用 struct super_block.super_operations 来定义这个超级块中的所有行为。

inode

struct inode 结构体在文件被访问时在内存中创建,它可以描述一个管道、块设备或者字符设备。

1
2
3
4
5
6
7
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
char *i_link;
unsigned i_dir_seq;
};

目录项

目录项有三种状态

  1. 被使用。这个状态的目录的d_inode是一个有效的节点,并且d_count为正,表示正在被VFS访问。
  2. 未被使用。这个状态的目录的d_count为0,表示这个对象没有被使用,但是仍然在中缓存以便将来使用。
  3. 负状态。此时d_inodeNULL,表示inode已经删除,或者路径已经不再合法,缓存这样的节点可以方便查询,即使是对一个不合法的节点。

和进程相关的结构

文件描述符 fd 在进程中以 struct file_struct 定义。

Page Cache

O_DIRECT 表示 IO 不使用 Buffer,
O_SYNC:以同步IO方式打开文件。

ZFS

ZFS 支持下面的内容:

  1. COW
  2. Snapshot
  3. 数据完整性验证和自动修复
  4. RAID-Z
  5. 最大 16 EiB 的文件大小
  6. 最大 256 ZiB 的存储

IO 调度

Linus 电梯

  1. 合并相邻扇区的请求
  2. 出现驻留时间过长的请求时插入尾部
  3. 尝试插入扫描序列中的对应顺序位置
  4. 如果不存在插入尾部

最终期限调度

对某个磁盘扇区上的繁重操作会导致其他扇区的饥饿。
写-饥饿-读问题。这是因为写操作常常是异步的,在内核有空时提交,而读操作则用户进程必须阻塞等待,所以是同步的。而读写操作都涉及到访问inode,因此这里存在竞争关系,能够料想写操作的饥饿能够导致读请求的饥饿。为了解决这个问题我们引入了最后期限调度,但是如我们所想的那样,这种控制延迟在一定程度内的算法一定会降低吞吐量。

磁盘相关性能测试工具介绍

/proc/diskstats

诸如 iostat,node_exporter 之类的都是基于读取该指标实现的。

  1. Field 1 – # of reads completed
    This is the total number of reads completed successfully.
  2. Field 2 – # of reads merged, field 6 – # of writes merged
    相邻的 read 和 write 会被 merge 起来读写。比如两个相邻的 4k 会被合并成一个 8k 的,然后被算作一次 io。通过这个可以了解 merge 的频率。

iostat

  1. rareq-sz/wareq-sz/dareq-sz
    read/write/discard 请求的平均大小,kb 计算。
  2. r_await/w_await/d_await
    read/write/discard 的处理耗时,包含队列中的等待时间,以及真正的处理时间。毫秒计算。
  3. f_await
    The average time (in milliseconds) for flush requests issued to the device to be served. The block layer combines flush requests and executes at most one at a time. Thus flush operations could be twice as long: Wait for current flush request, then execute it, then wait for the next one.
  4. aqu-sz
    队列的平均长度。
  5. %rrqm/%wrqm/%drqm
    The percentage of read/write/discard requests merged together before being sent to the device.
  6. rrqm/s wrqm/s drqm/s
    The number of read/write/discard requests merged per second that were queued to the device.
  7. r/s w/s d/s f/s
    The number (after merges) of read/write/discard/flush requests completed per second for the device.
  8. rkB/s wkB/s
    等于 rareq-sz * r/swareq-sz * w/s

Reference

  1. https://developer.aliyun.com/article/401890
    ZFS
  2. https://www.cnblogs.com/zl1991/p/10288291.html
    O_SYNC 和 O_DIRECT
  3. https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
  4. https://manpages.debian.org/testing/sysstat/iostat.1.en.html
  5. https://www.kernel.org/doc/Documentation/iostats.txt