跳转至

文件系统概述

导读

文件系统(File System)为操作系统提供了持久存储设备上高效管理信息的能力:其为上层应用抽象出统一的设备访问接口,屏蔽不同底层块设备的操作细节。文件系统强大而复杂,横跨计算机不同存储体系,涵盖了逻辑设计与物理实现的解耦与联系。因此,本章 Wiki 将从概念出发,逐步介绍文件系统的前世今生。

持久存储设备

持久存储设备(Persistent Storage)是指计算机中用于持久化存储数据的设备。

有别于易失性存储设备“内存”,持久存储设备的数据不会因为掉电而丢失

常见的持久存储设备包括磁盘、固态硬盘、光盘、磁带、闪存等。

要想深刻理解文件系统,首先需要了解文件与持久存储。由于计算机内存中的数据在掉电后会丢失,应用程序需要将数据保存在持久存储设备上。为了满足这个需求,计算机操作系统一般会有两种处理方法:

  • 应用程序直接操作持久存储设备,自己维护数据的存储和读写。
  • 应用程序通过操作系统提供的文件系统接口,由操作系统负责维护数据的存储和读写。

对于前者,由之前的学习可以知道:计算机中应用程序若要和设备直接交互,需要操作系统让渡部分设备管理权限,无形中会引入安全隐患(如侧信道攻击等)。此外,不同的设备有不同的操作模式,应用程序需要针对不同的设备编写不同的代码,增加了开发难度的同时,还降低了应用的可移植性。

因此,操作系统将设备的操作细节隐藏起来,为应用程序提供了统一的设备访问接口。这些统一的用户态/内核态访问接口和对应的数据结构组成了文件系统,而文件就是对在持久化存储设备上数据块的抽象。

了解以上基本概念后,本章 Wiki 将继续介绍常见文件系统的层次结构,文件系统的实现概述以及对传统内核存储栈的讨论。

文件系统的层次结构

文件系统的层次结构通常可以自上而下分为以下几个层次,每个层次负责不同的功能和任务:

  1. 物理层:物理层是文件系统最底层的部分,负责处理存储介质(即前文提到的“持久存储设备”)的读写操作。物理层将这些存储介质分割成逻辑块(例如扇区),并提供对这些块的标准化读写访问接口。

  2. 块设备层:块设备层建立在物理层之上,负责管理文件系统的逻辑块的分配和存储。它提供了块级别的访问接口,允许文件系统在这些块上进行读写操作。典型的块设备包括磁盘、硬盘的分区、磁盘卷(RAID)、虚拟存储设备等。

  3. 文件系统层:文件系统层是文件系统的核心部分,负责管理文件和目录的组织、存储以及访问。它提供了一个逻辑视图,使用户和应用程序可以通过文件和目录的名称来访问数据,而不需要关注物理存储的细节。文件系统通常包括文件的创建、删除、修改、权限管理等功能,以及元数据的存储和管理(如文件大小、创建时间、修改时间等)。

  4. 虚拟文件系统层:虚拟文件系统层是文件系统的抽象层,作为一个可选的抽象层,负责统一管理不同类型的文件系统,并提供统一的访问接口给上层应用程序和用户。

    为什么要在存储栈中额外抽象多一层接口?

    虚拟文件系统层(VFS)可以将不同文件系统(如 FAT、NTFS、EXT4 等)抽象成相同的接口,使得用户和应用程序可以统一地访问这些不同类型的文件系统,而不用关心底层文件系统的差异。

  5. 用户空间接口层:用户空间接口层提供了用户和应用程序与文件系统之间的交互接口,使用户能够通过命令行或图形界面来操作文件和目录。这包括常见的文件操作命令(如 lscpmvrm 等)、文件管理器、API 接口等。

以上是文件系统的一般层次结构,不同的文件系统可能会有些许变化,如继续拆分某个层次、合并部分层次,但大致上都会包含类似的组成部分。

