[Linux]学习笔记系列 -- [fs]dcache


title: dcache

categories:

  • linux
  • fs
    tags:
  • linux
  • fs
    abbrlink: a5c27015
    date: 2025-10-03 09:01:49

https://github.com/wdfk-prog/linux-study

文章目录

fs/dcache.c 目录项缓存(Directory Entry Cache) VFS路径查找加速器

历史与背景

这项技术是为了解决什么特定问题而诞生的?

这项技术以及它所实现的目录项缓存(dcache),是为了解决Linux虚拟文件系统(VFS)中最核心的性能瓶颈之一:路径名到inode的解析过程

  • 消除磁盘I/O :在一个典型的文件系统中,解析一个路径如/home/user/file.txt需要一系列的磁盘读取操作。首先读取根目录/的内容找到home,然后读取home目录的内容找到user,以此类推,直到最后找到file.txt。每一次目录读取都是一次缓慢的磁盘I/O。如果每次open()stat()系统调用都执行这个过程,系统性能将无法接受。
  • 提供快速路径查找:dcache在内存中缓存了目录项(dentry)的树状结构,它直接映射了文件系统的目录层次。当内核需要解析一个路径时,它首先在dcache中查找。如果路径的所有组件都在缓存中,整个解析过程就可以在内存中以极高的速度完成,完全无需访问磁盘。
  • 缓存负面结果(Negative Lookups) :dcache不仅缓存了存在的路径,还会缓存不存在 的路径查询结果。例如,如果一个程序频繁尝试打开一个不存在的文件/tmp/lockfile,第一次查询会在磁盘上确认其不存在,dcache会将这个"负面"结果缓存起来。后续的查询会直接从dcache得知该文件不存在,避免了无谓的磁盘访问。
它的发展经历了哪些重要的里程碑或版本迭代?

dcache是内核中最古老、最核心的数据结构之一,它的演进主要是为了提升在多核系统上的扩展性(Scalability)。

  • 基本哈希表:dcache最初是一个简单的、由单个锁保护的哈希表。这在单核或双核系统上工作良好,但在多核系统上,这个锁成为了严重的性能瓶颈。
  • RCU(Read-Copy-Update)的引入 :这是dcache发展史上最重要的里程碑。内核开发者利用RCU技术重构了dcache的查找路径。这使得路径查找(绝大多数操作)可以无锁进行,多个CPU核心可以同时并发地在dcache中进行查找,而不会相互阻塞。只有在修改dcache(如创建、删除、重命名文件)时才需要获取锁。这极大地提升了dcache在多核环境下的性能。
  • 重命名操作的优化rename操作是dcache中最复杂的操作之一,因为它涉及到在树状结构中移动整个子树。为了在不长时间锁定整个dcache的情况下安全地执行此操作,内核引入了序列锁(seqlocks)和d_seq字段,允许读者(查找者)检测到自己是否在一次不完整的rename操作中途进行查找,并在必要时重试。
目前该技术的社区活跃度和主流应用情况如何?

dcache是Linux VFS的心脏,其重要性无与伦比。

  • 社区活跃度:dcache的代码极其稳定,但作为内核性能的关键,它仍然是内核开发者持续关注和进行微调的对象。任何对VFS性能的重大改进都可能涉及对dcache的调整。
  • 主流应用 :dcache是所有 文件系统操作的基础。任何涉及文件路径的系统调用,如open(), stat(), chmod(), unlink(), ls命令等,都必须经过dcache。它的性能直接决定了整个系统的文件I/O性能。

核心原理与设计

它的核心工作原理是什么?

dcache是一个结合了哈希表树状结构的复杂内存缓存。

  1. dentry对象 :dcache的核心数据结构是struct dentry(目录项)。一个dentry对象代表路径中的一个组成部分(如home),它包含了这个组成部分的名称,并有一个指针指向其父目录的dentry,以及一个指针指向其对应的inode(如果路径存在)。
  2. 树状结构 :通过d_parent指针,所有的dentry对象在内存中构成了一个树状结构,这个树精确地反映了磁盘上文件系统的目录层次。这使得路径解析可以沿着树的分支在内存中快速进行。
  3. 哈希表 :为了快速在某个目录中查找一个特定的子项(例如在代表/home的dentry下查找user),dcache使用了一个全局的哈希表。通过一个哈希函数d_hash(parent_dentry, child_name)可以快速定位到可能的dentry对象,避免了线性扫描目录下的所有子项。
  4. 状态管理 :dentry有几种关键状态:
    • In-use (Positive):dentry被一个或多个进程使用(引用计数大于0),并且它指向一个有效的inode。
    • Unused (Positive):dentry是有效的(指向一个inode),但当前没有进程在使用它(引用计数为0)。它位于LRU(最近最少使用)列表中,当内存不足时可以被回收。
    • Negative :dentry不指向任何inode(d_inode为NULL)。它代表一个不存在的路径。Negative dentry也会被缓存起来,以加速对不存在文件的重复查找。
  5. 内存回收(Shrinking) :当系统内存压力增大时,内核的内存回收机制会调用dcache的"收缩器"(shrinker)。收缩器会扫描dcache的LRU列表,释放那些"Unused"状态的dentry对象,从而回收内存。可以通过/proc/sys/vm/vfs_cache_pressure来调整收缩器的积极程度。
