在了解了第一部分:磁盘物理结构和运行原理以后,再看一下基于磁盘的结构基础上,如何构建一个物理的文件系统。
2 Linux物理文件系统原理与实现
在一般的操作系统书籍中,谈到Linux文件系统,都称作虚拟文件系统,我觉得这种叫法存在严重的误导,尤其是对于初学者,在我看来,Linux的文件系统是由物理文件系统和虚拟文件系统两部分构成的。所谓物理文件系统,是指持久化在磁盘上的一种数据结构,这个数据结构可以实现文件的所有功能,并且这个数据结构是以数组来建模的。下面会介绍一个简单的物理文件系统的实现(ext2),来说明物理文件的原理。
2.1 磁盘的进一步抽象
在构建一个文件系统之前,需要对磁盘做进一步的抽象。最初,磁盘被抽象成一个数组,数组的每个元素是512个字节,将数组的8个元素合并成1个,这样数组的长度就缩短了,如果原来的数组长度为N,合并之后的数组长度为N/8,但是数组中每个元素的大小增加到4K,叫做一个block(块),如下图所示:
经过这一层抽象后,由原来的一个扇区数组变为块数组。
2.2 block数组划分5个区域
接下来将block数组划分成5个区域,分别是:超级块、inode位图、逻辑块位图、inode、逻辑块。如下图所示:
这5个区域分别是由若干个逻辑块组成的,其中,最为核心的就是inode,我们知道,在Linux系统中,并不是用文件名来唯一标识一个文件的,而是用inode来唯一标识一个文件,即,inode与文件是一一对应的。一个inode代表一个文件,一个文件由两部分构成:元数据(inode)和文件内容(逻辑块),元数据用来存储文件管理相关的数据,逻辑块用来存储文件内容。inode位图用来存储inode是否被分配的信息,如果没有被分配,则代表该inode的bit为0,如果已经分别,则为1,逻辑块位图也是如此,是用来存储逻辑块是否被分配的信息。
2.3 定义inode数据结构
经过上面两个步骤,就可以开始物理文件系统最最核心的设计,即inode数据结构的设计,文件系统的核心功能需要用到的数据都在inode的结构里面。以ext2为例,inode结构如下:
可以看出,inode中包含三部分信息:文件权限相关的信息、文件的基本信息和文件存储的数据对应的逻辑块信息。
到这里,一个物理文件系统就设计完毕了,就可以实现文件系统的全部功能了,但也许你还没搞懂是如何实现的,不急,接下来会举一个简单文件系统实现和文件系统的核心功能是如何通过inode结构的实现过程。
2.4 物理文件系统实现举例
假设一个磁盘共包含64个block,构建一个简单的文件系统,步骤如下:
首先,对磁盘空间进行划分,分成5个部分,1个block用来存超级块、1个block用来存inode位图、1个block用来存逻辑块位图、5个block用来存inode,剩余的56个block做逻辑块,如下图:
接下来对每个部分进行编号,如下:
- inode位图:1个bit用来表示一个inode节点是否被空闲,4K大小可以表示32768个inode的状态,编号从0~32767
- 逻辑块位图:1个bit用来表示一个inode节点是否被空闲,4K大小可以表示32768个inode的状态,编号从0~32767
- inode:用256个字节表示一个inode,一个block可以保存16个inode,5个block一共可以保存80个inode,编号从0~79
- 逻辑块:1个block表示一个逻辑块,共有56个逻辑块,编号从0~55 从上面的编号可以看出,inode位图和逻辑块位图所能表示的范围是完全够用的,将inode的划分进一步细化,如下图:
block的划分和编号完成后,inode再按照上述的数据结构来设计,就可以实现一个简单的文件系统功能了。
2.5 目录是如何实现的
Linux系统号称一切皆文件,这句话不是盖的,目录也是一种文件,只不过这种文件比较特殊,他存储的内容是目录下面的子文件夹或者文件的名称和inode号,如下图所示:
进入/usr目录,执行ls -ai命令,可以看到里面都是子文件夹,/etc前面的163339就是/etc这个文件夹文件的inode号,如果想查看/etc这个文件夹下面都存储了哪些文件,可以读取163339这个inode,并根据逻辑块指针读取文件的内容,内容的格式也是类似,一个文件或文件夹对应了一个inode号。
每个文件夹都有2个特殊的inode号,.和..,我们知道,在linux系统中,.表示当前路径,..表示上一级文件夹路径,..的inode为64表示上一级文件夹的inode号为64,即/路径的inode号为64。再打开/文件夹查看,如下图所示:
可以看到根文件夹的.和..的inode相等都是64,说明根文件夹再往上没有文件夹了。根文件夹的inode号是在操作系统启动的时候,文件系统挂载到虚拟目录之前就已经缓存在内核中了,只要有了根节点的inode号,就可以顺藤摸瓜遍历下去,找到所有文件的文件夹。
寻找文件夹很重要,因为要想访问一个文件,就需要先找到文件对应的inode号,这个数据是保存在上级目录的文件中的,找到了文件对应的inode号以后,才可以对文件进行访问。比如,如果你想访问/home/iceli/abc.txt,需要先访问/的目录文件,由于/的inode是缓存在内核中,因此可以直接打开,拿到/home对应的inode 398920,然后再打开398920拿到/home/iceli的inode 987123,再打开987123拿到/home/iceli/abc.txt的inode 337092,然后就可以访问abc.txt文件了。
但是这里有个问题,访问一个文件的过程太繁琐了,要根据文件的目录层级依次访问每个文件目录的文件,每访问下一层目录都需要访问一次磁盘,这样效率岂不是很低?操作系统早就考虑到这一点,因此把所有目录文件的inode和文件内容都直接缓存在内核中,这样当访问一个文件的时候就不用访问磁盘了,直接通过内核找到/home/iceli/abc.txt的inode 337092并访问。甚至abc.txt文件的内容也会缓存在操作系统的page cache中(后面讲到虚拟文件系统的时候会讲到),提升下次访问时候的效率。
2.6 硬链接和软链接是如何实现的
Linux系统支持硬链接和软链接功能,可以通过ln命令来创建一个硬链接,创建了/root/abc.txt的一个硬链接xxoo,通过stat命令查看,如下图:
可以看到,abc.txt和xxoo的inode号是相同的,这说明abc.txt和xxoo无论是inode还是逻辑块都是相同的,文件名不同是因为文件名是存储在目录文件里面的,因此,硬链接只是文件的别名,除了文件名称不同,其他(inode、逻辑块)都相同。
再给abc.txt增加一个软链接xxyy,通过stat命令查看,如下图:
可以看到,xxyy的inode和abc.txt的inode是不同的,是新创建出来的,但是软链接的Blocks又是0,可以看出软链接本身并没有指向任何逻辑块,而是只有一个inode,它是逻辑块指针是指向的abc.txt的逻辑块。因此,软链接可以看作是一个没有逻辑块的inode。因此,如果把原文件删除,则原文件的inode和逻辑块都没有了,而软链接又只有一个inode,此时,就会出现指向的数据不存在的问题,如下图所示:
2.7 一个文件的访问过程
接下来从物理文件系统的视角来介绍一个文件的完整访问过程,强调一下,之所以说是从物理文件系统的视角,是因为真实的文件访问过程不是这样的,这个例子只是为了更进一步说明文件和目录是怎样在磁盘上存储的。
举例:在/home/iceli下打开一个文件demo.txt,并写入字符串abc
首先,在访问demo.txt之前,先要找到demo.txt文件对应的inode,而这个inode号是保存在/home/iceli的目录文件中,因此,先根据/文件的inode,顺藤摸瓜一直找到/home/iceli的inode号,假设是8,打开8号inode并查看里面的block 108,inode 108就代表了/home/iceli这个目录的文件内容,这里保存了demo.txt的inode,通过比较发现demo.txt的inode号为47;找到demo.txt的inode号以后,就可以开始访问demo.txt的文件了,读取47号inode,得到block为627,即,demo.txt的数据部分保存在627号block中,于是往627号block写入数据abc,任务完成。