1.1.VFS中的索引节点缓存

搜索安装文件系统时,其VFS索引节点不断地被读或写。虚拟文件系统通过维护一个索引节点缓存来加速对所有已安装文件系统的访问。每次VFS索引节点都可从索引节点缓存中读取出来以加速对物理设备的访问。VFS索引节点缓存是一个入口为VFS inodes链表指针的哈希表,哈希表的每一项包含一个指向VFS索引节点的指针,这个节点在这个链的链首。同一链表的索引节点拥有相同的哈希值,这个值是根据索引节点和该文件所在的物理设备的标识符得到的。

VFS要访问某个节点时首先查看VFS索引节点缓存,通过计算哈希值找到对应的hash_table,在索引节点链表中查找与指定设备号和索引节点号匹配的索引节点。如果访问的节点在索引节点缓存中则访问计数i_count加1,否则需要找到一个空闲VFS索引节点,以便文件系统能从内存中读取此节点。

VFS有许多种选择可以取得空闲的索引节点。如果系统可以分配多个VFS索引节点,将按如下步骤进行:首先分配一物理页面,将其按索引节点长度分割并放入索引节点链表中。如果系统已经拥有所有的索引节点就必须找到便于重新使用的节点,即节点的记数i_count为0,这种索引节点是没有使用的。重要的VFS索引节点,如文件系统的根节点,其count域总是大于0,所以它使用的索引节点是不能被重新使用的。

获得空闲的VFS节点后VFS会调用文件系统专有的例程从逻辑文件系统中读取信息并开始填充该索引节点,使节点的使用计数为1,同时被锁定,以免其他进程访问该节点。

1.2.VFS中的目录缓存

为了加速对常用目录的访问VFS维护了一个目录入口缓冲区。

在实际文件系统寻找目录时,有关此目录的细节将被存入目录缓存中。再次寻找此目录时,比如在此目录下列出文件名或打开文件,这些信息就可以在目录缓存中找到。由于较短目录名的目录是使用最频繁的,因此在实际实现中只有短目录(最多15个字符)被缓存。该目录缓存由一个哈希表来管理,哈希索引值由文件所在的设备号以及目录名称计算得到。

为了保证目录缓存的有效性和及时更新,VFS保存了一个最近最少使用(LRU)的目录缓存链表。首次查找此目录项时,其目录项被首次放入缓存中并添加到第一级LRU链表的尾部。如果缓冲区被全部占用,它就代替LRU链表的表头。此目录项被再次使用时,将被放到第二级LRU链表的末尾,此时需要将位于第二级LRU链表的表头替换掉。目录项在链表的前端表示它们已经很勺没被访问过了。如果被访问过,那么它们将位于此链表的尾部附近。位于第二级LRU链表中的项要比位于第一级LRU链表中的安全一些。

对目录缓存操作的函数在fs/dcache.c中定义。

1.3.缓冲区缓存

Linux支持的文件系统大多以块的形式组织文件,为了减少对物理块设备的访问,当文件以块的形式调入内存后,可以使用块缓冲区对它们进行管理。每个块缓冲区中存在一个缓冲区首部(用数据结构buffer_head表示)和存储数据的缓冲区空间。缓冲区首部不与数据区域相连,数据区域独立存储,两者之间由一个指针*b_data链接。

所有数据块的读/写请求以buffer_head数据结构的形式传递给设备驱动程序。

缓冲区缓存由两个部分组成:一是空闲块缓冲区链表,它为每个可支持的块大小提供了一个链表,并且系统中的空闲块缓冲区在创建或被丢弃时都被链接到此链表中;二是缓存本身,这是一张哈希表。每个入口是指向由指针串起来的缓冲区链表的指针。哈希索引值由数据块拥有的设备标识符和块号码产生。

块缓冲区在空闲链表中或是在哈希缓冲区缓存中。如果在缓冲区缓存中它们按照LRU链表来排列。对于每种缓冲区类型都有一个LRU链表。

文件系统需要从其底层物理设备读取一个缓冲块时,首先在缓冲区缓存中寻找。如果在此缓冲区缓存中得不到一个缓冲区,则将从适当大小的空闲链表中取得一个clean状态的节点,同时将新缓冲区添加到缓冲区缓存中去。如果所需的缓冲区位于缓冲区缓存中,则可能已经含有最新的数据。如果没有被更新或者为新块则文件系统必须请求相应的设备驱动程序从磁盘中读取该数据块。

为了让此缓冲区缓存运行更加有效,并且在使用此缓冲区缓存的块设备之间合理地分配缓存入口,Linux使用bdflush监控程序来执行缓存的看护工作。bdflush对过多的dirty缓冲系统提供动态响应的简单核心后台进程,这些缓冲块中包含必须被写入到硬盘上的数据。通常此进程一直在休眠,直到系统中的dirty缓冲区数目增大到一定数量。分配与丢弃缓冲区时系统将检查dirty缓冲区的数目,如果超过某个百分比(默认为60%),则唤醒bdflush进程。但如果系统急需缓冲区,则任何时刻都可能唤醒bdflush。这个域值可以通过update命令修改。数据写入缓冲区使之变成dirty时,所有的dirty缓冲区被链接到一个BUF_DIRTY LRU链表中,bdflush会将适当数目的缓冲块(默认值为500)写到磁盘上。

至于update,它不仅仅是一个命令,还是一个后台进程。当作为超级用户运行时(在系统初始化时),它将周期性调用系统服务例程,将老的dirty缓冲区内容冲刷到磁盘上去。这个工作与bdflush类似。一个dirty缓冲区完成此操作后,它将把本应写入各自磁盘上的时间标记到buffer_head结构中。update每次运行时将在系统中的所有dirty缓冲区中查找那些冲刷时间已超过一定期限的缓冲区,这些过期缓冲区都要被写入磁盘。