Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

5.1 文件系统的本质

文件系统是操作系统对存储设备上的数据进行组织和管理的一种机制,它抽象了底层存储设备的细节,让我们可以用“文件“和“目录“的直观方式来管理数据,而不需要关心数据在磁盘上的具体存储位置和方式。

文件系统的核心功能

文件系统主要提供以下核心功能:

  1. 数据的持久化存储:将数据存储在磁盘等非易失性存储设备上,断电后数据不会丢失
  2. 抽象与命名:用“文件“和“目录“的抽象概念来组织数据,用户可以通过文件名来访问数据,不需要关心底层的存储细节
  3. 访问控制:提供权限机制,控制不同用户对文件的访问权限
  4. 可靠性与容错:保证数据的完整性,出现故障时能够恢复数据
  5. 性能优化:通过缓存、预读、块分配等策略提升文件访问性能

文件系统的核心概念

1. 文件(File)

文件是对数据的抽象,是一组相关数据的集合,每个文件有一个文件名,用户通过文件名来访问文件。

  • 文件的内容可以是任意的数据:文本、程序、图片、视频等等
  • 文件通常有一些属性:文件名、大小、创建时间、修改时间、访问权限、所有者等
  • 从操作系统的角度看,一切皆文件:不仅普通的数据是文件,目录、设备、管道、Socket等都被抽象为文件,用统一的接口来访问

2. 目录(Directory)

目录也叫文件夹,是用来管理和组织文件的容器,目录中存储了文件名和对应文件的引用。

  • 目录是一种特殊的文件,它的内容是目录项的列表
  • 目录可以嵌套,形成树形的目录结构
  • 每个文件系统有一个根目录,是整个目录树的起点

3. inode(索引节点)

inode是类Unix文件系统中的核心概念,每个文件对应一个唯一的inode,存储了文件的元数据信息:

  • 文件的权限(读、写、执行)
  • 文件的所有者和所属组
  • 文件的大小
  • 文件的时间戳(创建时间、修改时间、访问时间)
  • 文件数据块的存储位置指针
  • 链接计数

注意:inode中不存储文件名,文件名是存储在目录中的,目录项将文件名和inode号关联起来。这也是为什么硬链接可以有多个文件名指向同一个文件的原因。

示例(Linux查看inode号)

$ ls -i test.txt
131463 test.txt  # 131463就是inode号

4. 数据块

文件的实际内容存储在数据块中,数据块是文件系统分配磁盘空间的最小单位,通常是4KB大小。

  • 一个大文件会占用多个数据块
  • inode中存储了指向这些数据块的指针
  • 为了支持大文件,通常会有直接指针、间接指针、二级间接指针、三级间接指针的设计

虚拟文件系统(VFS)

现代操作系统都支持多种不同的文件系统,为了给上层应用提供统一的接口,操作系统引入了虚拟文件系统(Virtual File System,VFS)的抽象层。

VFS的作用

  • 提供统一的文件操作接口,上层应用不需要关心底层是什么文件系统,用同样的系统调用就可以操作不同的文件系统
  • 可以透明地支持多种不同的文件系统,用户可以同时挂载EXT4、NTFS、FAT32等不同的文件系统,使用方式完全一样
  • 抽象了文件系统的通用操作接口,新的文件系统只需要实现VFS定义的接口,就可以被操作系统支持

VFS的通用对象模型

VFS定义了四个核心对象:

  1. 超级块(Superblock):代表整个文件系统,存储文件系统的元信息(文件系统类型、块大小、inode数量等)
  2. inode对象:代表一个文件,存储文件的元数据
  3. 目录项对象(dentry):代表一个目录项,用来建立文件名和inode的映射
  4. 文件对象:代表一个进程打开的文件,存储文件和进程交互的相关信息

有了VFS这层抽象,上层应用调用open()、read()、write()等系统调用时,会先调用VFS的通用接口,VFS再根据文件所在的实际文件系统,调用对应文件系统的实现函数。

文件系统的通用架构

一个典型的文件系统从上到下分为几个层次:

┌─────────────────────────┐
│   系统调用接口层         │  → 提供open/read/write等系统调用
├─────────────────────────┤
│   VFS虚拟文件系统层      │  → 统一接口,适配不同的文件系统
├─────────────────────────┤
│   具体文件系统实现层      │  → EXT4/NTFS/FAT32等具体文件系统的实现
├─────────────────────────┤
│   页缓存层               │  → 缓存文件数据,提升性能
├─────────────────────────┤
│   块设备驱动层            │  → 与磁盘等存储设备交互
└─────────────────────────┘

文件系统的设计权衡

文件系统的设计需要在多个因素之间进行权衡:

  1. 性能 vs 可靠性:日志文件系统会记录操作日志,提升可靠性,但会带来一定的性能开销
  2. 空间利用率 vs 性能:小块可以提升空间利用率,但会增加元数据开销,降低性能
  3. 可扩展性 vs 复杂度:支持更大的容量和更多的文件会增加文件系统的复杂度
  4. 特性丰富度 vs 兼容性:更多的特性会带来更好的功能,但可能和其他系统不兼容

不同的文件系统会根据目标场景进行不同的权衡,比如面向桌面的文件系统会更注重性能和易用性,面向服务器的文件系统会更注重可靠性和扩展性,面向嵌入式的文件系统会更注重资源占用和响应速度。

思考问题

  1. 为什么我们可以用同样的方式操作硬盘上的文件、U盘上的文件和网络共享文件?这背后是什么在起作用?
  2. inode中为什么不存储文件名?文件名存在哪里?这样的设计有什么好处?
  3. 虚拟文件系统VFS的作用是什么?如果没有VFS会怎么样?
  4. 同一个文件可以有多个文件名吗?为什么?