它的主要优势体现在哪些方面?
  • 极高的性能:将绝大多数路径查找操作从磁盘I/O转换为了内存访问。
  • 通用性:作为VFS的一部分,dcache对所有文件系统(ext4, XFS, NFS, tmpfs等)都有效。
  • 可扩展性:基于RCU的无锁读取路径,使得其在拥有大量CPU核心的现代服务器上也能高效工作。
  • 效率:缓存负面查询结果,避免了对不存在文件的重复、昂贵的磁盘检查。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
  • 内存消耗:这是dcache最主要的"成本"。在一个拥有数千万甚至上亿个文件的系统上,dcache可能会消耗GB级别的内存。如果内存配置不当,dcache本身可能成为内存压力的来源。
  • 复杂性:dcache的内部实现,特别是其并发控制和与内存管理的交互,是内核中最复杂的部分之一。不正确的交互很容易导致系统死锁或崩溃。
  • 缓存一致性(针对网络文件系统):对于NFS等网络文件系统,dcache中的内容可能与服务器上的实际内容存在短暂的不一致。NFS驱动需要额外的机制来确保缓存的有效性或在必要时使其失效。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

dcache是内核的底层机制,不是一个可选方案,而是所有文件路径操作的必经之路。它的好处在以下场景中体现得尤为明显:

  • Web服务器 :一个繁忙的Web服务器需要为每个请求频繁地stat()open()相同的静态文件(如图片、CSS、JS文件)。dcache使得这些重复的操作几乎没有I/O开销。
  • 软件编译:在编译大型项目时,编译器会反复打开相同的头文件。这些头文件的路径会被dcache牢牢记住,极大地加速了编译过程。
  • 任何大量使用小文件的应用:例如,邮件服务器(Maildir格式)、某些数据库或版本控制系统(如Git的早期对象存储)。dcache对于这类工作负载至关重要。
是否有不推荐使用该技术的场景?为什么?

不存在不使用dcache的场景。但是,在某些特定工作负载下,其默认行为可能不是最优的:

  • 一次性大规模文件扫描 :例如,一个备份程序或find /命令,它会遍历文件系统中的每一个文件,但可能再也不会访问它们。这种工作负载会用大量"一次性"的dentry填满dcache,可能会将之前缓存的热点数据(如常用程序和库的路径)挤出缓存,造成性能下降。这种现象称为"缓存污染"。在这种情况下,可以通过echo 2 > /proc/sys/vm/drop_caches手动清理dcache(仅限非生产环境调试!),或者期待内核的LRU机制能尽快淘汰这些冷数据。

对比分析

请将其 与 其他相似技术 进行详细对比。

对比一:dcache vs. Page Cache (页面缓存)

这是内核中最重要但也最容易混淆的一对缓存。它们协同工作,但职责完全不同。

特性 dcache (目录项缓存) Page Cache (页面缓存)
缓存对象 文件系统元数据:文件名、目录结构、路径名与inode的映射关系。 文件数据:文件实际的内容。
核心数据结构 struct dentry struct page
解决的问题 路径查找性能:"我如何找到文件?" 文件读写性能:"文件的内容是什么?"
工作流程 open("/path/to/file")时,内核在dcache中查找路径,最终找到文件的inode。 read()write()一个已打开的文件时,内核在Page Cache中查找或缓存文件的内容页。
关系 它们是上下游关系。必须先通过dcache找到一个文件的inode,然后才能通过该inode的地址空间(address_space)去访问其在Page Cache中的数据。

对比二:dcache vs. Inode Cache (inode缓存)

特性 dcache Inode Cache
缓存对象 文件名和目录层次结构。 文件的元数据属性。
核心数据结构 struct dentry struct inode
包含的信息 文件名、父子关系指针、指向inode的指针。 文件模式(权限)、所有者、大小、时间戳、数据块位置指针等。
关系 多对一。多个dentry可以指向同一个inode(例如,通过硬链接)。dcache是通往inode的"路标"。 inode代表了文件的本质。inode缓存在内存中,避免了每次stat()都要从磁盘读取inode信息。
生命周期 dentry是易失的,当内存不足时可以被回收。 只要一个dentry或一个打开的文件引用了一个inode,该inode就会一直存在于inode缓存中。

include/linux/dcache.h

dget 获取一个 dentry 的引用

c 复制代码
/**
* dget - 获取一个 dentry 的引用  
* @dentry: 要获取引用的 dentry  

* 给定一个 dentry 或 %NULL 指针,如果合适则增加引用计数并返回该 dentry。  
  当 dentry 有引用时不会被销毁。相反,没有引用的 dentry 可能由于多种原因消失,  
  首先是内存压力。换句话说,该原语用于克隆现有引用;在引用计数为零的情况下使用它是一个错误。  

* 注意:如果 @dentry->d_lock 被持有,它将会自旋。从避免死锁的角度来看,  
  它等同于 spin_lock()/增加引用计数/spin_unlock(),因此在 @dentry->d_lock 下调用它总是一个错误;  
  在其任何后代的 ->d_lock 下调用它也是一个错误。  
 */
