一篇文章理解Ext4文件系统的目录

图1 Linux中目录列表

使用过Linux的同学应该对Ext4文件系统都有了解。在Linux文件系统中一切皆文件,同样目录也是文件的一种类型。熟悉Linux服务器的同学经常会看到如下内容,上图是某个目录的列表内容。

在上图中每一行的前面形如drwxr-xr-x的内容为文件的属性,而第一个字符d则表示这个文件是一个特殊的文件,也就是目录(directory)。第一个字符是用于标示文件类型的,对于块设备则是b,字符设备是c等等,每种特殊的文件这个字符都是不同的。

对于有图像界面(GUI)的情况下,目录的呈现形式更加丰富,如图2所示,目录中有子目录和文件。同时有些文件(例如图片和PDF等)还可以呈现其概览。这样在浏览文件的时候很有帮助,可以很方便的找到想要的文件。
图2 目录呈现形式

什么是目录

废话说了一大箩,下面我们进入正题。到底什么是目录?目录的本质是什么?我们知道在Ext4中文件是被组织成树型结构的,而目录就是其中的中间节点。如图是一个目录结构的基本示意图。
图3 目录结构
直观上感觉目录就是一个容器,其中子目录和文件是包含在目录中的。那么目录在磁盘空间上有事怎么样一个存在呢?直观的感觉是目录是一个比较大的存储区域,然后子目录和文件等都在这个区域中,如图4所示。
图4 猜测的数据布局形式
然而,上述存储形式可能问题会比较多:

  • 由于目录是层层嵌套的,如果采用这种形式,那子目录中的内容就不容易放置
  • 由于文件大小差异比较大,目录中内容的检索不太容易
    实际上,在目录中并没有存储文件的数据信息,而只是存储了一个类似C语言指针的东东,这个东东就是文件的inode id。而目录中的子目录数据和文件数据仍然是平铺在磁盘上的。这样,在目录中通过这个指针就可以轻易的找到文件的数据,而且目录的数据和文件的数据组织也变得非常简单。
    图5 目录数据存储形式

    目录数据结构与组织

    前面我们了解了Ext4文件系统目录的组织形式,但还是停留在感性的认识层面。接下来我们结合Ext4文件系统中关键的数据结构和磁盘实际数据理解一下目录数据到底是怎么存储的。首先我们需要意识到的是目录本质上也是一个文件,只不过其中存储的数据是关于子目录和文件的名称信息。理解到这一层面对理解后续内容来说很重要。在逻辑层面上,文件就是一个线性空间,可以理解为一个大的数组(物理层面可能是分散的,暂时不考虑)。
    那么这个大数组中的元素是什么呢?就是图6所示的这个结构体。从该结构体可以看出,每一项内容包括inode的id、该结构体的大小、文件(子目录)名大小和文件名等信息。在检索目录内容的时候,其实就是根据文件名获得inode的id,然后在根据该id从inode表中获得inode(文件)的详细信息。
    图6 目录子项内容
    为了便于理解上述数据结构,我们看一个具体的例子。我们在一个目录中创建文件名为test-0到test-2000等几千个文件,然后把目录中一部分数据导出到某个文件中,图7是这个文件的局部数据。可以对照这图7的数据和图6的数据结构理解一下,图中test-223文件的inode是0X800E1(524513),文件名长度为0X8,结构体长度为0X10(16)。
    图7 目录内容细节
    我们再通过stat命令看一下test-223文件的详细信息。可以看到inode信息与上面存储的信息是一致的
    图8 文件详细信息
    目录数据组织本文就介绍到这里,关于逻辑地址与物理地址的对应关系部分本文不再介绍。如果想了解,可以参考《Ext4文件系统之文件数据组织》一文,目录和文件的数据组织形式是一致的。
    细心的同学可能会发现图7中文件名并不是有序的,这样就存在一个问题。如果一个目录中文件的数量非常多,比如数万个,那么查找文件的性能就会非常差,这个问题怎么解决?

    目录查询加速

    上面我们提出了一个问题,那就是对于大目录,如何提升查询性能。在Ext4文件系统中实现了一个名为目录索引的特性,通过索引可以极大的提升性能。

    Use hashed b-trees to speed up name lookups in large directories. This feature is supported by ext3 and ext4 file systems, and is ignored by ext2 file systems.

在Ext4文件系统中这个索引是通过一个成为哈希树(多叉树)的方式实现的,其中Key为文件名的哈希值,而Value则是具体的数据位置(磁盘块位置)。由于Key是有序的,因此查找非常方便,也就是可以通过文件名快速的找到ext4_dir_entry_2,然后可以找到inode信息。
如图9是关于哈希树的一个简单示意图,例如其中hash1和hash2之间的值的文件名都存储在hash1所指向的块中,而hash2和hash3之间的内容则存储在hash2存储的块中,以此类推。当然,这里给出的是一个一层的哈希树,实际上可以是二层。
图9 哈希树示意图
可以通过debugfs工具查看目录的哈希树信息,如图10为本文构造的目录的哈希树树根内容。其中Entry #0就是一个哈希项的内容,其中包括哈希值(Key)和指向的逻辑地址(Value)。
图10 哈希树根内容
我们结合Ext4文件系统的数据结构可以将哈希树调整为图11所示。从这个图上可以看出inode节点并没有任何变化,而是其中i_block指针发生了变化。这里从原来的Extent B树变成了现在的哈希树。
图11 Ext4哈希树结构