在 Linux 环境下有过一些经历的同学可能都会遇到一个问题,这个问题就是往机器上插入 U盘 或者其他外接设备的时候,居然经常没有反应,上网查找之后别人会教你怎么用几条命令然后你就可以像操作正常文件一样操作这些设备了(当然,现在很多流行的桌面Linux环境例如 Ubuntu/Centos 都支持自动挂载了)。

但是,可能我们也曾想过为什么 Linux 要这么麻烦,很多时候我遇到这种问题都是想毕竟 Linux 不是普通的 OS 啊,需要这么麻烦也很正常([/手动尴尬]),但是,就是时不时对某些东西的抽风执念,我突然对这个原理上心了,所以想了解一下为什么要这么麻烦,或者说为什么需要这样。当然,在本文中,你得不到为什么要这么麻烦的答案,我猜想无非就是开发对应 OS 的人不想做这个 Feature 而已,实质上可以做到的,就像提过的 Ubuntu 一样;但是,你可以在本文中了解到挂载 U盘 之类的操作是怎么个原理。

Linux 的文件系统抽象模型

在 Linux 中,为了适应不同的格式的文件系统(ext4/nfs),也就是说可以支持同时使用不同文件系统的文件,做了一层抽象,也就是所谓的 VFS(Virtual File System),整个 Linux 的文件层级可以这么抽象得概括一下:

VFS 对上层应用程序和进程隐藏了底层复杂的各种文件系统细节,也就是进程不需要知道文件系统是存放在本地的硬盘或者是 U盘 还是一个网络的文件系统,同时,VFS 对下层提供了一系列抽象的接口,从而使得上层应用可以使用同一的接口从而访问不同来源的设备。需要注意的是,因为 VFS 是一个抽象的概念,所以很多里面的元素都是和具体的文件系统名称相同,在本文中,如果没有将具体的文件系统,那么所说的名词(例如 iNode/Superblock)都是描述 VFS,而不是具体的文件系统。

在 VFS 中,有四个重要的数据结构,他们分别是:

所以当我们在进程中打开一些文件的时候,内存中的对象模型应该是这样的:

具体的文件系统举例:ext2

所谓的具体文件系统就是在我们平时使用过程中,当新装一个 OS 或者新加了一块存储,一般情况下都是不能直接挂载使用的,因为你的磁盘没有设置文件系统,即使挂载上去了,我们的 OS 也是不能识别的。那么,特殊情况是什么呢,那就是你想象这么一种情况,你有两台配置什么的一模一样的机器 A 和 B,一台在家里,一台在其他场所,你想在使用两台机器的时候都使用同一个环境(OS/Soft等等),那么你在任意一台机器上安装配置好环境,然后在两条机器上都通用这个安装了你需要环境的磁盘,这样,你就经常性的在两台机器中往返携带磁盘,而且机器 A 上工作完好的磁盘直接拔下来装到机器 B 中也是正常工作的,并不需要你在机器 B 上又重新设置一下文件系统,虽然你可能觉得这个例子有点傻,但是,这个在云环境确实很方便的实现。

ok,话不多说,来看看 Linux 中一个经典的 ext2 文件系统。在 ext2 文件系统中,这个文件系统所管理的磁盘(可以不是一个完整的磁盘,例如磁盘大小为 1T,这里可以只给 40G)被划分为均等大小的 block,而 block 的大小是可变的,通俗点说 /info/liuliqiang/da/info/liuliqiang/db 都是使用的 ext2 的文件系统,但是 da 的 block 大小是 1024 byte,而 db 的 block 大小却是 4096 byte。

在 ext2 文件系统中,磁盘位置中最开始的 1024 bytes 是 superblock,也就说 superblock 的大小是 1024 bytes。有一个属性需要我们注意,那就是 magic,ext2 文件系统和 ext3 文件系统的 magic 都是 0xEF53,这说明了 ext2 和 ext3 的兼容性很好!,还有一个属性叫 block_size,它用于表示这个 ext2 文件系统中 block 的大小,前面说了,这是可变的。

ext2 的文件系统被均分为了 block,那么我们要怎么存储文件呢?接着往下看,在 ext2 文件系统中,不同数量的 block 被聚成了所谓的 block group,每个 block group 都会对应一个 block group description,这些 group description 会被放在一起,位置紧跟在 superblock 后面。

每个 block group 都包含inode tableblock bitmap,通过这个 inode table,我们就可以得到一个个的 inode 了,然后 inode 里面存储了 block 的指针信息,这样我们就得到了真实的磁盘数据。这些 inode tableblock bitmap 等信息都是放在 block group 的开头,一块接着一块,就组成了所谓的 ext2 文件系统。

VFS 和 ext2 的结合

看完抽象的虚拟文件系统和真实的文件系统,那么我们是时候结合两者进行统一得看一看了。

当我们系统启动加载的时候,会构建一个 Dentry 的目录树,这个目录树和具体的 OS 目录树是两码事,默认创建出来的时候的目录树只有一个真实的 rootfs 文件系统,然后目录树中的其他目录都是为以后挂载其他真实的文件系统提供的挂载点。例如我们的 ext2 文件系统就是挂载到其中的某个目录中,这样就挂载上了。

然后是打开文件,打开文件之后,我们建立了这个文件的 FD,FD 会对应到 Dentry 和 VFS i-node,通过 Dentry 我们就可以找到对应的文件系统(因为它是挂载在 Dentry 中的),然后通过 VFS i-node 我们就可以获取到文件的具体内容,这样就完成了整个 VFS 到 ext2 真实文件系统的转换。

总结

本文从概括性的角度描述了 Linux 下,文件操作的抽象与具体的结合,但是,对于 Linux 的 IO 来说,这还只是冰山一角,而我再后续的文章中也将结合自身的思考和认识,更多得挖掘一些细节。最后,感谢下面 Reference 的这些文章的帮助,让我了解得更清楚一些。

Reference

  1. UNIX环境高级编程
  2. 现代操作系统(第3版)
  3. Linux 虚拟系统文件交换器剖析
  4. 从文件 I/O 看 Linux 的虚拟文件系统
  5. 解析 Linux 中的 VFS 文件系统机制
  6. Ext2 文件系统的硬盘布局