文件系统的实现

设计思路

在正式介绍文件系统的实现方法之前,首先来了解一下文件系统的“松耦合”设计哲学。

在操作系统的设计中,松耦合(Loose Coupling)是一种设计思路,它强调模块之间的独立性和解耦性,使得模块之间的依赖关系尽可能的减少。这种设计思路的好处是,当一个模块发生变化时,不会对其他模块产生太大的影响,从而提高了系统的可维护性和可扩展性

在本实验和rCore的设计中,都采用了这样的思路,具体可以体现在:

  • BlockDevice:文件系统与底层设备驱动之间通过抽象层 BlockDevice 来连接,避免了与设备驱动、分区方式的绑定。
  • alloc crate:通过 Rust 提供的 alloc crate 来隔离了语言层面的内存管理,避免了直接调用内存管理的内核函数,同时提供了在 std 环境下进行测试的能力。
  • traitdyn:通过对文件系统的功能、相关数据结构进行抽象封装,从而使得创建虚拟文件系统和支持更多不同的文件系统成为可能。

实现方法

由于种种的历史遗留、技术迭代,留下了很多诸如“扇区”、“分区”等概念。考虑向前的兼容性也是文件系统设计的一个重要因素。

磁盘与分区

磁盘是计算机中最常见的持久存储设备之一,它是由一个或多个盘片组成的,每个盘片上都有一个或多个磁道,每个磁道上都有一个或多个扇区。磁盘上的数据是以扇区为单位进行读写的,每个扇区的大小通常是 512 字节或 4KB。

SSD 也是磁盘吗

在现代计算机中,这些概念也被逐渐模糊,不一定有磁盘才具有扇区,更多考虑到的是访问驱动和兼容性,在 QEMU 模拟的虚拟机中,甚至可以将内存映射为块设备。

在常见的磁盘上,通常会划分出一个或多个分区,每个分区都是一个逻辑上的独立存储空间,它们可以被格式化为不同的文件系统,或者用于存储不同的数据。分区的信息通常需要存储在磁盘中,通过分区表进行管理,GPT(GUID Partition Table)和 MBR(Master Boot Record)是两种常见的分区表格式。

同时,一般还存在引导扇区等概念,将会在具体的实现中进行介绍。

文件存储

文件系统的核心是文件数据的管理,文件系统需要负责文件的创建、删除、修改、读取等操作。文件系统通常会将文件的数据和元信息(如文件大小、创建时间、修改时间等)存储在磁盘上,以便在需要时进行读取和修改。

文件系统的实现方法有很多种,常见的文件系统包括 FAT、NTFS、EXT4 等。不同的文件系统有不同的设计思路和实现方法,但它们都需要解决文件的组织、存储和访问等问题。

FAT 的实现很简单:它将文件系统的元信息和文件数据都存储在磁盘上,通过文件表来管理文件的组织和存储。EXT4 则更加复杂,它采用了日志、索引节点等技术,以提高文件系统的性能和可靠性。

你还可以参考 rCore - 代码导读部分,学习系统代码迭代开发的过程,获得另一视角的实现参考。

讨论

当然,应用程序(用户态)直接与持久存储设备交互的模式也不是绝对的缺点。在传统的设计(如 Linux)中,应用程序的存储栈往往由以下数个部分组成:用户态逻辑-系统调用-虚拟文件系统-文件系统-块 I/O -设备驱动-物理设备(参考:Linux-storage-stack-diagram

兼容取向的松耦合逻辑带来超长的软件调用栈,进而引入无法忽视的性能开销(参考:OSDI '22 Best Paper: XRP)。

因此,目前科研前沿提出了不少绕过传统内核-文件系统体系(Kernel Bypass)的存储解决方案,如 SPDK 等。通过解耦权限控制面与数据传输面,在用户态完成高效数据传输,而不用经过冗长的内核存储栈。

参考资料