static inline struct dentry *dget(struct dentry *dentry)
{
	if (dentry)
		lockref_get(&dentry->d_lockref);
	return dentry;
}

fs/dcache.c

dcache_init_early 目录项缓存初始化

c 复制代码
static struct hlist_bl_head *dentry_hashtable __ro_after_init __used;

static void __init dcache_init_early(void)
{
	/* 如果哈希分布在 NUMA 节点之间,请推迟哈希分配,直到 vmalloc 空间可用。
	 */
	if (hashdist)
		return;

	dentry_hashtable =
		alloc_large_system_hash("Dentry cache",
					sizeof(struct hlist_bl_head),
					dhash_entries,
					13,
					HASH_EARLY | HASH_ZERO,
					&d_hash_shift,
					NULL,
					0,
					0);
	// = 32 - 12 = 20
	d_hash_shift = 32 - d_hash_shift;
	/* do { } while (0) */
	runtime_const_init(shift, d_hash_shift);
	runtime_const_init(ptr, dentry_hashtable);
}

dcache_init 目录项缓存初始化

c 复制代码
/* Only NUMA needs hash distribution. 64bit NUMA architectures have
 * sufficient vmalloc space.
 */
#ifdef CONFIG_NUMA
#define HASHDIST_DEFAULT IS_ENABLED(CONFIG_64BIT)
extern int hashdist;		/* Distribute hashes across NUMA nodes? */
#else
#define hashdist (0)
#endif

static void __init dcache_init(void)
{
	/*
	 * 可以像列表一样为稳定状态添加构造函数,但由于 dcache 的缓存性质,这可能不值得。
	 */
	/* 创建目录项缓存(dentry_cache),用于存储目录项(dentry)
		SLAB_RECLAIM_ACCOUNT 标志允许缓存被回收以节省内存。
		SLAB_PANIC 确保在分配失败时触发内核错误,避免系统进入不稳定状态。
		SLAB_ACCOUNT 用于内存使用的统计和控制 */
	dentry_cache = KMEM_CACHE_USERCOPY(dentry,
		SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_ACCOUNT,
		d_shortname.string);

	/* Hash may have been set up in dcache_init_early */
	if (!hashdist)
		return;

	dentry_hashtable =
		alloc_large_system_hash("Dentry cache",
					sizeof(struct hlist_bl_head),
					dhash_entries,
					13,
					HASH_ZERO,
					&d_hash_shift,
					NULL,
					0,
					0);
	d_hash_shift = 32 - d_hash_shift;

	runtime_const_init(shift, d_hash_shift);
	runtime_const_init(ptr, dentry_hashtable);
}

vfs_caches_init_early 虚拟文件系统缓存初始化

c 复制代码
void __init vfs_caches_init_early(void)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(in_lookup_hashtable); i++)
		INIT_HLIST_BL_HEAD(&in_lookup_hashtable[i]);

	dcache_init_early();
	inode_init_early();
}

vfs_caches_init 设置虚拟文件系统(VFS)相关的缓存和数据结构

c 复制代码
void __init vfs_caches_init(void)
{
	/*  创建一个名称缓存(names_cache),用于存储文件路径名 
		SLAB_HWCACHE_ALIGN 确保缓存对齐以优化硬件性能,
		SLAB_PANIC 确保在分配失败时触发内核错误*/
	names_cachep = kmem_cache_create_usercopy("names_cache", PATH_MAX, 0,
			SLAB_HWCACHE_ALIGN|SLAB_PANIC, 0, PATH_MAX, NULL);
	/* 初始化目录缓存(dcache),用于管理目录项(dentry) */
	dcache_init();
	/* 初始化索引节点(inode)缓存 */
	inode_init();
	/* 初始化文件表,用于管理打开的文件 */
	files_init();
	/* 置系统允许打开的最大文件数 */
	files_maxfiles_init();
	/* 初始化挂载点管理,用于处理文件系统的挂载和卸载操作 */
	mnt_init();
	/* 初始化块设备缓存,用于管理块设备的缓存和操作 */
	bdev_cache_init();
	/* 初始化字符设备管理,用于处理字符设备的注册和操作 */
	chrdev_init();
}

VFS Dentry分配:文件路径组件的内存对象工厂

本代码片段是Linux VFS层中负责创建dentry(目录项)的核心工厂函数。其主要功能是从内核的slab缓存中分配一个新的struct dentry对象,并对其进行全面的初始化。Dentry是VFS中用于表示一个路径名组件(即一个文件名或目录名)的内存对象,是dcache(目录项缓存)的基本单元。此代码提供了多个API变体(d_alloc, d_alloc_anon等),以满足不同场景(如创建普通文件、根目录、匿名对象等)对dentry创建的需求。

