Linux文件系统中使用了大量cache,用于提升IO性能,本篇来梳理一下这些与文件系统相关的cache,它们在内存中是如何组织管理的,它们是如何加速文件系统操作的。
Dentry Cache
dentry用于描述系统目录树中的一个节点,磁盘文件系统中通常没有相关结构,dentry只存在于内存之中,它的主要作用是提供了一种快速的通过路径名查找到对应文件的方法。
组织方式
dentry中用于组织管理的数据结构如下:
struct dentry {
...
struct hlist_bl_node d_hash; /* lookup hash list */ //dentry_hashtable全局hash表中的节点
struct qstr d_name; //根据其->hash来计算该dentry在dentry_hashtable中的索引
...
union {
struct list_head d_lru; /* LRU list */ //其d_sb->s_dentry_lru中的节点
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
struct list_head d_child; /* child of parent list */ //其d_parent->d_subdirs中的节点
struct list_head d_subdirs; /* our children */ //子dentry链表
...
union {
struct hlist_node d_alias; /* inode alias list */ //在其对应inode->i_dentry中的节点(一个inode可能有多个硬连接,在inode中需要用一个链表管理这些dentry)
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu;
} d_u;
}
dentry结构体由struct kmem_cache *dentry_cache 分配,dentry_cache 在内核初始化流程中 dcache_init() 函数里初始化。同时该函数中还会初始化管理dentry的全局hash表dentry_hashtable。
所有dentry总是处于三种状态:
- unused,未被内核使用,引用计数d_lockref为0,d_inode非空。
- inuse,正在被使用,引用计数d_lockref>0。
- negative,相应的磁盘inode已经被删除,d_inode为空。
dentry结构通常在路径查找流程中被创建,主要有以下四种管理方式:
- dentry_hashtable:以dentry->d_name.hash作为索引在dentry全局hash表中管理,用于路径查找。
- superblock->s_dentry_lru:管理所有unused和negative的dentry,当需要回收内存时,从中选取“最少使用的”dentry回收。
- d_subdirs:dentry结构中记录其子dentry,用于dir管理。
- inode->i_dentry:由于一个inode可能有多个硬链接,这些dentry称为alias,使用双向链表管理,每个inuse的dentry加入其inode->i_dentry链表。
其中用的最多的是dentry_hashtable,每次open文件时,都要反复访问它来查找对应的文件。
path-lookup流程
path-lookup(Documentation/filesystems/path-lookup.txt)是根据一个路径名,逐级查找dentry,最终找到目标文件dentry的过程。既然有了dentry cache,基本策略就是先从cache里找,如果找不到再通过磁盘数据生成dentry,直到得到最终文件的dentry,而内核设计了复杂的流程用于提升查找性能。
由于dentry在系统中可能被并发的访问及修改,为了保证查找数据的正确性并兼顾性能,内核设计了两种查找方式。
- ref-walk,传统的cache访问方式,使用自旋锁以及引用计数来保证数据正确性,这使得查找路径中的每个dentry都会有加锁的开销以及可能发生阻塞,造成性能下降。
- rcu-walk,基于顺序锁的查询方式,在查询过程中不用加锁,十分高效,若rcu-walk查询失败再尝试切换到ref-walk模式继续后面的查询。
这两种方式都是从dentry_hashtable中查找(lookup_fast),若dentry_hashtable中无此dentry,再通过inode->i_op->lookup查找(lookup_slow)。path-lookup整体流程如下:
- 以rcu-walk模式逐级查找dentry,并校验正确性。
- 若rcu-walk出现校验失败,从头开始以ref-walk模式查找。
- 若rcu-walk出现查找失败,调用unlazy_walk尝试drop-rcu,校验顺序锁,对最后一个可用dentry增加引用计数,若unlazy_walk成功以ref-walk模式继续查找,若不成功从头开始以ref-walk模式查找。
- 若ref-walk查找失败,执行lookup_slow,通过inode->i_op->lookup查找。
回收流程
当dentry不在被使用,即其引用计数d_lockref变为0,或者其对应的inode被删除时,将该dentry加入superblcok的s_dentry_lru。当需要回收内存时,由prune_dcache_sb(),回收superblock中使用较少的dentry。
Inode Cache
inode描述文件系统中的一个文件,通常有一个或多个dentry与之对应。dentry已经完整描述了系统的目录树,并且提供了路径查找的方法,inode就不需要再处理复杂的相互关系,其管理方式也相对简单一些。
组织方式
inode中用于组织管理的数据结构如下:
struct inode {
...
/* Stat data, not accessed from path walking */
unsigned long i_ino;