文件系统之dentry
注:本文分析基于linux-4.18.0-193.14.2.el8_2内核版本,即CentOS 8.23 dentry3.1 dentry主要成员变量3.2 添加dentry到cache3.3 从cache中删除dentry(回收dentrycache)4 file4.1 file主要成员变量4.2 创建file4.3 删除file
注:本文分析基于linux-4.18.0-193.14.2.el8_2内核版本,即CentOS 8.2
1 dentry
在内存中,每个文件除了有inode,同时也会有一个dentry结构。它记录文件的名称,父目录,子目录等信息,形成我们看到的层级树状结构。与inode不同的是,dentry只存在于内存,磁盘上并没有对应的实体文件,因此目录项也就不会涉及回写磁盘。
2 dentry主要成员变量
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock */
seqcount_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; //通过该变量挂载到全局dentry哈希表dentry_hashtable上
struct dentry *d_parent; //父目录的目录项对象
struct qstr d_name; //目录项名称
struct inode *d_inode; //指向目录对应的inode结构
unsigned char d_iname[DNAME_INLINE_LEN]; //存放短文件名
struct lockref d_lockref; //使用计数
const struct dentry_operations *d_op; //目录项的操作函数
struct super_block *d_sb; //目录项对应的超级块
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; //文件系统私有数据
union {
struct list_head d_lru; //通过该变量链接到超级块的s_dentry_lru链表
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
struct list_head d_child; //通过该变量链接到父目录的d_subdirs中
struct list_head d_subdirs; //本目录所有子目录链表
union {
//通过该变量链接到inode结构的i_dentry中,
//一个inode可对应多个dentry,因为存在软链接
struct hlist_node d_alias;
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu;
} d_u;
};
3 创建一个dentry
我们仍旧以以ext4的创建目录来大概分析下整个过程,
我们从系统调用开始,在上一篇文章——文件系统之inode中,我们分析的是mkdir的过程,其实在此之前需要创建dentry对象。
SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode)
{
return do_mkdirat(AT_FDCWD, pathname, mode);
}
long do_mkdirat(int dfd, const char __user *pathname, umode_t mode)
{
...
//为该目录创建dentry
dentry = user_path_create(dfd, pathname, &path, lookup_flags);
...
if (!error)
//调用对应文件系统的mkdir为该目录创建inode,对于ext4调用的是ext4_mkdir
error = vfs_mkdir(path.dentry->d_inode, dentry, mode);
...
}
user_path_create通过用户传入的文件路径创建对应的dentry,但是需要查看下系统中是否已存在该目录,查询前需要获取父目录信息,
inline struct dentry *user_path_create(int dfd, const char __user *pathname,
struct path *path, unsigned int lookup_flags)
{
return filename_create(dfd, getname(pathname), path, lookup_flags);
}
static struct dentry *filename_create(int dfd, struct filename *name,
struct path *path, unsigned int lookup_flags)
{
//查找父目录文件对象,因为新建的目录还不存在,我们需要找到其父目录
//然后将新建的目录关联到父目录,形成树形结构
name = filename_parentat(dfd, name, lookup_flags, path, &last, &type);
...
//分别在dentry哈希表和磁盘中查找目标dentry,没有则创建新的dentry
//对于新建的操作,此时还没有建立该dentry对应的inode对象
dentry = __lookup_hash(&last, path->dentry, lookup_flags);
...
}
目标目录的查询主要通过__lookup_hash,现在全局哈希表中查询,查不到则到磁盘中查,对于新建目录肯定是查不到的,因此返回的只有一个dentry对象,还没有填充对应的inode结构,这个就需要回到最开始的vfs_mkdir,对于ext4也就是ext4_mkdir里创建inode并关联到该dentry。
static struct dentry *__lookup_hash(const struct qstr *name,
struct dentry *base, unsigned int flags)
{
//从dentry hash table上查询目标dentry
struct dentry *dentry = lookup_dcache(name, base, flags);
struct dentry *old;
struct inode *dir = base->d_inode;
//hash table上有,直接返回
if (dentry)
return dentry;
/* Don't create child dentry for a dead directory. */
if (unlikely(IS_DEADDIR(dir)))
return ERR_PTR(-ENOENT);
//哈希表没查到,从dentry slab缓存中获取空闲对象,创建新的dentry
dentry = d_alloc(base, name);
if (unlikely(!dentry))
return ERR_PTR(-ENOMEM);
//调用对应文件系统的lookup函数,到磁盘中查找目标目录项
//对于新建的操作,显然磁盘中是查不到的
old = dir->i_op->lookup(dir, dentry, flags);
//如果查到了就释放刚才创建的dentry,返回找到的这个
if (unlikely(old)) {
dput(dentry);
dentry = old;
}
return dentry;
}
在分配dentry对象时,使用的是slab缓存,用以提高分配效率。分配后需要将该dentry的d_parent指向刚才查找到的父目录,同时将d_child挂到父目录的d_subdirs链表,这样由父子目录一层层堆叠,就形成了树形结构。
struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
{
//从对应的超级块的slab缓存中分配空闲dentry
struct dentry *dentry = __d_alloc(parent->d_sb, name);
if (!dentry)
return NULL;
dentry->d_flags |= DCACHE_RCUACCESS;
spin_lock(&parent->d_lock);
__dget_dlock(parent);
//对新目录项设置父目录项
dentry->d_parent = parent;
//新目录项的d_child挂到父目录的d_subdirs链表
list_add(&dentry->d_child, &parent->d_subdirs);
spin_unlock(&parent->d_lock);
return dentry;
}
struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
{
struct dentry *dentry;
char *dname;
int err;
//从slab缓存中分配dentry对象
dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
//对dentry进行各个变量的初始化
...
return dentry;
}
4 添加dentry到dentry cache中
我们知道超级块上有用于管理未使用的inode的LRU链表s_inode_lru,同样的,对于dentry,超级块上也有对应的LRU链表s_dentry_lru,同样也是用于管理未使用的dentry,其实也就是使用完的对象,在内存不足时,就会回收这里的对象。
主要的路径就是dput,在使用完dentry后减少对其的引用,如果引用计数大于1,就比较简单,减少引用就可以返回。
void dput(struct dentry *dentry)
{
while (dentry) {
might_sleep();
rcu_read_lock();
//如果该dentry引用计数大于1,则减少引用计数后直接返回
if (likely(fast_dput(dentry))) {
rcu_read_unlock();
return;
}
/* Slow case: now with the dentry lock held */
rcu_read_unlock();
//如果该dentry引用计数小于等于1,判断是否要直接回收
if (likely(retain_dentry(dentry))) {
spin_unlock(&dentry->d_lock);
return;
}
//走到这说明这个dentry可以回收了
dentry = dentry_kill(dentry);
}
}
但对于引用计数小于等于1时,还需要进一步判断,如果dentry不在哈希表,或者该dentry有自己的d_delete方法,此时可以对其进行回收,否则就先将其加入超级块的s_dentry_lru链表,等待内存回收时再将其释放
static inline bool retain_dentry(struct dentry *dentry)
{
WARN_ON(d_in_lookup(dentry));
//dentry已不在全局hash表,直接返回,后续调用dentry_kill删除该dentry
if (unlikely(d_unhashed(dentry)))
return false;
if (unlikely(dentry->d_flags & DCACHE_DISCONNECTED))
return false;
//该dentry有d_delete操作函数,则调用,然后返回再dentry_kill删除该dentry
//常见的文件系统ext4、xfs都没有设置该函数
if (unlikely(dentry->d_flags & DCACHE_OP_DELETE)) {
if (dentry->d_op->d_delete(dentry))
return false;
}
/* retain; LRU fodder */
dentry->d_lockref.count--;
//不属于以上情况,那就先放超级块的s_dentry_lru链表
if (unlikely(!(dentry->d_flags & DCACHE_LRU_LIST)))
d_lru_add(dentry);
else if (unlikely(!(dentry->d_flags & DCACHE_REFERENCED)))
dentry->d_flags |= DCACHE_REFERENCED;
return true;
}
static void d_lru_add(struct dentry *dentry)
{
D_FLAG_VERIFY(dentry, 0);
dentry->d_flags |= DCACHE_LRU_LIST;
this_cpu_inc(nr_dentry_unused);
if (d_is_negative(dentry))
this_cpu_inc(nr_dentry_negative);
//将dentry挂到对应超级块的s_dentry_lru链表
WARN_ON_ONCE(!list_lru_add(&dentry->d_sb->s_dentry_lru, &dentry->d_lru));
}
5 从dentry cache中删除dentry(回收dentry cache)
当内存不足时,除了回收inode cache,dentry cache也会被回收,主要通过prune_dcache_sb操作,
long prune_dcache_sb(struct super_block *sb, struct shrink_control *sc)
{
LIST_HEAD(dispose);
long freed;
//遍历s_dentry_lru链表,分离部分dentry到dispose,避免锁竞争
freed = list_lru_shrink_walk(&sb->s_dentry_lru, sc,
dentry_lru_isolate, &dispose);
//对分离出来的dentry回收
shrink_dentry_list(&dispose);
return freed;
}
回收dentry主要是要将dentry从各个链表中摘除,也就是所以引用的地方都要解除,
- s_dentry_lru链表
- 全局dentry哈希表
- 与父目录的关系
- 关联的inode
- 关联的inode别名
static void shrink_dentry_list(struct list_head *list)
{
while (!list_empty(list)) {
struct dentry *dentry, *parent;
dentry = list_entry(list->prev, struct dentry, d_lru);
spin_lock(&dentry->d_lock);
...
//将dentry从超级块的s_dentry_lru链表中摘除,
//并清除DCACHE_SHRINK_LIST和DCACHE_LRU_LIST标志
d_shrink_del(dentry);
parent = dentry->d_parent;
//做进一步清理工作
__dentry_kill(dentry);
//如果该dentry是根目录,到此为止
if (parent == dentry)
continue;
//删除dentry后,如果父目录引用计数也小于等于1了,对父目录尝试做回收操作
dentry = parent;
while (dentry && !lockref_put_or_lock(&dentry->d_lockref))
dentry = dentry_kill(dentry);
}
}
static void __dentry_kill(struct dentry *dentry)
{
struct dentry *parent = NULL;
bool can_free = true;
if (!IS_ROOT(dentry))
parent = dentry->d_parent;
//调用dentry的d_prune去unhash dentry
if (dentry->d_flags & DCACHE_OP_PRUNE)
dentry->d_op->d_prune(dentry);
//如果dentry还在s_dentry_lru链表,则摘除
if (dentry->d_flags & DCACHE_LRU_LIST) {
if (!(dentry->d_flags & DCACHE_SHRINK_LIST))
d_lru_del(dentry);
}
/* if it was on the hash then remove it */
//对于没有d_prune函数的dentry,会在这里从哈希表里删除
__d_drop(dentry);
//解除和父目录的关系,d_child和d_child之间的关系
dentry_unlist(dentry, parent);
if (parent)
spin_unlock(&parent->d_lock);
if (dentry->d_inode)
//解除和对应inode的关联
dentry_unlink_inode(dentry);
else
spin_unlock(&dentry->d_lock);
this_cpu_dec(nr_dentry);
//如果有d_release则调用
if (dentry->d_op && dentry->d_op->d_release)
dentry->d_op->d_release(dentry);
spin_lock(&dentry->d_lock);
if (dentry->d_flags & DCACHE_SHRINK_LIST) {
dentry->d_flags |= DCACHE_MAY_FREE;
can_free = false;
}
spin_unlock(&dentry->d_lock);
if (likely(can_free))
//来到最后一站,就可以解脱了
dentry_free(dentry);
cond_resched();
}
static void dentry_free(struct dentry *dentry)
{
//解除d_alias上的映射,如果它是某个inode的别名
WARN_ON(!hlist_unhashed(&dentry->d_u.d_alias));
...
//该处理的资源都处理了,可以走了
if (!(dentry->d_flags & DCACHE_RCUACCESS))
__d_free(&dentry->d_u.d_rcu);
else
call_rcu(&dentry->d_u.d_rcu, __d_free);
}
所有地方都抽身后,就可以释放回slab缓存了,
static void __d_free(struct rcu_head *head)
{
struct dentry *dentry = container_of(head, struct dentry, d_u.d_rcu);
//秉承着从哪里来回哪里去的原则,dentry被释放回slab缓存
kmem_cache_free(dentry_cache, dentry);
}
6 结构关系
一个路径的各个组成部分,不管是目录还是普通的文件,都是一个dentry对象。我们以/home/test.c为例,/,home,test.c这三个都是一个目录项。
- 超级块的s_root指向当前文件系统根目录“/”
- 根目录的d_parent指向自己
- home目录和var目录的d_parent指向根目录
- test.c文件的d_parent指向home目录
- home目录和var目录通过d_child链接到根目录的d_subdirs
- test.c文件通过d_child链接到home目录的d_subdirs,由此形成树形结构
- 根目录,home目录,var目录以及test.c文件的d_hash都指向全局目录哈希表dentry_hashtable
- 使用完被删除资源的deleted目录项通过d_lru链接到超级块的s_dentry_lru链表,而根目录,home目录,var目录以及test.c文件在使用中,不会链接到该链表
- 根目录,home目录,var目录,test.c文件以及deleted目录项d_sb都指向超级块
更多推荐
所有评论(0)