实现原理分析

该实现的原理是提供一个高度优化的、集中的内部函数__d_alloc来处理所有dentry的共性初始化,然后通过多个简单的包装函数来处理不同用例的特性。

  1. 核心分配与初始化 (__d_alloc):

    • 内存分配 : 它不使用通用的kmalloc,而是从一个专门的slab缓存dentry_cache中分配内存。kmem_cache_alloc_lru的使表明它与dcache的LRU(最近最少使用)回收机制紧密集成,有利于高效的内存管理和回收。
    • 名称管理优化 : 这是一个关键的性能优化。
      • 短名称 (Inline) : 如果dentry的名称长度小于DNAME_INLINE_LEN,名称字符串会直接存储在struct dentry对象内部的d_shortname数组中。
      • 长名称 (External) : 如果名称过长,它会额外分配一个external_name结构体来存储,并在dentry中只保存一个指针。这种设计避免了为所有dentry都分配最大长度的名称缓冲区,从而在平均情况下节省了大量内存。
    • 并发安全 : 在设置d_name.name指针时,它使用了smp_store_release。这是一个带有内存屏障的写操作,确保了名称字符串的memcpy和NUL终止符的写入,对于使用RCU(Read-Copy-Update)进行无锁路径遍历的其他CPU来说是完全可见的。这是保证dcache并发安全性的关键细节。
    • 字段初始化 : 它负责对dentry的所有字段进行初始化,包括引用计数(lockref)、序列计数器(seqcount)、父子关系(初始时d_parent指向自身)、所属文件系统(d_sb)以及各种链表头。
    • 文件系统钩子 : 它提供了d_op->d_init回调,允许底层具体的文件系统(如ext4, tmpfs)在dentry分配后立即对其进行特定的初始化。
  2. 包装函数 (Wrappers):

    • d_alloc: 这是最常用的API。它调用__d_alloc创建dentry后,会锁定父dentry,然后将新创建的dentry链接到父dentry的d_children链表中,并正确设置其d_parent指针,从而将其"嫁接"到VFS的dentry树中。
    • d_alloc_anon: 用于创建匿名dentry,如文件系统的根dentry(/),它没有父节点和名称。
    • d_alloc_pseudo: 用于为"无查找"的文件系统(如管道pipefs、套接字sockfs)创建dentry。这些dentry被标记为DCACHE_NORCU,意味着它们的生命周期不通过RCU管理,可以在最后一个引用被释放后立即被销毁,这对于这些临时的内核对象来说更高效。

代码分析

c 复制代码
// __d_alloc: 分配一个dcache条目的内部核心函数。
// @sb: dentry将属于的文件系统。
// @name: 待分配dentry的名字(qstr结构)。
static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
{
	struct dentry *dentry;
	char *dname;
	int err;

	// 从dentry的专用slab缓存中分配一个对象,并将其与超级块的LRU关联。
	dentry = kmem_cache_alloc_lru(dentry_cache, &sb->s_dentry_lru,
				      GFP_KERNEL);
	if (!dentry)
		return NULL;

	// 保证内联名称缓冲区总是以NUL结尾,确保字符串操作的安全性。
	dentry->d_shortname.string[DNAME_INLINE_LEN-1] = 0;
	if (unlikely(!name)) { // 处理匿名dentry的情况
		name = &slash_name; // 默认为 "/"
		dname = dentry->d_shortname.string;
	} else if (name->len > DNAME_INLINE_LEN-1) { // 处理长名称
		size_t size = offsetof(struct external_name, name[1]);
		// 动态分配一个外部名称结构体来存储长名称。
		struct external_name *p = kmalloc(size + name->len,
						  GFP_KERNEL_ACCOUNT |
						  __GFP_RECLAIMABLE);
		if (!p) {
			kmem_cache_free(dentry_cache, dentry); 
			return NULL;
		}
		atomic_set(&p->count, 1);
		dname = p->name;
	} else  { // 处理短名称,使用内联缓冲区。
		dname = dentry->d_shortname.string;
	}	

	dentry->d_name.len = name->len;
	dentry->d_name.hash = name->hash;
	// 将名称字符串拷贝到目标缓冲区。
	memcpy(dname, name->name, name->len);
	dname[name->len] = 0; // 确保NUL结尾。

	// 使用带有release内存屏障的存储操作,确保名称内容对其他CPU可见。
	smp_store_release(&dentry->d_name.name, dname);

	// 初始化dentry的各个字段。
	dentry->d_flags = 0;
	lockref_init(&dentry->d_lockref); // 初始化锁和引用计数。
	seqcount_spinlock_init(&dentry->d_seq, &dentry->d_lock);
	dentry->d_inode = NULL; // 新dentry是negative的,没有关联inode。
	dentry->d_parent = dentry; // 初始时,parent指向自身。
	dentry->d_sb = sb; // 关联到超级块。
	dentry->d_op = sb->__s_d_op; // 继承超级块的默认dentry操作。
	dentry->d_flags = sb->s_d_flags;
	dentry->d_fsdata = NULL;
	// 初始化所有链表节点。
	INIT_HLIST_BL_NODE(&dentry->d_hash);
	INIT_LIST_HEAD(&dentry->d_lru);
	INIT_HLIST_HEAD(&dentry->d_children);
	INIT_HLIST_NODE(&dentry->d_u.d_alias);
	INIT_HLIST_NODE(&dentry->d_sib);

	// 如果文件系统提供了d_init回调,则调用它。
	if (dentry->d_op && dentry->d_op->d_init) {
		err = dentry->d_op->d_init(dentry);
		if (err) { // 如果回调失败,则清理并返回NULL。
			if (dname_external(dentry))
				kfree(external_name(dentry));
			kmem_cache_free(dentry_cache, dentry);
			return NULL;
		}
	}

	// 增加全局dentry计数器。
	this_cpu_inc(nr_dentry);

	return dentry;
}

