跳转至

File System

文件系统简介

计算机文件系统是操作系统的一个重要组成部分,它管理计算机存储设备上的文件,负责文件存储、读取和组织等功能。文件系统的主要作用包括:

  • 文件存储与寻址:文件系统负责在存储设备上对文件进行存取,需要找到文件在存储设备上的位置。常见的寻址机制有:
  • FAT表:使用文件分配表记录每个文件所占用簇的位置。
  • inode:为每个文件分配一个inode,记录文件存储位置、大小、访问时间等元信息。
  • 文件组织与优化:文件系统负责组织硬盘空间,常见的组织结构包括:
  • 目录结构:将文件组织成目录/子目录的树形层次结构。
  • 碎片整理:通过碎片整理优化空间利用率。
  • 块大小:通过调整块大小来改善IO性能。
  • 访问控制:管理文件访问权限、用户组等信息,确保访问安全。
  • 高级功能:一些文件系统实现了高级功能,如快照、数据压缩、加密等。
  • 系统完整性:提供一致性检验、崩溃恢复机制来保证文件系统完整可靠。

常见的文件系统包括Windows上的FAT、NTFS,Unix/Linux上的ext、XFS、Btrfs等。

文件系统的设计对操作系统的性能、安全性有很大影响。一个优秀的文件系统应提供高效的IO访问、良好的安全控制和数据完整性保障。选择正确的文件系统对不同场景也很重要。

评估文件系统

  • 性能:读写速度、响应时间、并发支持如何,可以测试IO性能。
  • 可靠性:数据完整性保证、Crash可恢复性如何,测试崩溃恢复。
  • 安全性:访问控制、防篡改机制如何,测重写、破坏后的数据恢复。
  • 容量:最大文件大小、卷大小、目录容量如何,测试边界极限。
  • 扩展性:可线性扩展还是需要重构,测试大容量情况下的性能。
  • 元数据:元信息组织结构,是否支持快速查找、高级索引。
  • 分配机制:如何分配和回收空间,是否会产生碎片。
  • 一致性:是否保证读写顺序一致性,如何支持缓存与本地IO。
  • 插件机制:是否可以通过插件扩展功能,如压缩、加密等。
  • 兼容性:是否兼容主流平台和老系统,测试迁移和交互兼容性。

基本概念:数据分块存储

文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。

但是就像内存读取也不会只读一个字节, 硬盘的存储和读取都是按照Block进行的(比如,4KB即连续八个 sector组成一个 block。)

早期:FAT文件系统

早期文件分配表(File Allocate Table,FAT) 链表结构解决了文件和物理块映射的问题。

小结

  • 常见的FAT12、FAT16、FAT32格式,用于早期Windows系统。
  • 优点:简单易用,支持跨平台
  • 缺点:非常占用内存, 效率和安全性不高。
  • 比如 1T 的硬盘,如果块的大小是 1K,那么就需要 1G 个 FAT 条目。
  • 通常每个 FAT 条目还会存一些其他信息,需要 2~3 个字节, FAT条目总共占用 2-3G 的内存空间,才能用来管理 1T 的硬盘空间。

常见:基于inode的文件系统

基于 inode(index node的数据结构) 的传统文件系统。文件数据被存储不同块里面,文件的元数据信息就会被存储在inode里面。

特点

  • 在Unix/Linux中广泛使用的文件系统,如Ext、XFS等。
  • 每个文件都有一个对应的inode,记录文件元信息和数据块位置。
  • 操作系统通过inode找到文件内容,支持权限控制等高级功能。
  • 效率高,安全可靠,但inode会占用一定存储空间。

文件操作流程

