聊聊文件系统那些事

2,274 阅读7分钟

原文链接:pjmike的博客

前言

本文探究的主题是文件系统,学过操作系统的同学应该都有了解。个人觉得文件系统是操作系统比较重要的一部分内容,作为后端开发人员,肯定会有与文件打交道的时候,通过相关文件I/O函数读写文件,而学习文件系统可以让我们编写代码时做到心中有数,明白背后的逻辑

磁盘

磁盘是一种块设备,用于存储,这种块设备是在系统中能够随机(不需要按顺序)访问固定大小数据片的硬件设备

disk

图片来源:time.geekbang.org/column/arti…

磁盘就是上图左边的样子,中间圆的部分是磁盘的盘片,右边的图是抽象出来的图。每一层里分多个磁道,每个磁道分多个扇区,每个扇区是 512 个字节。

磁盘分区

对于大型硬盘,可以将可用空间划分或分区为多个不同的分区,比如在windows系统上,将一块磁盘分区成C、D、E盘,这就叫做分区。

分区信息存储在磁盘上的分区表上,该表列出了每个分区的起点和终点信息、它的类型信息等,这些信息都存储在分区表中,而磁盘分区表主要有两种:MBR和GPT。前者是传统格式,兼容性好;后者更现代,功能更强大。

文件系统

文件系统实际上就是文件的存储方式,它存放在磁盘上的,磁盘被分成多个分区之后,每个分区都可以有一个独立的文件系统。而为硬盘安装文件系统的操作就被称为"格式化",格式化就是将一块盘使用命令组织成一定格式的文件系统的过程,在Windows下,我们常格式化的格式是NTFS,而在Linux下面,常用的是ext2、ext3ext4

对硬盘完成分区和格式化以后,还需要将分区挂载 (mount) 到操作系统的某个目录下面,这样才能作为普通文件系统进行访问。被挂载的目录可以是根目录,也可以是其他二级、三级目录,任何目录都可以是挂载点,但是根目录是所有Linux的文件和目录所在的地方,需要挂载上一个磁盘分区

文件系统与块

操作系统在读取磁盘时,不会一个个扇区地读取,因为这样的效率太低了,而是一次性连续读取多个扇区,也就是一次性读取一个"块"。块的大小是扇区大小的2的整数倍,通常是4KB.

文件系统创建在磁盘之上,它将磁盘空间以为单位进行划分,也就是说,块是文件系统的最小寻址的单元,有时会被称作"文件块"或者"I/O"块。

sector_to_disk

文件系统与文件

在文件系统中,一个文件大体上可以由目录项inode数据块组成:

  • inode: 索引节点,存放数据块的指针
  • 目录项:包含文件名和inode节点号
  • 数据块:包含具体的文件内容

还有一个关注点是超级块,它会记录整个文件系统的整体信息,包括 inode 与 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等。

inode

inode,全称是index node,中文含义就是索引节点,它用来记录文件的元数据,比如 inode 编号、文件大小、访问权限、修改日期以及文件数据块的地址索引等,通过数据块的索引就可以找到具体的数据块。

inode

在ext3文件系统中inode有15个索引,其中前12个索引直接记录数据块的地址,第13个索引记录索引地址。也就是说,索引块指向的硬盘数据块并不直接记录文件数据,而是记录文件数据块的索引表,依次的,第 14 个索引记录二级索引地址,第 15 个索引记录三级索引地址,下面是示意图:

inode_ext3

图片来源:time.geekbang.org/column/arti…

还有一点需要注意,索引节点和文件一一对应,它跟文件内容一样,需要持久化到磁盘,是占磁盘空间的。

目录项

在Linux系统中,目录(directory)也是一种文件,打开目录,也就是打开目录文件,目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。下面是示意图

inode_and_directory_entry

图片来源:www.pc-freak.net/blog/find-f…

不同于索引节点,目录项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存

虚拟文件系统(VFS)

一个磁盘可以被划分为多个分区,每个分区可以有不同的文件系统,也就是说,在操作系统中,可能会面临对不同文件系统的读写问题,那么如何去管理这些文件系统呢?

在Linux系统中,提供了一种虚拟文件系统(VFS),它是一种内核子系统,为用户空间程序提供了文件和具体文件系统相关的接口。换句话说,向上,对应用层提供一个标准的文件操作接口;对下,对具体的文件系统提供一个标准的接口。

为了支持多种文件系统,抽象一层,以衔接各种各样的文件系统。而这抽象层(也就是VFS),直接屏蔽掉具体的文件系统,从而使用户可以直接使用open()、read()和write()这样的函数而无须考虑具体文件系统和实际物理介质,如下图所示:

vfs

当计算机系统启动时,磁盘各分区上的文件系统需要向VFS注册,注册的时候,文件系统将提供一个包含VFS所需函数地址的列表,通过函数地址,VFS知道如何从文件系统中读取数据块。同时,VFS有一个超级块对象,各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块

文件系统组件的体系结构

接下来,我们来梳理一下体系结构,从应用层到VFS,再到磁盘的一个结构图,从这个架构图中我们可以对文件系统有一个更加清晰的认识,如下图所示:

file system

从图中可以看到上文未提及的Page Cache与直接IO,实际上根据是否利用操作系统的页缓存,可以将文件I/O分为直接IO和非直接IO。

  • 直接IO: 不经过页缓存,直接访问磁盘数据
  • 非直接IO: 文件读写时,先要经过系统的页缓存,如果缓存中有数据中,直接读取;如果没有,再从磁盘中读取

Page Cache主要用来减少对磁盘的I/O操作,通过将磁盘中的数据缓存到物理内存中,把对磁盘的访问变为对物理内存的访问。

其实,还有一个Buffer Cache,它主要用来缓存磁盘块,用于块I/O,也就是对磁盘块数据的缓存,而Page Cache是文件数据的缓存,在Linux 2.4内核以前,两者是独立的,2.4之后就统一了,只有Page Cache,而Buffer是页映射块的,它实际上就是在Page Cache中。

参考资料 & 鸣谢