// d_alloc: 分配一个dcache条目(通用API)。
// @parent: 要分配条目的父dentry。
// @name: 名字的qstr。
struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
{
	struct dentry *dentry = __d_alloc(parent->d_sb, name);
	if (!dentry)
		return NULL;
	spin_lock(&parent->d_lock);
	// 将新dentry的父指针指向父dentry (dget_dlock会增加引用计数)。
	dentry->d_parent = dget_dlock(parent);
	// 将新dentry添加到父dentry的子节点链表中。
	hlist_add_head(&dentry->d_sib, &parent->d_children);
	spin_unlock(&parent->d_lock);

	return dentry;
}
EXPORT_SYMBOL(d_alloc);

// d_alloc_anon: 分配一个匿名的dentry。
struct dentry *d_alloc_anon(struct super_block *sb)
{
	return __d_alloc(sb, NULL);
}
EXPORT_SYMBOL(d_alloc_anon);

// d_alloc_cursor: 分配一个用于遍历的"游标"dentry。
struct dentry *d_alloc_cursor(struct dentry * parent)
{
	struct dentry *dentry = d_alloc_anon(parent->d_sb);
	if (dentry) {
		dentry->d_flags |= DCACHE_DENTRY_CURSOR;
		dentry->d_parent = dget(parent);
	}
	return dentry;
}

// d_alloc_pseudo: 为无查找文件系统分配一个dentry。
struct dentry *d_alloc_pseudo(struct super_block *sb, const struct qstr *name)
{
	static const struct dentry_operations anon_ops = {
		.d_dname = simple_dname
	};
	struct dentry *dentry = __d_alloc(sb, name);
	if (likely(dentry)) {
		// 标记此dentry不受RCU延迟释放的管理。
		dentry->d_flags |= DCACHE_NORCU;
		if (!dentry->d_op)
			dentry->d_op = &anon_ops;
	}
	return dentry;
}

// d_alloc_name: 一个便利的包装函数,通过C字符串名字来分配dentry。
struct dentry *d_alloc_name(struct dentry *parent, const char *name)
{
	struct qstr q;

	q.name = name;
	// 计算名字的哈希和长度。
	q.hash_len = hashlen_string(parent, name);
	return d_alloc(parent, &q);
}
EXPORT_SYMBOL(d_alloc_name);

VFS Dentry实例化:将文件名与文件数据建立链接

本代码片段是Linux虚拟文件系统(VFS)中一个极为核心的部分,负责dentry(目录项)的"实例化"过程。其核心功能是将一个代表文件或目录名、但尚未关联到具体文件数据的dentry,与一个代表文件元数据和内容的inode(索引节点)进行链接。这个操作是所有文件系统查找、创建和访问操作的基础,它将抽象的文件路径名与底层的、持久化的文件对象联系起来,是dcache(目录项缓存)能够工作的关键所在。

实现原理分析

该机制的实现围绕着将struct inode指针安全地赋值给struct dentry的核心操作展开,并辅以必要的锁、状态转换和安全检查。

  1. 确定Dentry类型 (d_flags_for_inode) : 在进行链接之前,此辅助函数会检查inode的元数据(i_mode, i_op),以确定它代表的是目录、普通文件、符号链接还是特殊文件。它返回一组DCACHE_*_TYPE标志,这些标志将被存储在dentry中,用于优化后续的路径查找等操作。

  2. 核心链接操作 (__d_instantiate): 这是实际执行链接的内部函数。它假定调用者已经持有了必要的锁。

    • 它首先将dentry添加到inode的i_dentry哈希链表中。这个链表被称为"别名(alias)"链表,因为一个inode可以有多个dentry指向它(即硬链接)。
    • 然后,它原子地将inode指针和从d_flags_for_inode获得的类型标志设置到dentry中。这个过程使用seqcount(序列计数器)来保护,允许无锁的读者(如路径遍历代码)检测到更新并进行重试,从而获得很高性能。
    • 如果dentry之前是一个"negative" dentry(代表一次失败的查找),此操作会将其转变为"positive" dentry,并更新相应的全局计数器。
  3. 公共API (d_instantiated_instantiate_new):

    • d_instantiate: 这是通用的、导出的API。它负责处理所有外部锁定(inode->i_lock)和安全钩子(security_d_instantiate),然后调用__d_instantiate来执行核心工作。它用于将一个已经完全初始化的inode附加到一个dentry上。
    • d_instantiate_new: 这是一个专门的变体,用于处理新创建 的inode。除了执行d_instantiate的所有操作外,它还负责清除inode的I_NEW状态标志,并通过内存屏障和唤醒队列,通知任何正在等待此inode创建完成的进程。
  4. 根Dentry创建 (d_make_root) : 这是一个高层的辅助函数,专门用于文件系统挂载过程。它分配一个匿名的、无父目录的dentry,然后调用d_instantiate将其与文件系统的根inode进行链接,从而创建出整个文件系统树的起点(/)。