由于inode号码与文件名分离

  • 删除流程
  • 删除一个文件名,就会使得inode节点中的"链接数"减1。当这个值减到0,表明没有文件名指向这个inode,系统就会回收这个inode号码,以及其所对应block区域。
  • 直接删除inode,能够起到删除(包含特殊字符)文件的作用find ./* -inum 节点号 -delete
  • 移动文件或重命名文件
  • 只是改变文件名,不影响inode号码;

inode存储Block信息

为了解决数据变化问题,它引入了3个存储指针设计

  • 直接指针可以直接指向数据块本身,数据块就是保存数据的块
  • 间接指针是在前面的指针指针不够的时候才会启用,间接指针可以看成链表那样,间接指针会指向一个个索引块,这块本身又是一个数据块的指针也是只是指向存储数据块。
  • 第3类指针,指向一个二级索引块,二级索引块的指针还可以指向新的索引块

大小占用

  • 每个inode节点的大小固定,一般是128字节或256字节。
  • inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode
  • 每个文件都必须有一个inode,因此有可能发生inode已经用光,但是硬盘还未存满的情况。这时,就无法在硬盘上创建新文件。
  • 每个inode都有一个号码,操作系统用inode号码来识别不同的文件。
$ df -hi .
Filesystem     Inodes IUsed IFree IUse% Mounted on
/dev/sda2         56M  5.1M   51M   10% /
或者
/dev/sda2      58605568 5321132 53284436   10% /

# 每个inode节点的大小,一般是128字节或256字节。
$ sudo dumpe2fs -h /dev/sda2 | grep "Inode size"
dumpe2fs 1.45.5 (07-Jan-2020)
Inode size:               256

58605568/256 = 222928 个 inode节点

$ fdisk -l
Disk /dev/sda: 894.26 GiB, 960197124096 bytes, 1875385008 sectors
Disk model: INTEL SSDSC2KB96
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt
Disk identifier: 86F8050E-C9E7-4BDB-8B0C-89E20B013FF6

Device     Start        End    Sectors   Size Type
/dev/sda1   2048       4095       2048     1M BIOS boot
/dev/sda2   4096 1875382271 1875378176 894.3G Linux filesystem

$ sudo fdisk -l /dev/sda2
Disk /dev/sda2: 894.26 GiB, 960193626112 bytes, 1875378176 sectors (512 bytes per sector)
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

1875378176/228928 = 8192(8K) 个 sector 对应一个inode节点

一个inode节点对应 4MB的空间?

目录、软链接、硬链接

  • 目录:目录是一种特殊的文件,它的inode节点中存储的是文件名和inode号码的对应关系。
  • 软链接:软链接拥有自己的inode,但是文件内容就是一个快捷方式。
  • 命令 ln -s /etc/nginx/config link_config
  • 文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的"软链接"(soft link)或者"符号链接(symbolic link)。
  • 文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode"链接数"不会因此发生变化。
  • 硬链接:多个文件名指向同一个inode号码。
  • 命令 ln main.c link_main.c

软链接、硬链接区别与使用场景

在大部分常见场景下,硬链接是更优的选择:

  1. 硬链接是一个真实文件,不会无效,更可靠。软链接指向的文件移动或删除后会失效。
  2. 硬链接与原文件性能一致,软链接需要在查询时重新解析路径。

硬链接也有一些限制:

  1. 不能跨文件系统,软链接可以实现跨文件系统的链接。
  2. 目录无法创建硬链接,只能软链接。

日志文件系统

  • NTFS 和 Ext3 是日志文件系统,它们和 FAT 最大的区别在于写入到磁盘中的是日志,而不是数据。
  • 日志文件系统会先把日志写入到内存中一个高速缓冲区,定期写入到磁盘。
  • 日志写入是追加式的,不用考虑数据的覆盖。
  • 一段时间内的日志内容,会形成还原点。这种设计大大提高了性能,当然也会有一定的数据冗余。

日志文件系统 与 inode 文件系统的关系

  1. 日志文件系统是一种技术,可以建立在多种文件系统之上,包括inode文件系统。
  2. inode文件系统是一种文件系统架构,每个文件都有一个inode保存元信息。ext、xfs等都是这种架构。
  3. 但两者不是必然关联的,日志文件系统技术也可以用在非inode型文件系统上。

常见名词及文件系统:MBR、GPT和FAT、EXT2

前两者是磁盘分区格式,后两者是文件系统格式。

MBR、GPT是两种比较常见的磁盘分区格式,而且对于磁盘分区而言,目前也主要是这两种格式。一个分区是一个存储设备上的一块独立区域,用户可以针对这块区域进行单独管理。

  • NFS:网络文件系统,允许通过网络访问文件,可应用在分布式系统。
  • ZFS:引入了pooled storage和checksum等特性的128位文件系统。
  • HFS:Mac OS使用的层次化文件系统,使用B*树对元数据进行组织。

实践:fdisk的结果

  • “Disk label type”表示当前磁盘的分区形式,
  • dos表示磁盘分区形式为MBR,
  • gpt表示磁盘分区形式为GPT

Windows NTFS文件系统

New Technology File System (NTFS):Windows NT引入的文件系统,使用主文件表(MFT)来管理文件,支持高级功能如权限控制等。

  • NTFS卷上的任何事物都是文件(为了与平时使用的文件相区别,以下用FILE特指),
  • FILE通过主文件表(master file table, MFT)来确定其在卷上的位置,
  • 每个FILE有固定大小,一般为1KB。
  • FILE记录了文件的所有数据,每个数据以一个属性来表示,如文件名、文件长度、文件的时间等都是属性,文件的内容也是一个属性,每个属性有一个特征码。
  • 属性数据较小时能够存放在FILE记录中,称为驻留的属性,反之为非驻留的属性,通过Data Runs来保存其存储索引表。
  • 这一点与FAT文件系统不同,FAT文件系统只在目录区保存了文件的首簇号,还要通过FAT表链接关系才能确定文件的全部存放位置。
  • Data Runs在一个FILE记录存放不下时还可以用扩展属性,增加FILE记录来保存,即一个文件可以有多个FILE记录。

NTFS的同层目录采用B+树结构,按文件(夹)名保持有序,通过文件号指向文件夹内的文件。文件夹的目录项较少时可以直接存储在文件夹的FILE记录中,目录项较多时占用数据簇,建立INDX记录,存放各目录项的属性。

NTFS文件系统一共由16个“元文件”构成

Linux常见 EXT文件系统的发展简介

ext1

  • 优点:1992 年的 ext 使用在 Linux 内核中的新虚拟文件系统(VFS)抽象层。
  • 与之前的 MINIX 文件系统不同的是,ext 可以处理高达 2 GB 存储空间并处理 255 个字符的文件名。
  • 缺点:原始的时间戳(每个文件仅有一个时间戳,而不是今天我们所熟悉的有 inode、最近文件访问时间和最新文件修改时间的时间戳。)

ext2

  • 优点:提供了 GB 级别的最大文件大小和 TB 级别的文件系统大小。
  • 缺点:
  • 将数据写入到磁盘的时候,系统发生崩溃或断电,则容易发生灾难性的数据损坏。
  • 随着时间的推移,由于碎片(单个文件存储在多个位置,物理上其分散在旋转的磁盘上),它们也遭受了严重的性能损失。

ext3

  • 2001 年 11 月在 2.4.15 内核版本中被采用到 Linux 内核主线中。
  • 优点:使用日志来解决断电数据不一致问题,和 20 世纪 90 年代后期的其它文件系统一样,如微软的 NTFS。

ext4

  • 2008年在 2.6.28 内核版本中被加入到了 Linux 主线。
  • 优点:
  • 支持大文件系统,
    • ext3 文件系统使用 32 位寻址,这限制它仅支持 2 TiB 文件大小和 16 TiB 文件系统系统大小
    • ext4 使用 48 位的内部寻址,理论上可以在文件系统上分配高达 16 TiB 大小的文件,其中文件系统大小最高可达 1000000 TiB(1 EiB)
  • 分配方式改进,显著提高读写性能
    • 区段(extent)
    • 是一系列连续的物理块 (最多达 128 MiB,假设块大小为 4 KiB),可以一次性保留和寻址。使用区段而不是 block可以减少给定文件所需的 inode 数量,并显著减少碎片并提高写入大文件时的性能。
    • 多块分配(multiple block allocation)
    • ext3 为每一个新分配的块调用一次块分配器。当多个写入同时打开分配器时,很容易导致严重的碎片。
    • 多块分配(multiple block allocation)允许一次性分配大量的连续文件块,以降低碎片并且有利于 RAID 设备的并行写入
    • 延迟分配(delayed block allocation)
    • ext4 使用延迟分配(delayed block allocation),这允许它合并写入并更好地决定如何为尚未提交的写入分配块。
    • 延迟分配允许 ext4 等待分配将写入数据的实际块,直到它准备好将数据提交到磁盘。(相比之下,即使数据仍然在往写入缓存中写入,ext3 也会立即分配块。)
    • 当缓存中的数据累积时,延迟分配块允许文件系统对如何分配块做出更好的选择,降低碎片(写入,以及稍后的读)并显著提升性能。
    • 持久的预分配( allocation without initialization)
    • 在为文件预分配磁盘空间时,大部分文件系统必须在创建时将零写入该文件的块中。
    • ext4 允许替代使用 fallocate(),它保证了空间的可用性(并试图为它找到连续的空间),而不需要先写入它。这显著提高了写入和将来读取流和数据库应用程序的写入数据的性能。
  • 提高了对碎片的抵抗力
    • ext2 和 ext3 都不直接支持在线碎片整理 —— 即在挂载时会对文件系统进行碎片整理。
    • ext4 通过 e4defrag 解决了这个问题,且是一个在线、内核模式、文件系统感知、块和区段级别的碎片整理实用程序。

Nas 防止碎片,开启预分配

实践问题: Nas 的 ext4 挂载被识别成NTFS

估计是有一层转换,类似的软件有 UFS Explorer

磁盘读写原理

读写操作分层

对于磁盘的一次读请求,

  • 首先经过虚拟文件系统层(VFS Layer),
  • 其次是具体的文件系统层(例如Ext2),
  • 接下来是Cache层(Page Cache Layer)、
  • 通用块层(Generic Block Layer)、
  • I/O调度层(I/O Scheduler Layer)、
  • 块设备驱动层(Block Device Driver Layer),
  • 最后是物理块设备层(Block Device Layer)。

Page Cache层

  • 为了提高Linux操作系统对磁盘访问的性能。Cache层在内存中缓存了磁盘上的部分数据。当数据的请求到达时,如果在Cache中存在该数据且是最新的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。
  • 文件Cache分为两个层面,
  • 一是Page Cache,另一个Buffer Cache,每一个Page Cache包含若干Buffer Cache。
  • Page Cache主要用来作为文件系统上的文件数据的缓存来用,尤其是针对当进程对文件有read/write操作的时候。
  • Buffer Cache则主要是设计用来在系统对块设备进行读写的时候,对块进行数据缓存的系统来使用。
  • 磁盘Cache有两大功能:预读和回写。
  • 预读其实就是利用了局部性原理,具体过程是:
    • 对于每个文件的第一个读请求,系统读入所请求的页面并读入紧随其后的少数几个页面(通常是三个页面),这时的预读称为同步预读。
    • 对于第二次读请求,
    • 如果所读页面不在Cache中,即不在前次预读的页中,则表明文件访问不是顺序访问,系统继续采用同步预读;
    • 如果所读页面在Cache中,则表明前次预读命中,操作系统把预读页的大小扩大一倍,此时预读过程是异步的,应用程序可以不等预读完成即可返回,只要后台慢慢读页面即可,这时的预读称为异步预读。
    • 任何接下来的读请求都会处于两种情况之一:第一种情况是所请求的页面处于预读的页面中,这时继续进行异步预读;第二种情况是所请求的页面处于预读页面之外,这时系统就要进行同步预读。
  • 回写是通过暂时将数据存在Cache里,然后统一异步写到磁盘中。
    • 通过这种异步的数据I/O模式解决了程序中的计算速度和数据存储速度不匹配的鸿沟,减少了访问底层存储介质的次数,使存储系统的性能大大提高。Linux
    • 2.6.32内核之前,采用pdflush机制来将脏页真正写到磁盘中,什么时候开始回写呢?下面两种情况下,脏页会被写回到磁盘:
    • 在空闲内存低于一个特定的阈值时,内核必须将脏页写回磁盘,以便释放内存。
    • 当脏页在内存中驻留超过一定的阈值时,内核必须将超时的脏页写会磁盘,以确保脏页不会无限期地驻留在内存中。

I/O调度层

  • I/O调度层的功能是管理块设备的请求队列。
  • 即接收通用块层发出的I/O请求,缓存请求并试图合并相邻的请求。
  • 并根据设置好的调度算法,回调驱动层提供的请求处理函数,以处理具体的I/O请求。
  • 如果简单地以内核产生请求的次序直接将请求发给块设备的话,那么块设备性能肯定让人难以接受,因为磁盘寻址是整个计算机中最慢的操作之一。
  • 为了优化寻址操作,内核不会一旦接收到I/O请求后,就按照请求的次序发起块I/O请求。
  • 为此Linux实现了几种I/O调度算法,算法基本思想就是通过合并和排序I/O请求队列中的请求,以此大大降低所需的磁盘寻道时间,从而提高整体I/O性能。

常见的I/O调度算法包括

  1. Noop调度算法(No Operation)、
  2. CFQ(完全公正排队I/O调度算法)、
  3. DeadLine(截止时间调度算法)、
  4. AS预测调度算法等。

磁盘快速I/O常见机制

Linux系统中请求到达磁盘的一次完整过程,期间Linux一般会通过Cache以及排序合并I/O请求来提高系统的性能。

其本质就是由于磁盘随机读写慢、顺序读写快的磁盘I/O特性。

采用追加写

在进行系统设计时,良好的读性能和写性能往往不可兼得。在许多常见的开源系统中都是优先在保证写性能的前提下来优化读性能。那么如何设计能让一个系统拥有良好的写性能呢?

  • 一个好的办法就是采用追加写,每次将数据添加到文件。
  • 由于完全是顺序的,所以可以具有非常好的写操作性能。
  • 但是这种方式也存在一些缺点:从文件中读一些数据时将会需要更多的时间:
  • 需要倒序扫描,直到找到所需要的内容。
  • 当然在一些简单的场景下也能够保证读操作的性能:
    • 数据是被整体访问,比如HDFS
    • 知道文件明确的偏移量,比如Kafka
  • 在面对更复杂的读场景(比如按key)时,如何来保证读操作的性能呢?
    • 简单的方式是像Kafka那样,将文件数据有序保存,使用二分查找来优化效率;
    • 或者通过建索引的方式来进行优化;
    • 也可以采用hash的方式将数据分割为不同的桶。
  • 以上的方法都能增加读操作的性能,但是由于在数据上强加了数据结构,又会降低写操作的性能。
    • 比如如果采用索引的方式来优化读操作,那么在更新索引时就需要更新B-tree中的特定部分,这时候的写操作就是随机写。
  • 那么有没有一种办法在保证写性能不损失的同时也提供较好的读性能呢?
  • 一个好的选择就是使用LSM-tree。
    • LSM-tree与B-tree相比,LSM-tree牺牲了部分读操作,以此大幅提高写性能。

文件合并和元数据优化

目前的大多数文件系统,如XFS/Ext4、GFS、HDFS,在元数据管理、缓存管理等实现策略上都侧重大文件。

磁盘碎片

不同文件系统的比较

像 FAT 和 FAT32 这类文件系统中,文件紧挨着写入到磁盘中。文件之间没有空间来用于增长或者更新:

NTFS 中在文件之间保留了一些空间,因此有空间进行增长。但因块之间的空间是有限的,碎片也会随着时间出现。

Linux 的日志型文件系统采用了一个不同的方案。与文件相互挨着不同,每个文件分布在磁盘的各处,每个文件之间留下了大量的剩余空间。这就给文件更新和增长留下了很大的空间,碎片很少会发生。

此外,碎片一旦出现了,大多数 Linux 文件系统会尝试将文件和块重新连续起来。

检测命令

在已经挂载的分区中运行 fsck 将会严重危害到你的数据和磁盘。

umount /path
fsck -fn /path

大于20%需要整理。批评fsck不准的文章:大文件碎片少才是最重要的

NEC 的 Akira Fujita 和 Takashi Sato 在十年前就为 ext4 写了在线整理碎片的工具,因此我们直接拿来用就好了。

e4defrag -v /path

碎片整理

方法一:整个磁盘文件备份,格式化,重新搬运。(Liunx 会自动将文件进行连续分布排列。)

方法二:

# 不要中断,很危险
sudo e4defrag /

常见实践问题

为什么出现坏道之后,硬盘会飞速损耗

坏道分为逻辑坏道,和物理坏道。如果是物理坏道,由于异物或者碰撞导致磁头,盘面受损,会导致物理损坏扩散,应该今早备份数据。

并行下载多个两个文件,存储是交叉的吗?读数据会变慢吗?

场景问题:

1.同时向机械硬盘拷贝多个文件夹,每个文件夹里都有多个小文件。 2.同时解压多个压缩包,每个压缩包有大量的小文件。 3.同时安装多个游戏安装包。 会不会导致交叉存储?会不会导致碎片增加?会不会导致游玩游戏时读取速度变慢?

一般不用担心,有些文件系统会做碎片整理。然后操作系统的缓存写和预读策略也会优化。

硬盘的寿命

对于机械硬盘来讲,反复读写、多线程读写等情况对磁盘使用寿命的影响很低, 但“高温”、“低温”、“尘土”、“外力冲击(跌落)”等情况,会对磁盘的寿命造成较大的影响。

需要进一步的研究学习

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://learn.lianglianglee.com/%E4%B8%93%E6%A0%8F/%E9%87%8D%E5%AD%A6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%AE%8C/30%20%20%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0%EF%BC%9AFAT%E3%80%81NTFS%20%E5%92%8C%20Ext3%20%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB%EF%BC%9F.md

https://www.ruanyifeng.com/blog/2011/12/inode.html

https://tech.meituan.com/2017/05/19/about-desk-io.html

https://zhuanlan.zhihu.com/p/44267768

https://developer.aliyun.com/article/197465