ILD

negative dentry
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2026-6-1 站点:Inside Linux Development

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。


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.