代码分析

c 复制代码
// d_flags_for_inode: 根据inode的类型确定dentry应有的标志。
static unsigned d_flags_for_inode(struct inode *inode)
{
	unsigned add_flags = DCACHE_REGULAR_TYPE; // 默认为普通文件类型。

	if (!inode)
		return DCACHE_MISS_TYPE; // 如果没有inode,这是一个失败的查找。

	if (S_ISDIR(inode->i_mode)) { // 如果是目录
		add_flags = DCACHE_DIRECTORY_TYPE;
		// 检查并缓存inode是否支持自定义lookup操作,用于优化。
		if (unlikely(!(inode->i_opflags & IOP_LOOKUP))) {
			if (unlikely(!inode->i_op->lookup))
				add_flags = DCACHE_AUTODIR_TYPE;
			else
				inode->i_opflags |= IOP_LOOKUP;
		}
		goto type_determined;
	}

	if (unlikely(!(inode->i_opflags & IOP_NOFOLLOW))) {
		if (unlikely(inode->i_op->get_link)) { // 如果是符号链接
			add_flags = DCACHE_SYMLINK_TYPE;
			goto type_determined;
		}
		inode->i_opflags |= IOP_NOFOLLOW;
	}

	if (unlikely(!S_ISREG(inode->i_mode))) // 如果不是普通文件(也不是目录或符号链接)
		add_flags = DCACHE_SPECIAL_TYPE; // 则是特殊文件(设备文件、管道等)。

type_determined:
	if (unlikely(IS_AUTOMOUNT(inode))) // 检查是否为自动挂载点
		add_flags |= DCACHE_NEED_AUTOMOUNT;
	return add_flags;
}

// __d_instantiate: 实际将inode链接到dentry的内部函数(无锁)。
static void __d_instantiate(struct dentry *dentry, struct inode *inode)
{
	unsigned add_flags = d_flags_for_inode(inode);
	WARN_ON(d_in_lookup(dentry)); // 警告:不应在查找过程中实例化dentry。

	spin_lock(&dentry->d_lock);
	// 如果dentry之前是negative dentry且在LRU链表上,则递减全局计数器。
	if ((dentry->d_flags &
	     (DCACHE_LRU_LIST|DCACHE_SHRINK_LIST)) == DCACHE_LRU_LIST)
		this_cpu_dec(nr_dentry_negative);
	// 将dentry添加到inode的别名哈希链表中。
	hlist_add_head(&dentry->d_u.d_alias, &inode->i_dentry);
	// 使用序列计数器保护对inode指针和类型标志的更新。
	raw_write_seqcount_begin(&dentry->d_seq);
	__d_set_inode_and_type(dentry, inode, add_flags);
	raw_write_seqcount_end(&dentry->d_seq);
	fsnotify_update_flags(dentry); // 通知文件系统事件子系统。
	spin_unlock(&dentry->d_lock);
}

// d_instantiate: 为dentry填充inode信息(公共API)。
// @entry: 要完成的dentry。
// @inode: 要附加到此dentry的inode。
void d_instantiate(struct dentry *entry, struct inode * inode)
{
	// BUG_ON检查,确保此dentry尚未被链接。
	BUG_ON(!hlist_unhashed(&entry->d_u.d_alias));
	if (inode) {
		security_d_instantiate(entry, inode); // 调用LSM安全钩子。
		spin_lock(&inode->i_lock); // 锁定inode。
		__d_instantiate(entry, inode); // 调用内部函数执行链接。
		spin_unlock(&inode->i_lock); // 解锁inode。
	}
}
EXPORT_SYMBOL(d_instantiate);

