8.3.3  目录高速缓存

由于从磁盘读入一个目录项并构造相应的目录项对象需要花费大量的时间,所以,在完成对目录项对象的操作后,可能后面还要使用它,因此在内存仍保留它有重要的意义。例如,我们经常需要编辑文件,随后进行编译或编辑,然后打印或拷贝,再进行编辑,诸如此类的情况中,同一个文件需要被反复访问。

  每个目录项对象属于以下四种状态之一:

·      空闲状态::处于该状态的目录项对象不包含有效的信息,还没有被VFS使用。它对应的内存区由slab分配器进行管理。

·      未使用状态:处于该状态的目录项对象当前还没有被内核使用。该对象的引用计数器d_count的值为NULL。但其d_inode域仍然指向相关的索引节点。该目录项对象包含有效的信息,但为了在必要时回收内存,它的内容可能被丢弃。

·      正在使用状态:处于该状态的目录项对象当前正在被内核使用。该对象的引用计数器d_count的值为正数,而其d_inode域指向相关的索引节点对象。该目录项对象包含有效的信息,并且不能被丢弃。

·      负状态:与目录项相关的索引节点不复存在,那是因为相应的磁盘索引节点已被删除。该目录项对象d_inode域置为NULL,但该对象仍然被保存在目录项高速缓存中,以便后续对同一文件目录名的查找操作能够快速完成,术语“负的”容易使人误解,因为根本不涉及任何负值。

为了最大限度地提高处理这些目录项对象的效率,Linux使用目录项高速缓存,它由两种类型的数据结构组成:

·      处于正在使用、未使用或负状态的目录项对象的集合。

·      一个哈希表,从中能够快速获取与给定的文件名和目录名对应的目录项对象。如果访问的对象不在目录项高速缓存中,哈希函数返回一个空值。

目录项高速缓存的作用也相当于索引节点高速缓存的控制器。内核内存中,目录项可能已经不使用,但与其相关的索引节点并不被丢弃,这是由于目录项高速缓存仍在使用它们,因此,索引节点的i_count域不为空。于是,这些索引节点对象还保存在RAM中,并能够借助相应的目录项快速引用它们。

所有“未使用” 目录项对象都存放在一个“最近最少使用”的双向链表中,该链表按照插入的时间排序。换句话说,最后释放的目录项对象放在链表的首部,所以最近最少使用的目录项对象总是靠近链表的尾部。一旦目录项高速缓存的空间开始变小,内核就从链表的尾部删除元素,使得多数最近经常使用的对象得以保留。LRU链表的首元素和尾元素的地址存放在变量dentry_unused中的next 域和 prev域中。目录项对象d_lru域包含的指针指向该链表中相邻目录的对象。

每个“正在使用”的目录项对象都被插入一个双向链表中,该链表由相应索引节点对象的i_dentry域所指向(由于每个索引节点可能与若干硬连接关联,所以需要一个链表)。目录项对象d_alias域存放链表中相邻元素的地址。

当指向相应文件的最后一个硬链接被删除后,一个“正在使用”的目录项对象可能变成“负”状态。在这种情况下,该目录项对象被移到“未使用” 目录项对象组成的LRU链表中。每当内核缩减目录项高速缓存时,“负”状态目录项对象就朝着LRU链表的尾部移动,这样一来,这些对象就逐渐被释放。

哈希表是由dentry_hashtable数组实现的。数组中的每个元素是一个指向链表的指针,这种链表就是把具有相同哈希表值的目录项进行散列而形成的。该数组的长度取决于系统已安装RAM的数量。目录项对象d_hash域包含指向具有相同hash值的链表中的相邻元素。哈希函数产生的值是由目录及文件名的目录项对象的地址计算出的。