dentry有一个d_flags字段,dentry最重要的状态,保存在这个flags里面。
这个d_flags,
有时由dentry->d_lock保护。
有时由dentry->d_seq保护。
有时由smp_load_acquire()读取
有时又直接访问。
核心思想理解:
dentry处于不同的状态下,用不同的方式访问d_flags,
比如刚创建的时候,没有任何其它人可以访问dentry,那么就可以直接访问d_flags。
这只是个例子,具体的需要详细了解dentry的状态变迁。
d_flags 20-22位,表示文件的类型。
如果是0,则是:DCACHE_MISS_TYPE,表示negative dentry。
还有一些其他的类型:文件、文件夹、Whiteout、sysmlink、special等。
删除一个文件后,该文件对应的dentry变成negative。调用栈:
[ 24.751831] CPU: 3 UID: 0 PID: 124 Comm: rm Not tainted 6.12.37+ #15 [ 24.751851] Hardware name: linux,dummy-virt (DT) [ 24.751858] Call trace: [ 24.751861] dump_backtrace+0xac/0xcc [ 24.751879] show_stack+0x14/0x1c [ 24.751887] dump_stack_lvl+0x78/0xa8 [ 24.751895] dump_stack+0x14/0x1c [ 24.751900] dentry_unlink_inode+0x3c/0xe8 [ 24.751908] d_delete+0x50/0x7c [ 24.751914] vfs_unlink+0xfc/0x110 [ 24.751921] do_unlinkat+0x1b4/0x214 [ 24.751927] __arm64_sys_unlinkat+0x54/0x60
重点是:d_delete()接口。
这个接口很有意思。有两种处理场景。dentry的核心思想在这里体现。
注释已经说的非常详细了。
通常我们希望这个dentry变成negative。否则需要释放它,下一次查询的情况,还会重新创建一个negative dentry。
但是,如果它的引用计数大于1,那么不能直接把它变成negative,
因为dentry设计一旦inode和dentry关联,那么就不会把它解关联。这是dcache实现高效无锁访问的关键。
这可以保证内核用户获得了一个positive dentry的引用计数后,可以保证inode不会发生变化,也不会变成NULL。
此时,内核的做法是把这个dentry从hashtable拿出来,变成一个孤魂野鬼。
这样后续用户从哈希表查找的时候,会找不到,然后产生一个negative dentry。
d_delete代码如下:
void d_delete(struct dentry * dentry)
{
struct inode *inode = dentry->d_inode;
spin_lock(&inode->i_lock);
spin_lock(&dentry->d_lock);
/*
* Are we the only user?
*/
if (dentry->d_lockref.count == 1) {
dentry->d_flags &= ~DCACHE_CANT_MOUNT;
dentry_unlink_inode(dentry);
} else {
__d_drop(dentry);
spin_unlock(&dentry->d_lock);
spin_unlock(&inode->i_lock);
}
}
EXPORT_SYMBOL(d_delete);__d_drop把dentry从hashtable中移除,并且invalidate d_seq,它需要上d_lock锁。
static void ___d_drop(struct dentry *dentry)
{
struct hlist_bl_head *b;
/*
* Hashed dentries are normally on the dentry hashtable,
* with the exception of those newly allocated by
* d_obtain_root, which are always IS_ROOT:
*/
if (unlikely(IS_ROOT(dentry)))
b = &dentry->d_sb->s_roots;
else
b = d_hash(dentry->d_name.hash);
hlist_bl_lock(b);
__hlist_bl_del(&dentry->d_hash);
hlist_bl_unlock(b);
}
void __d_drop(struct dentry *dentry)
{
if (!d_unhashed(dentry)) {
___d_drop(dentry);
dentry->d_hash.pprev = NULL;
write_seqcount_invalidate(&dentry->d_seq);
}
}
EXPORT_SYMBOL(__d_drop);dentry_unlink_inode()解除inode和dentry的关联关系。
需要先上i_lock锁,在上d_lock锁。
static void dentry_unlink_inode(struct dentry * dentry)
__releases(dentry->d_lock)
__releases(dentry->d_inode->i_lock)
{
struct inode *inode = dentry->d_inode;
raw_write_seqcount_begin(&dentry->d_seq);
__d_clear_type_and_inode(dentry);
hlist_del_init(&dentry->d_u.d_alias);
raw_write_seqcount_end(&dentry->d_seq);
spin_unlock(&dentry->d_lock);
spin_unlock(&inode->i_lock);
if (!inode->i_nlink)
fsnotify_inoderemove(inode);
if (dentry->d_op && dentry->d_op->d_iput)
dentry->d_op->d_iput(dentry, inode);
else
iput(inode);
}因为涉及更新d_flags。且并发者为RCU Lookup,需要上seq写锁,
调用__d_clear_type_and_inode()将d_flags中类型相关的位全部清空,也就是变成DCACHE_MISS_TYPE。并且将d_inode设置为NULL。
将dentry从inode的i_dentry列表中移除。
释放锁后,执行iput()释放inode的引用计数。
static inline void __d_clear_type_and_inode(struct dentry *dentry)
{
unsigned flags = READ_ONCE(dentry->d_flags);
printk("------------ dentry %px %s clear inode\n", dentry, dentry->d_name.name);
flags &= ~DCACHE_ENTRY_TYPE;
WRITE_ONCE(dentry->d_flags, flags);
dentry->d_inode = NULL;
/*
* The negative counter only tracks dentries on the LRU. Don't inc if
* d_lru is on another list.
*/
if ((flags & (DCACHE_LRU_LIST|DCACHE_SHRINK_LIST)) == DCACHE_LRU_LIST)
this_cpu_inc(nr_dentry_negative);
}总结:
有两种场景会生成negative dentry。
1 lookup不存在的文件
2 删除一个没有被任何人引用的文件,直接从positive变成negative。