// d_instantiate_new: 专用于实例化新创建的inode。
void d_instantiate_new(struct dentry *entry, struct inode *inode)
{
	BUG_ON(!hlist_unhashed(&entry->d_u.d_alias));
	BUG_ON(!inode);
	lockdep_annotate_inode_mutex_key(inode); // 为锁调试器提供注解。
	security_d_instantiate(entry, inode);
	spin_lock(&inode->i_lock);
	__d_instantiate(entry, inode);
	WARN_ON(!(inode->i_state & I_NEW)); // 警告:inode应处于I_NEW状态。
	// 清除I_NEW和I_CREATING状态,表示inode已完全可用。
	inode->i_state &= ~I_NEW & ~I_CREATING;
	/*
	 * 与prepare_to_wait_event()中的屏障配对,确保等待者能看到状态位
	 * 被清除,或者唤醒者能看到等待队列中的任务。
	 */
	smp_mb(); // 内存屏障
	// 唤醒所有等待此inode创建完成的进程。
	inode_wake_up_bit(inode, __I_NEW);
	spin_unlock(&inode->i_lock);
}
EXPORT_SYMBOL(d_instantiate_new);

// d_make_root: 为根inode创建一个根dentry。
struct dentry *d_make_root(struct inode *root_inode)
{
	struct dentry *res = NULL;

	if (root_inode) {
		// 分配一个匿名的dentry。
		res = d_alloc_anon(root_inode->i_sb);
		if (res)
			// 将其与根inode实例化。
			d_instantiate(res, root_inode);
		else
			// 如果分配失败,释放对根inode的引用。
			iput(root_inode);
	}
	return res;
}
EXPORT_SYMBOL(d_make_root);

目录缓存统计: 通过Sysctl暴露VFS缓存指标

本代码片段的核心功能是为Linux内核的目录条目缓存(dentry cache, dcache)创建一个监控和调优接口。它利用sysctl机制,在/proc/sys/fs//proc/sys/vm/目录下生成一系列文件,用于向用户空间暴露dcache的实时统计数据(如缓存条目总数、未使用数)和相关的内存回收策略参数(如vfs_cache_pressure)。这为系统管理员和性能分析工具提供了洞察VFS层性能和内存占用的关键途径。

实现原理分析

此功能的实现依赖于per-cpu计数器和sysctl框架,以一种高效且可扩展的方式收集和展示统计信息。

  1. Per-CPU计数器 (DEFINE_PER_CPU):

    • 为了在高并发的多核系统上高效地、无锁地更新dcache的统计信息,代码为nr_dentry(总数)、nr_dentry_unused(未使用数)和nr_dentry_negative("否定"缓存数)都定义了**每CPU(per-cpu)**的计数器。
    • 当内核在某个CPU上创建、释放或使用一个dentry时,它只会原子地更新当前CPU上的对应计数器。这种操作几乎没有锁竞争,性能极高。
  2. 数据汇总 (get_nr_dentry, etc.):

    • 当需要获取全局总数时(例如,当用户读取sysctl文件时),get_nr_dentry这样的辅助函数会被调用。
    • 这些函数通过for_each_possible_cpu循环遍历所有可能存在的CPU(而不是仅仅是在线的CPU,以避免处理CPU热插拔带来的复杂性),并将每个CPU上的计数值累加起来,得到一个全局的总和。
  3. Sysctl接口 (dentry-state):

    • dentry-state是一个只读的、多值的sysctl接口。它不像之前的例子那样只关联一个变量,而是关联一个dentry_stat_t结构体。
    • 它使用了一个自定义的proc handler proc_nr_dentry。当用户读取/proc/sys/fs/dentry-state时,这个函数被调用。
    • proc_nr_dentry实时 调用get_nr_dentry()get_nr_dentry_unused()等函数来汇总最新的统计数据,并将它们填充到dentry_stat结构体的相应字段中。
    • 最后,它调用proc_doulongvec_minmax,这个函数会将dentry_stat结构体中的多个long值格式化成一个由制表符分隔的ASCII字符串,并返回给用户。
  4. 内存回收调优 (vfs_cache_pressure):

    • 代码还在/proc/sys/vm/下注册了vfs_cache_pressure接口。这个参数是一个非常重要的内存管理调优参数。
    • 它控制着内核在面临内存压力时,回收dentry和inode缓存的"积极性"。值越高,内核越倾向于回收这些VFS缓存,为其他用途(如进程内存)释放页面;值越低,内核越倾向于保留VFS缓存以提高文件系统性能。
    • 这个参数的实现和暴露,使得管理员可以根据系统负载的特点(例如,是I/O密集型还是计算密集型)来微调内核的内存回收策略。

代码分析

c 复制代码
// 定义用于dentry统计的结构体。
struct dentry_stat_t {
	long nr_dentry;     // dentry总数
	long nr_unused;     // 未使用的dentry数
	long age_limit;     // 回收dentry的年龄限制(秒)
	long want_pages;    // 系统请求的页面数
	long nr_negative;   // 未使用的"否定"dentry数
	long dummy;         // 保留字段
};

// 为dentry总数定义一个per-cpu计数器。
static DEFINE_PER_CPU(long, nr_dentry);
// 为未使用的dentry数定义一个per-cpu计数器。
static DEFINE_PER_CPU(long, nr_dentry_unused);
// 为"否定"dentry数定义一个per-cpu计数器。
static DEFINE_PER_CPU(long, nr_dentry_negative);
// 控制是否缓存"否定"dentry的策略变量。
static int dentry_negative_policy;

// dentry统计结构体的静态实例,部分字段会被动态填充。
static struct dentry_stat_t dentry_stat = {
	.age_limit = 45, // 硬编码的年龄限制值。
};

// get_nr_dentry: 汇总所有CPU上的nr_dentry计数器。
static long get_nr_dentry(void)
{
	int i;
	long sum = 0;
	// 遍历所有可能的CPU,并将它们的计数值相加。
	for_each_possible_cpu(i)
		sum += per_cpu(nr_dentry, i);
	return sum < 0 ? 0 : sum;
}

// get_nr_dentry_unused: 汇总所有CPU上的nr_dentry_unused计数器。
static long get_nr_dentry_unused(void)
{
	int i;
	long sum = 0;
	for_each_possible_cpu(i)
		sum += per_cpu(nr_dentry_unused, i);
	return sum < 0 ? 0 : sum;
}

// get_nr_dentry_negative: 汇总所有CPU上的nr_dentry_negative计数器。
static long get_nr_dentry_negative(void)
{
	int i;
	long sum = 0;

	for_each_possible_cpu(i)
		sum += per_cpu(nr_dentry_negative, i);
	return sum < 0 ? 0 : sum;
}

// proc_nr_dentry: dentry-state文件的专用proc handler。
static int proc_nr_dentry(const struct ctl_table *table, int write, void *buffer,
			  size_t *lenp, loff_t *ppos)
{
	// 在每次读取时,实时汇总per-cpu计数器,并更新dentry_stat结构体。
	dentry_stat.nr_dentry = get_nr_dentry();
	dentry_stat.nr_unused = get_nr_dentry_unused();
	dentry_stat.nr_negative = get_nr_dentry_negative();
	// 调用标准处理函数,将整个结构体格式化为字符串并返回。
	return proc_doulongvec_minmax(table, write, buffer, lenp, ppos);
}

// 定义在 /proc/sys/fs/ 目录下的dcache相关sysctl条目。
static const struct ctl_table fs_dcache_sysctls[] = {
	{
		.procname	= "dentry-state",
		.data		= &dentry_stat,
		.maxlen		= 6*sizeof(long),
		.mode		= 0444, // 只读
		.proc_handler	= proc_nr_dentry,
	},
	{
		.procname	= "dentry-negative", // 文件名,控制是否缓存否定dentry
		.data		= &dentry_negative_policy,
		.maxlen		= sizeof(dentry_negative_policy),
		.mode		= 0644,
		.proc_handler	= proc_dointvec_minmax,
		.extra1		= SYSCTL_ZERO, // 允许值为0
		.extra2		= SYSCTL_ONE,  // 或1
	},
};

// 定义在 /proc/sys/vm/ 目录下的VFS缓存压力相关sysctl条目。
static const struct ctl_table vm_dcache_sysctls[] = {
	{
		.procname	= "vfs_cache_pressure", // VFS缓存回收压力
		.data		= &sysctl_vfs_cache_pressure,
		.maxlen		= sizeof(sysctl_vfs_cache_pressure),
		.mode		= 0644,
		.proc_handler	= proc_dointvec_minmax,
		.extra1		= SYSCTL_ZERO, // 最小值0
	},
	{
		.procname	= "vfs_cache_pressure_denom", // VFS缓存压力分母
		.data		= &sysctl_vfs_cache_pressure_denom,
		.maxlen		= sizeof(sysctl_vfs_cache_pressure_denom),
		.mode		= 0644,
		.proc_handler	= proc_dointvec_minmax,
		.extra1		= SYSCTL_ONE_HUNDRED, // 最小值100
	},
};

// init_fs_dcache_sysctls: 初始化函数,注册上述sysctl表。
static int __init init_fs_dcache_sysctls(void)
{
	register_sysctl_init("vm", vm_dcache_sysctls);
	register_sysctl_init("fs", fs_dcache_sysctls);
	return 0;
}
fs_initcall(init_fs_dcache_sysctls);
相关推荐
xrl20122 小时前
ruoyi-vue2集成flowable6.7.2后端篇
数据库·ruoyi·flowable·工作流集成
fufu03112 小时前
Linux环境下的C语言编程(四十三)
linux·c语言·算法
小智RE0-走在路上2 小时前
Python学习笔记(7)--集合,字典,数据容器总结
笔记·python·学习
java1234_小锋2 小时前
Redis到底支不支持事务啊?
java·数据库·redis
_F_y2 小时前
Linux:多线程
linux·运维·服务器
__lai2 小时前
iflow cli一键安装脚本运行了,也正常安装了,但是无法通过iflow命令进入软件。在termux安装iflow-cli AI工具
linux·人工智能·termux
呵呵哒( ̄▽ ̄)"2 小时前
专项智能练习(古代神话)
学习
Ha_To3 小时前
2025.12.18 NAT地址转换、PAT
linux·服务器·网络
爱吃番茄鼠骗3 小时前
Linux操作系统———I/O多路复用
linux