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


title: inode

categories:

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

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

文章目录

  • [fs/inode.c Inode管理 VFS中文件对象的抽象核心](#fs/inode.c Inode管理 VFS中文件对象的抽象核心)
  • include/linux/fs.h
    • [inode_wake_up_bit 唤醒等待 inode(文件系统中的索引节点)特定状态位(bit)的线程](#inode_wake_up_bit 唤醒等待 inode(文件系统中的索引节点)特定状态位(bit)的线程)
    • [alloc_inode_sb 用于分配 inode(文件系统中的索引节点)](#alloc_inode_sb 用于分配 inode(文件系统中的索引节点))
    • [inode_init_always 用于初始化 inode(文件系统中的索引节点)](#inode_init_always 用于初始化 inode(文件系统中的索引节点))
    • [inode_fsuid_set 使用调用者的 fsuid 初始化 inode 的 i_uid 字段](#inode_fsuid_set 使用调用者的 fsuid 初始化 inode 的 i_uid 字段)
  • fs/proc/inode.c
    • [proc_sys_invalidate_dcache 清除 proc 的目录缓存](#proc_sys_invalidate_dcache 清除 proc 的目录缓存)
  • fs/inode.c
    • [inode_init_early 初始化 inode 的早期阶段](#inode_init_early 初始化 inode 的早期阶段)
    • [__address_space_init_once 用于初始化地址空间(address space)的一次性设置](#__address_space_init_once 用于初始化地址空间(address space)的一次性设置)
    • [init_once 用于初始化 inode 的一次性设置](#init_once 用于初始化 inode 的一次性设置)
    • [inode_init 设置与索引节点(inode)相关的缓存和哈希表](#inode_init 设置与索引节点(inode)相关的缓存和哈希表)
    • [__iget 用于增加 inode 的引用计数](#__iget 用于增加 inode 的引用计数)
    • [igrab 用于增加 inode 的引用计数](#igrab 用于增加 inode 的引用计数)
    • [__inode_add_lru 用于将 inode 添加到 LRU(最近最少使用)列表中](#__inode_add_lru 用于将 inode 添加到 LRU(最近最少使用)列表中)
    • [inode_wait_for_lru_isolating 等待 inode(文件系统中的索引节点)完成 LRU(最近最少使用)隔离操作](#inode_wait_for_lru_isolating 等待 inode(文件系统中的索引节点)完成 LRU(最近最少使用)隔离操作)
    • [destroy_inode 用于销毁 inode(文件系统中的索引节点)](#destroy_inode 用于销毁 inode(文件系统中的索引节点))
    • [evict 释放传入的 inode(文件系统中的索引节点),并从其关联的所有列表中移除](#evict 释放传入的 inode(文件系统中的索引节点),并从其关联的所有列表中移除)
    • [iput_final 用于处理文件系统中 inode 的最后一个引用被释放的情况](#iput_final 用于处理文件系统中 inode 的最后一个引用被释放的情况)
    • [iput 用于减少 inode 的引用计数](#iput 用于减少 inode 的引用计数)
    • [inode_bit_waitqueue 用于获取 inode 的等待队列头](#inode_bit_waitqueue 用于获取 inode 的等待队列头)
    • [alloc_inode 获取一个 inode](#alloc_inode 获取一个 inode)
    • [inode_sb_list_add 将 inode 添加到超级块的 inode 列表中](#inode_sb_list_add 将 inode 添加到超级块的 inode 列表中)
    • [new_inode 获取一个inode](#new_inode 获取一个inode)
    • [inode_init_always_gfp 用于执行 inode 结构的初始化](#inode_init_always_gfp 用于执行 inode 结构的初始化)
    • [get_next_ino 获生成文件系统的下一个 inode 编号(inode number)](#get_next_ino 获生成文件系统的下一个 inode 编号(inode number))
    • [inode_init_owner 根据POSIX标准初始化新inode的uid、gid和mode](#inode_init_owner 根据POSIX标准初始化新inode的uid、gid和mode)
    • [VFS Inode时间戳管理:高精度与并发安全的更新机制](#VFS Inode时间戳管理:高精度与并发安全的更新机制)

fs/inode.c Inode管理 VFS中文件对象的抽象核心

历史与背景

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

这项技术以及其核心数据结构struct inode,是为了在Linux内核中实现一个**统一的文件系统接口(VFS)**而诞生的。它解决了在支持多种不同文件系统时面临的根本性挑战:

  • 抽象与统一 :不同的文件系统(如ext4, XFS, FAT, NFS)在磁盘上存储文件元数据(metadata)的方式千差万别。例如,ext4的磁盘inode结构与XFS的完全不同。为了让内核的上层代码(如系统调用实现)能够以一种统一的方式操作任何文件,而无需关心其底层文件系统类型,VFS需要一个通用的、在内存中的文件对象表示。struct inode就是这个抽象。
  • 性能(元数据缓存) :访问磁盘是极其缓慢的。如果每次需要读取文件元数据(如文件大小、权限、所有者等)时都必须从磁盘读取,系统性能将非常低下。fs/inode.c管理着一个全局的inode缓存,将最近使用的inode对象保存在内存中,极大地加速了对文件元数据的访问。
  • 状态管理 :一个文件在内存中有许多运行时的状态,这些状态在磁盘上是不存在的。例如,需要跟踪inode是否被修改过("脏"状态,I_DIRTY)、inode是否被锁定、有多少个内核组件正在引用它等。fs/inode.c负责管理struct inode的这些内存中的状态。
它的发展经历了哪些重要的里程碑或版本迭代?

inode的概念继承自早期的UNIX系统,而Linux中的inode管理实现则随着内核的演进而不断优化。

  • 从全局数组到哈希缓存:早期的内核可能使用简单的数组或链表来管理inode。现代内核则使用一个高效的、可扩展的哈希表来缓存和快速查找inode。
  • 与Slab分配器的集成 :为了高效地分配和释放大量的struct inode对象,inode管理与内核的Slab分配器紧密集成。每个文件系统都可以创建自己的inode缓存池(kmem_cache)。
  • 锁机制的演进 :随着多核(SMP)系统的普及,对inode的并发访问成为性能瓶yll颈。inode的锁机制经历了从单一全局锁到更细粒度的锁(如每个inode内部的自旋锁i_lock和互斥锁i_mutex)的演进,以提高并行性。
  • 脏inode的回写机制inode与页面回写(page-writeback)机制紧密集成。fs/inode.c负责管理一个脏inode列表,flusher线程会定期将这些脏inode的元数据写回到磁盘,以确保数据持久化。
目前该技术的社区活跃度和主流应用情况如何?

inode管理是VFS乃至整个Linux内核的基石,其重要性无可替代。

  • 社区活跃度fs/inode.c的代码是内核中最稳定、最核心的部分之一。相关的改动非常谨慎,通常是为了修复深层次的竞态条件、进行性能微调,或与内存管理、块设备层的新特性进行集成。
  • 主流应用 :任何与文件系统相关的操作都离不开inode。它是所有文件系统在VFS层面的统一代表。从打开文件(open)到读取属性(stat),再到修改权限(chmod),每一个操作都涉及在内核中查找、操作并最终释放对一个struct inode对象的引用。

核心原理与设计

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

fs/inode.c的核心是管理struct inode对象的生命周期和缓存。

  1. VFS Inode的定义struct inode是一个通用的内核数据结构,包含了所有文件系统都必须提供的标准信息,如:
    • i_mode:文件类型(普通文件、目录、链接等)和权限。
    • i_uid, i_gid:所有者和所属组。
    • i_size:文件大小(以字节为单位)。
    • i_atime, i_mtime, i_ctime:访问、修改和状态改变时间。
    • i_nlink硬链接计数,即有多少个文件名指向这个inode。这是磁盘上的持久计数值。
    • i_count引用计数 ,即在内存中有多少个内核组件正在使用这个struct inode对象。这是一个纯粹的内存状态。
    • i_op, i_fop:指向操作函数表的指针(inode_operations, file_operations),这是实现多态性、调用具体文件系统代码的关键。
    • i_mapping:指向address_space结构,将inode与它在Page Cache中的数据页关联起来。
  2. Inode缓存 :内核维护一个全局的inode哈希表。当需要访问一个文件时(例如,在dcache中查找到一个dentry后),内核会通过iget(superblock, inode_number)来获取对应的inode。iget会首先在哈希表中查找。如果找到,就增加其引用计数i_count并返回;如果没找到,它会调用文件系统的read_inode回调函数,从磁盘读取元数据,在内存中"填充"一个新的struct inode对象,并将其加入缓存。
  3. 生命周期管理
    • 获取 :每次需要使用inode时,都会增加i_count
    • 释放 :当一个组件使用完毕,它会调用iput(inode)来减少i_count
    • 销毁 :当i_count减到0时,意味着内存中不再有任何地方引用这个inode。此时,如果inode的硬链接数i_nlink也为0(意味着文件已被删除),内核就会将该inode标记为可回收,并最终调用文件系统的evict_inode回调来清理它,释放其占用的数据块。
  4. 与具体文件系统的交互 :VFS inode通过函数指针与底层文件系统交互。当上层代码对一个inode执行lookupcreate等操作时,VFS会通过inode->i_op->lookup(...)调用到具体文件系统(如ext4)提供的实现函数。
它的主要优势体现在哪些方面?
  • 抽象和解耦:为内核提供了一个稳定、统一的接口来操作文件,将通用逻辑与文件系统特定逻辑完全分离开。
  • 性能:通过缓存inode,避免了频繁的、昂贵的磁盘元数据读取操作。
  • 集中管理:提供了集中的生命周期、状态和锁管理,简化了文件系统的编写,并提高了整个系统的健壮性。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
  • 内存消耗:与dcache类似,在一个拥有海量文件的系统上,inode缓存也会消耗大量的内核内存。
  • 复杂性:inode的生命周期、状态转换以及与dcache、Page Cache和回写机制的复杂交互,使得这部分代码成为内核中最难理解和调试的部分之一。

使用场景

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

与dcache一样,inode不是一个可选方案,而是所有基于VFS的文件操作的强制性核心组件

  • stat系统调用 :当用户执行ls -lstat file时,内核找到文件的inode后,直接从内存中的struct inode对象读取文件大小、权限、所有者等信息并返回给用户,通常无需任何磁盘I/O。
  • chmod系统调用 :执行chmod 755 file时,内核找到inode,修改其i_mode字段,然后将该inode标记为"脏"。这个修改会由flusher线程在稍后异步地写回磁盘。
  • 硬链接 :执行ln file1 file2时,内核找到file1的inode,然后创建一个新的dentry(file2),将其指向同一个inode,并原子地将该inode的i_nlink计数加1。
是否有不推荐使用该技术的场景?为什么?

不存在。任何希望被Linux系统识别和操作的文件系统,都必须 实现与VFS inode模型的集成。绕过VFS和inode直接操作块设备的应用(如某些高性能数据库),实际上是在用户空间重新实现了一套自己的、功能类似的文件和元数据管理系统。

对比分析

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

对比一:inode vs. dentry (fs/dcache.c)

这是VFS中最重要的一对概念,理解它们的区别是理解VFS的关键。

特性 inode (索引节点) dentry (目录项)
代表对象 代表一个文件实体文件对象。是文件的元数据集合。 代表一个路径组件文件名。是连接路径和文件实体的"路标"。
包含信息 文件的属性:大小、权限、所有者、时间戳、数据块指针等。 文件的名称、指向父dentry的指针、指向inode的指针。
关系 一对多。一个inode可以被多个dentry引用(硬链接的情况)。 多对一。多个dentry可以指向同一个inode。
生命周期 只要i_nlink > 0(磁盘上存在)或i_count > 0(内存中被引用),inode就有效。 dentry是纯粹的内存缓存对象,即使对应的文件存在,其dentry也可能因内存压力被回收。
存在性 与磁盘上的物理实体一一对应。 可以是"负dentry",即缓存一个不存在的文件名查询结果。

对比二:VFS inode (struct inode) vs. On-Disk Inode (如 struct ext4_inode)

特性 VFS Inode (struct inode) On-Disk Inode (e.g., struct ext4_inode)
存在位置 内存中 持久化存储介质上(磁盘、SSD)
结构 通用、庞大。包含了所有文件系统通用的字段,以及大量运行时状态(如锁、链表指针、引用计数)。 特定、紧凑。只包含需要持久化存储的元数据,其布局由具体文件系统定义。
生命周期 动态创建和销毁,作为磁盘inode在内存中的一个缓存实例 持久存在,直到文件被删除且其空间被重用。
作用 为内核上层提供统一的操作接口 持久地记录文件的元数据。
关系 VFS Inode是On-Disk Inode在内存中的运行时表示 。文件系统的read_inode函数负责从磁盘读取On-Disk Inode并用其信息填充VFS Inode。

include/linux/fs.h

inode_wake_up_bit 唤醒等待 inode(文件系统中的索引节点)特定状态位(bit)的线程

c 复制代码
static inline void inode_wake_up_bit(struct inode *inode, u32 bit)
{
	/* 调用方负责正确的内存屏障. */
	wake_up_var(inode_state_wait_address(inode, bit));
}

alloc_inode_sb 用于分配 inode(文件系统中的索引节点)

c 复制代码
/*
 * 此代码必须用于分配文件系统特定的inode,以正确设置inode回收上下文。
*/
#define alloc_inode_sb(_sb, _cache, _gfp) kmem_cache_alloc_lru(_cache, &_sb->s_inode_lru, _gfp)

inode_init_always 用于初始化 inode(文件系统中的索引节点)

c 复制代码
static inline int inode_init_always(struct super_block *sb, struct inode *inode)
{
	return inode_init_always_gfp(sb, inode, GFP_NOFS);
}

inode_fsuid_set 使用调用者的 fsuid 初始化 inode 的 i_uid 字段

c 复制代码
/**
 * inode_fsuid_set - 使用调用者的 fsuid 初始化 inode 的 i_uid 字段
 * @inode: 要初始化的 inode
 * @idmap: 从中找到 inode 的挂载的 idmap
 *
 * 初始化 @inode 的 i_uid 字段。如果通过 idmapped 挂载找到/创建了 inode,则根据 @idmap 映射调用者的 fsuid。
 */
static inline void inode_fsuid_set(struct inode *inode,
				   struct mnt_idmap *idmap)
{
	inode->i_uid = mapped_fsuid(idmap, i_user_ns(inode));
}

fs/proc/inode.c

proc_sys_invalidate_dcache 清除 proc 的目录缓存

c 复制代码
void proc_invalidate_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock)
{
	struct hlist_node *node;
	struct super_block *old_sb = NULL;

	rcu_read_lock();
	while ((node = hlist_first_rcu(inodes))) {
		struct proc_inode *ei = hlist_entry(node, struct proc_inode, sibling_inodes);
		struct super_block *sb;
		struct inode *inode;

		spin_lock(lock);
		/* 删除节点 */
		hlist_del_init_rcu(&ei->sibling_inodes);
		spin_unlock(lock);

		inode = &ei->vfs_inode;
		sb = inode->i_sb;
		/* 增如果当前 super_block 不等于之前处理的 super_block,则尝试增加其活动计数 
		如果活动计数为 0(表示 super_block 已经被释放或正在释放),则返回 false,跳过当前 inode 的处理*/
		if ((sb != old_sb) && !atomic_inc_not_zero(&sb->s_active))
			continue;
		/* igrab 函数增加 inode 的引用计数,确保 inode 在后续操作中不会被释放 */
		inode = igrab(inode);
		rcu_read_unlock();
		/* 如果切换到新的 super_block,则释放之前的 super_block,并更新 old_sb */
		if (sb != old_sb) {
			if (old_sb)
				deactivate_super(old_sb);
			old_sb = sb;
		}
		if (unlikely(!inode)) {
			rcu_read_lock();
			continue;
		}

		/* 处理 inode 的目录项缓存 */
		if (S_ISDIR(inode->i_mode)) {
			/* 如果 inode 是目录类型,查找其目录项并使其失效 */
			struct dentry *dir = d_find_any_alias(inode);
			if (dir) {
				d_invalidate(dir);
				dput(dir);
			}
		} else {
			/* 如果 inode 是其他类型,查找所有与其关联的目录项并逐个使其失效 */
			struct dentry *dentry;
			while ((dentry = d_find_alias(inode))) {
				d_invalidate(dentry);
				dput(dentry);
			}
		}
		/* 释放 inode 的引用 */
		iput(inode);

		rcu_read_lock();
	}
	rcu_read_unlock();
	/* 如果最后处理的 super_block 仍然存在,释放其资源 */
	if (old_sb)
		deactivate_super(old_sb);
}

static void proc_sys_invalidate_dcache(struct ctl_table_header *head)
{
	proc_invalidate_siblings_dcache(&head->inodes, &sysctl_lock);
}

fs/inode.c

inode_init_early 初始化 inode 的早期阶段

c 复制代码
/*
 * 初始化 waitqueues 和 inode 哈希表。
 */
void __init inode_init_early(void)
{
	/* 
	 * 如果哈希分布在 NUMA 节点之间,请推迟哈希分配,直到 vmalloc 空间可用。
	 */
	/* #define hashdist (0) */
	if (hashdist)
		return;

	inode_hashtable =
		alloc_large_system_hash("Inode-cache",
					sizeof(struct hlist_head),
					ihash_entries,
					14,
					HASH_EARLY | HASH_ZERO,
					&i_hash_shift,
					&i_hash_mask,
					0,
					0);
}

__address_space_init_once 用于初始化地址空间(address space)的一次性设置

c 复制代码
static void __address_space_init_once(struct address_space *mapping)
{
	/* 	初始化 i_pages 字段,该字段使用 xarray 数据结构管理文件的页缓存
	 	XA_FLAGS_LOCK_IRQ 确保操作在中断上下文中安全,
		XA_FLAGS_ACCOUNT 用于内存使用的统计和控制*/
	xa_init_flags(&mapping->i_pages, XA_FLAGS_LOCK_IRQ | XA_FLAGS_ACCOUNT);
	/*	初始化 i_mmap_rwsem 字段,该字段用于保护文件的内存映射操作。
		读写信号量允许多个线程同时读取,但写操作是独占的。
		
		RCU 适用于读多写少的场景,但写操作较复杂,无法满足频繁读写的需求。
		不支持写操作的独占性*/
	init_rwsem(&mapping->i_mmap_rwsem);
	/* 该字段用于管理与 address_space 相关的私有数据 */
	INIT_LIST_HEAD(&mapping->i_private_list);
	spin_lock_init(&mapping->i_private_lock);
	/* RB_ROOT_CACHED,表示内存映射的红黑树根节点。
	红黑树是一种自平衡二叉搜索树,用于高效管理内存映射区域 */
	mapping->i_mmap = RB_ROOT_CACHED;
}

init_once 用于初始化 inode 的一次性设置

c 复制代码
/*
 * 这些是只需要执行一次的初始化,因为这些字段在使用 inode 时是幂等的,因此请让 slab 知道这一点。
 */
void inode_init_once(struct inode *inode)
{
	memset(inode, 0, sizeof(*inode));
	INIT_HLIST_NODE(&inode->i_hash);
	INIT_LIST_HEAD(&inode->i_devices);
	INIT_LIST_HEAD(&inode->i_io_list);
	INIT_LIST_HEAD(&inode->i_wb_list);
	INIT_LIST_HEAD(&inode->i_lru);
	INIT_LIST_HEAD(&inode->i_sb_list);
	/* 初始化 i_data 字段,该字段表示 inode 的地址空间。
	地址空间用于管理文件的页缓存和块映射 */
	__address_space_init_once(&inode->i_data);
	/* 初始化与文件大小相关的字段,确保文件大小的有序性 */
	/*  do { } while (0) */
	i_size_ordered_init(inode);
}
EXPORT_SYMBOL(inode_init_once);

static void init_once(void *foo)
{
	struct inode *inode = (struct inode *) foo;

	inode_init_once(inode);
}

inode_init 设置与索引节点(inode)相关的缓存和哈希表

c 复制代码
void __init inode_init(void)
{
	/* inode slab cache */
	/* 	SLAB_RECLAIM_ACCOUNT 允许缓存被回收以节省内存。
		SLAB_PANIC 确保在分配失败时触发内核错误,避免系统进入不稳定状态。
		SLAB_ACCOUNT 用于内存使用的统计和控制。
		init_once 是一个构造函数,用于初始化每个 inode 的稳定状态 */
	inode_cachep = kmem_cache_create("inode_cache",
					 sizeof(struct inode),
					 0,
					 (SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|
					 SLAB_ACCOUNT),
					 init_once);

	/* 哈希可能已在 inode_init_early 中设置 */
	if (!hashdist)
		return;

	inode_hashtable =
		alloc_large_system_hash("Inode-cache",
					sizeof(struct hlist_head),
					ihash_entries,
					14,
					HASH_ZERO,
					&i_hash_shift,
					&i_hash_mask,
					0,
					0);
}

__iget 用于增加 inode 的引用计数

c 复制代码
/*
 * inode->i_lock must be held
 */
static inline void __iget(struct inode *inode)
{
	atomic_inc(&inode->i_count);
}

igrab 用于增加 inode 的引用计数

c 复制代码
struct inode *igrab(struct inode *inode)
{
	spin_lock(&inode->i_lock);
    /* 表示 inode 正在释放或即将释放 */
	if (!(inode->i_state & (I_FREEING|I_WILL_FREE))) {
        /* 如果 inode 不处于释放状态,调用 __iget 增加 inode 的引用计数,并释放锁 */
		__iget(inode);
		spin_unlock(&inode->i_lock);
	} else {
		spin_unlock(&inode->i_lock);
		/*
		 * 如果 inode 正在释放,直接释放锁,并将 inode 设置为 NULL。
         * 这种情况可能发生在 s_op->clear_inode 尚未调用时,确保不会对正在释放的 inode 进行操作
        */
		inode = NULL;
	}
	return inode;
}
EXPORT_SYMBOL(igrab);

__inode_add_lru 用于将 inode 添加到 LRU(最近最少使用)列表中

  • LRU 缓存是一种常见的资源管理机制,用于优先保留最近使用的对象并逐步移除较少使用的对象。
c 复制代码
static void __inode_add_lru(struct inode *inode, bool rotate)
{
	/* 确保它不是脏的(I_DIRTY_ALL)、正在同步(I_SYNC)、正在释放(I_FREEING)或即将释放(I_WILL_FREE) */
	if (inode->i_state & (I_DIRTY_ALL | I_SYNC | I_FREEING | I_WILL_FREE))
		return;
	/* 如果引用计数不为零,说明 inode 仍在使用,不能被添加到 LRU 缓存中 */
	if (atomic_read(&inode->i_count))
		return;
	/* 如果文件系统不活跃,inode 不会被添加到 LRU 缓存中 */
	if (!(inode->i_sb->s_flags & SB_ACTIVE))
		return;
	/*  检查 inode 的数据映射(i_data)是否可以缩减。
		如果映射不可缩减,说明 inode 的资源无法被释放,
		因此不会被添加到 LRU 缓存中。 */
	if (!mapping_shrinkable(&inode->i_data))
		return;

	/* 尝试将 inode 添加到超级块的 LRU 列表(s_inode_lru)中。 */
	if (list_lru_add_obj(&inode->i_sb->s_inode_lru, &inode->i_lru))
		/* 调用 this_cpu_inc 增加当前 CPU 的未使用 inode 计数(nr_unused),用于统计目的 */
		this_cpu_inc(nr_unused);
	else if (rotate)
		/* 表示 inode 最近被访问过,可以在后续操作中优先处理 */
		inode->i_state |= I_REFERENCED;
}

inode_wait_for_lru_isolating 等待 inode(文件系统中的索引节点)完成 LRU(最近最少使用)隔离操作

c 复制代码
static void inode_wait_for_lru_isolating(struct inode *inode)
{
	struct wait_bit_queue_entry wqe;
	struct wait_queue_head *wq_head;

	lockdep_assert_held(&inode->i_lock);
	if (!(inode->i_state & I_LRU_ISOLATING))
		return;

	wq_head = inode_bit_waitqueue(&wqe, inode, __I_LRU_ISOLATING);
	for (;;) {
		prepare_to_wait_event(wq_head, &wqe.wq_entry, TASK_UNINTERRUPTIBLE);
		/*
		 * Checking I_LRU_ISOLATING with inode->i_lock guarantees
		 * memory ordering.
		 */
		if (!(inode->i_state & I_LRU_ISOLATING))
			break;
		spin_unlock(&inode->i_lock);
		schedule();
		spin_lock(&inode->i_lock);
	}
	finish_wait(wq_head, &wqe.wq_entry);
	WARN_ON(inode->i_state & I_LRU_ISOLATING);
}

destroy_inode 用于销毁 inode(文件系统中的索引节点)

c 复制代码
void free_inode_nonrcu(struct inode *inode)
{
	kmem_cache_free(inode_cachep, inode);
}
EXPORT_SYMBOL(free_inode_nonrcu);

static void i_callback(struct rcu_head *head)
{
	struct inode *inode = container_of(head, struct inode, i_rcu);
	if (inode->free_inode)
		inode->free_inode(inode);
	else
		free_inode_nonrcu(inode);
}

void __destroy_inode(struct inode *inode)
{
	BUG_ON(inode_has_buffers(inode));
	inode_detach_wb(inode);
	security_inode_free(inode);
	fsnotify_inode_delete(inode);
	locks_free_lock_context(inode);
	if (!inode->i_nlink) {
		WARN_ON(atomic_long_read(&inode->i_sb->s_remove_count) == 0);
		atomic_long_dec(&inode->i_sb->s_remove_count);
	}

#ifdef CONFIG_FS_POSIX_ACL
	if (inode->i_acl && !is_uncached_acl(inode->i_acl))
		posix_acl_release(inode->i_acl);
	if (inode->i_default_acl && !is_uncached_acl(inode->i_default_acl))
		posix_acl_release(inode->i_default_acl);
#endif
	this_cpu_dec(nr_inodes);
}
EXPORT_SYMBOL(__destroy_inode);

static void destroy_inode(struct inode *inode)
{
	const struct super_operations *ops = inode->i_sb->s_op;

	BUG_ON(!list_empty(&inode->i_lru));
	__destroy_inode(inode);
	if (ops->destroy_inode) {
		ops->destroy_inode(inode);
		if (!ops->free_inode)
			return;
	}
	inode->free_inode = ops->free_inode;
	call_rcu(&inode->i_rcu, i_callback);
}

evict 释放传入的 inode(文件系统中的索引节点),并从其关联的所有列表中移除

c 复制代码
/*
 * 释放传入的 inode,将其从它仍然连接到的列表中删除。我们删除仍附加到 inode 的所有页面,并等待仍在进行的任何 IO,然后再最终销毁 inode。
 *
 * inode 必须已经标记为 I_FREEING,这样我们就可以避免在与作列表的其他代码(例如 writeback_single_inode)竞争时将 inode 移回列表。调用方负责设置此项。
 *
 * 在从缓存中逐出 inode 之前,必须已从 LRU 列表中删除该 inode。这应该在设置 I_FREEING state 标志时以原子方式发生,因此在被驱逐时,此处的任何 inode 都不应位于 LRU 上。
 */
static void evict(struct inode *inode)
{
	const struct super_operations *op = inode->i_sb->s_op;

	/* 是否已标记为 I_FREEING,确保不会与其他代码竞争 */
	BUG_ON(!(inode->i_state & I_FREEING));
	/* 已从 LRU(最近最少使用)列表中移除,确保其不再被缓存管理使用 */
	BUG_ON(!list_empty(&inode->i_lru));

	if (!list_empty(&inode->i_io_list))
		/* 从 I/O 列表,确保其不再参与文件系统操作 */
		inode_io_list_del(inode);
	/* 和超级块列表中移除 inode */
	inode_sb_list_del(inode);

	spin_lock(&inode->i_lock);
	/* 等待 LRU 隔离操作完成 */
	inode_wait_for_lru_isolating(inode);

	/*
	 * 等待 flusher 线程完成 inode,以便文件系统不会在写回仍在运行时开始销毁它。由于 inode 已设置I_FREEING,因此刷新程序线程不会在 inode 上启动新工作。 我们只需要等待运行写回完成。
	 */
	inode_wait_for_writeback(inode);
	spin_unlock(&inode->i_lock);

	if (op->evict_inode) {
		op->evict_inode(inode);
	} else {
		truncate_inode_pages_final(&inode->i_data);
		clear_inode(inode);
	}
	/* inode 表示字符设备,调用 cd_forget 清理其关联的字符设备数据 */
	if (S_ISCHR(inode->i_mode) && inode->i_cdev)
		cd_forget(inode);

	/* 从 inode 哈希表中移除,确保其不再被文件系统引用 */
	remove_inode_hash(inode);

	/*
	 * 在 __wait_on_freeing_inode() 中唤醒服务员。
	 *
	 * 在 remove_inode_hash() 获取 ->i_lock 之前,我们需要唤醒的任何线程都已经被考虑在内,这是一个不变的 - 如果发现 inode 未哈希,则双方都获取锁并中止 sleep。因此,要么 sleeper 获胜并离开 CPU,要么 removal 获胜,sleeper 在使用锁进行测试后中止。
	 *
	 * 这也意味着我们不需要为下面的电话会议设置任何围栏。
	 */
	/* 唤醒等待 inode 释放的线程,确保资源回收过程的同步 */
	inode_wake_up_bit(inode, __I_NEW);
	BUG_ON(inode->i_state != (I_FREEING | I_CLEAR));

	/* 销毁 inode */
	destroy_inode(inode);
}

iput_final 用于处理文件系统中 inode 的最后一个引用被释放的情况

c 复制代码
// 决定是否可以从缓存中移除一个 inode
static inline int generic_drop_inode(struct inode *inode)
{
	/* 如果链接计数为 0,说明该 inode 不再被任何文件或目录引用,可以安全地移除 */
	/* 如果 inode 未被哈希化,说明它不再属于文件系统的活跃状态,可以被移除 */
	return !inode->i_nlink || inode_unhashed(inode);
}

/*
 *当我们删除对 inode 的最后一个引用时调用。
 *
 * 调用 FS "drop_inode()" 函数,默认为传统的 UNIX 文件系统行为。 如果它告诉我们要驱逐 inode,请这样做。 否则,如果 fs 处于活动状态,则保留 inodein 缓存,如果 fs 正在关闭,则 sync 和 evict。
 */
static void iput_final(struct inode *inode)
{
	struct super_block *sb = inode->i_sb;
	const struct super_operations *op = inode->i_sb->s_op;
	unsigned long state;
	int drop;

	WARN_ON(inode->i_state & I_NEW);

	if (op->drop_inode)
		drop = op->drop_inode(inode);
	else
		drop = generic_drop_inode(inode);

	/* 保留 inode 在缓存中 */
	if (!drop &&
		/* inode 没有被标记为不可缓存(I_DONTCACHE) */
	    !(inode->i_state & I_DONTCACHE) &&
		/* 且文件系统处于活动状态(SB_ACTIVE) */
	    (sb->s_flags & SB_ACTIVE)) {
		/* 将 inode 添加到 LRU(最近最少使用)缓存中 */
		__inode_add_lru(inode, true);
		spin_unlock(&inode->i_lock);
		return;
	}

	state = inode->i_state;
	if (!drop) {
		/*  inode 需要被释放,则将其状态标记为 I_WILL_FREE */
		WRITE_ONCE(inode->i_state, state | I_WILL_FREE);
		spin_unlock(&inode->i_lock);

		/* 将 inode 的数据同步到磁盘,确保数据一致性 */
		write_inode_now(inode, 1);

		spin_lock(&inode->i_lock);
		state = inode->i_state;
		WARN_ON(state & I_NEW);
		/* 同步完成后,清除 I_WILL_FREE 状态 */
		state &= ~I_WILL_FREE;
	}

	/* 将 inode 的状态标记为 I_FREEING,表示它正在被释放 */
	WRITE_ONCE(inode->i_state, state | I_FREEING);
	if (!list_empty(&inode->i_lru))
		/* 如果 inode 在 LRU 缓存中,则将其从缓存中移除 */
		inode_lru_list_del(inode);
	spin_unlock(&inode->i_lock);
	/* 调用 evict 函数执行最终的释放操作 */
	evict(inode);
}

iput 用于减少 inode 的引用计数

c 复制代码
/**
 * iput - 放置一个 inode
 * @inode:要放置的 inode
 *
 * 放置一个 inode,丢弃其使用计数。如果 inode 使用计数达到零,则 inode 将被释放,也可能被销毁。
 *
 * 因此,iput() 可以休眠。
 */
void iput(struct inode *inode)
{
	if (!inode)
		return;
    /* 检查 inode 的状态是否包含 I_CLEAR(表示 inode 已被清理) */
	BUG_ON(inode->i_state & I_CLEAR);
retry:
    /* 减少引用计数并尝试加锁 
	如果引用计数降至零且成功获取锁,进入锁定状态处理 inode */
	if (atomic_dec_and_lock(&inode->i_count, &inode->i_lock)) {
		/*  I_DIRTY_TIME(表示需要写回时间元数据) */
		if (inode->i_nlink && (inode->i_state & I_DIRTY_TIME)) {
			atomic_inc(&inode->i_count);
			spin_unlock(&inode->i_lock);
			trace_writeback_lazytime_iput(inode);
			/* 同步更新 inode 的元数据 */
			mark_inode_dirty_sync(inode);
			goto retry;
		}
		/* 如果引用计数降至零且不需要处理 lazytime 写回,
		调用 iput_final 释放 inode */
		iput_final(inode);
	}
}
EXPORT_SYMBOL(iput);

inode_bit_waitqueue 用于获取 inode 的等待队列头

c 复制代码
/*
 *从 inode->i_state 获取位地址以与 wait_var_event() infrastructre 一起使用。
 */
#define inode_state_wait_address(inode, bit) ((char *)&(inode)->i_state + (bit))

struct wait_queue_head *inode_bit_waitqueue(struct wait_bit_queue_entry *wqe,
					    struct inode *inode, u32 bit)
{
        void *bit_address;
		/* 获取与 inode 的特定状态位(bit)相关联的地址 
			该地址用于标识 inode 的状态位,作为等待队列的关键变量*/
        bit_address = inode_state_wait_address(inode, bit);
		/* 使用 init_wait_var_entry 初始化等待队列条目(wqe) */
        init_wait_var_entry(wqe, bit_address, 0);
		/* 获取与 bit_address 关联的等待队列头(wait_queue_head) 
			等待队列头是等待队列的入口点,管理所有等待该变量的线程*/
        return __var_waitqueue(bit_address);
}
EXPORT_SYMBOL(inode_bit_waitqueue);

alloc_inode 获取一个 inode

c 复制代码
/**
 *	alloc_inode 	- 获取一个 inode
 *	@sb: 超级块
 *
 *	为给定的超级块分配一个新的 inode。
 *	inode 不会链接到超级块的 s_inodes 列表中。
 *	这意味着:
 *	- 文件系统无法卸载
 *	- 配额、fsnotify、写回无法工作
 */
struct inode *alloc_inode(struct super_block *sb)
{
	const struct super_operations *ops = sb->s_op;
	struct inode *inode;

	if (ops->alloc_inode)
		inode = ops->alloc_inode(sb);
	else
		inode = alloc_inode_sb(sb, inode_cachep, GFP_KERNEL);

	if (!inode)
		return NULL;

	if (unlikely(inode_init_always(sb, inode))) {
		if (ops->destroy_inode) {
			ops->destroy_inode(inode);
			if (!ops->free_inode)
				return NULL;
		}
		inode->free_inode = ops->free_inode;
		i_callback(&inode->i_rcu);
		return NULL;
	}

	return inode;
}

inode_sb_list_add 将 inode 添加到超级块的 inode 列表中

c 复制代码
/**
 *inode_sb_list_add - 将 inode 添加到超级块的 inode 列表中  
* @inode: 要添加的 inode  
*/  
void inode_sb_list_add(struct inode *inode)
{
	struct super_block *sb = inode->i_sb;

	spin_lock(&sb->s_inode_list_lock);
	list_add(&inode->i_sb_list, &sb->s_inodes);
	spin_unlock(&sb->s_inode_list_lock);
}
EXPORT_SYMBOL_GPL(inode_sb_list_add);

new_inode 获取一个inode

c 复制代码
/**
 *	new_inode 	- 获取一个inode
 *	@sb: 超级块
 *
 *	为给定的超级块分配一个新的inode。与inode->i_mapping相关的分配的默认gfp_mask
 *	是GFP_HIGHUSER_MOVABLE。如果HIGHMEM页面不适用,或者已知为页面缓存分配的页面
 *	不可回收或不可迁移,则必须在新创建的inode的映射上调用mapping_set_gfp_mask(),
 *	并使用适当的标志。
 *
 */
struct inode *new_inode(struct super_block *sb)
{
	struct inode *inode;

	inode = alloc_inode(sb);
	if (inode)
		inode_sb_list_add(inode);
	return inode;
}
EXPORT_SYMBOL(new_inode);

inode_init_always_gfp 用于执行 inode 结构的初始化

c 复制代码
/**
 * inode_init_always_gfp - 执行 inode 结构初始化  
* @sb: inode 所属的超级块  
* @inode: 要初始化的 inode  
* @gfp: 分配标志  

这些是每次 inode 分配时需要完成的初始化,因为这些字段不会通过 slab 分配进行初始化。  
如果需要额外的分配,则使用 @gfp。  
*/
int inode_init_always_gfp(struct super_block *sb, struct inode *inode, gfp_t gfp)
{
	static const struct inode_operations empty_iops;
	static const struct file_operations no_open_fops = {.open = no_open};
	struct address_space *const mapping = &inode->i_data;
	/* 基本字段初始化 */
	inode->i_sb = sb;
	inode->i_blkbits = sb->s_blocksize_bits;
	inode->i_flags = 0;
	inode->i_state = 0;
	atomic64_set(&inode->i_sequence, 0);
	/* 引用计数(i_count)为 1,表示该 inode 已被分配 */
	atomic_set(&inode->i_count, 1);
	/* 设置默认的 inode 操作(i_op)和文件操作(i_fop) */
	inode->i_op = &empty_iops;
	inode->i_fop = &no_open_fops;
	inode->i_ino = 0;
	inode->__i_nlink = 1;
	/* 操作标志初始化 */
	inode->i_opflags = 0;
	/* 如果超级块支持扩展属性(s_xattr),设置 IOP_XATTR 标志。 */
	if (sb->s_xattr)
		inode->i_opflags |= IOP_XATTR;
	/* 支持管理时间(FS_MGTIME),设置 IOP_MGTIME 标志。 */
	if (sb->s_type->fs_flags & FS_MGTIME)
		inode->i_opflags |= IOP_MGTIME;
	/* 设置 inode 的用户 ID 和组 ID 为 0,表示默认所有者 */
	i_uid_write(inode, 0);
	i_gid_write(inode, 0);
	atomic_set(&inode->i_writecount, 0);
	/* 初始化 inode 的大小(i_size)、块计数(i_blocks)、字节计数(i_bytes)等字段 */
	inode->i_size = 0;
	inode->i_write_hint = WRITE_LIFE_NOT_SET;
	inode->i_blocks = 0;
	inode->i_bytes = 0;
	inode->i_generation = 0;
	inode->i_pipe = NULL;
	inode->i_cdev = NULL;
	inode->i_link = NULL;
	inode->i_dir_seq = 0;
	inode->i_rdev = 0;
	inode->dirtied_when = 0;

#ifdef CONFIG_CGROUP_WRITEBACK
	inode->i_wb_frn_winner = 0;
	inode->i_wb_frn_avg_time = 0;
	inode->i_wb_frn_history = 0;
#endif
	/* 锁和同步机制初始化 */
	spin_lock_init(&inode->i_lock);
	lockdep_set_class(&inode->i_lock, &sb->s_type->i_lock_key);

	init_rwsem(&inode->i_rwsem);
	lockdep_set_class(&inode->i_rwsem, &sb->s_type->i_mutex_key);

	atomic_set(&inode->i_dio_count, 0);
	/* 地址空间初始化 */
	mapping->a_ops = &empty_aops;
	mapping->host = inode;
	mapping->flags = 0;
	mapping->wb_err = 0;
	atomic_set(&mapping->i_mmap_writable, 0);
#ifdef CONFIG_READ_ONLY_THP_FOR_FS
	atomic_set(&mapping->nr_thps, 0);
#endif
	mapping_set_gfp_mask(mapping, GFP_HIGHUSER_MOVABLE);
	mapping->i_private_data = NULL;
	mapping->writeback_index = 0;
	init_rwsem(&mapping->invalidate_lock);
	lockdep_set_class_and_name(&mapping->invalidate_lock,
				   &sb->s_type->invalidate_lock_key,
				   "mapping.invalidate_lock");
	if (sb->s_iflags & SB_I_STABLE_WRITES)
		mapping_set_stable_writes(mapping);
	inode->i_private = NULL;
	inode->i_mapping = mapping;
	INIT_HLIST_HEAD(&inode->i_dentry);	/* buggered by rcu freeing */
#ifdef CONFIG_FS_POSIX_ACL
	inode->i_acl = inode->i_default_acl = ACL_NOT_CACHED;
#endif

#ifdef CONFIG_FSNOTIFY
	inode->i_fsnotify_mask = 0;
#endif
	inode->i_flctx = NULL;

	if (unlikely(security_inode_alloc(inode, gfp)))
		return -ENOMEM;
	/* 增加当前 CPU 的 inode 计数,更新统计信息 */
	this_cpu_inc(nr_inodes);

	return 0;
}
EXPORT_SYMBOL(inode_init_always_gfp);

get_next_ino 获生成文件系统的下一个 inode 编号(inode number)

c 复制代码
/*
* 每个 CPU 拥有一组 LAST_INO_BATCH 范围的编号。
* 只有在 LAST_INO_BATCH 次分配中,'shared_last_ino' 才会被修改一次,
* 用于更新耗尽的范围。
*
* 这不会显著增加溢出率,因为每个 CPU 最多只能消耗 LAST_INO_BATCH-1 个未使用的 inode 编号。因此会有 NR_CPUS*(LAST_INO_BATCH-1) 的浪费。在 4096 和 1024 的情况下,这大约是 2^32 范围的 ~0.1%,并且是最坏情况。即使浪费达到 50%,溢出率也只会增加 2 倍,这似乎并不太显著。
*
* 在 32 位非 LFS 的 stat() 调用中,如果 st_ino 无法适应目标结构字段,glibc 会生成一个 EOVERFLOW 错误。在此使用 32 位计数器以尝试避免该问题。
 */
#define LAST_INO_BATCH 1024
static DEFINE_PER_CPU(unsigned int, last_ino);

unsigned int get_next_ino(void)
{
	unsigned int *p = &get_cpu_var(last_ino);
	unsigned int res = *p;

#ifdef CONFIG_SMP
	if (unlikely((res & (LAST_INO_BATCH-1)) == 0)) {
		static atomic_t shared_last_ino;
		int next = atomic_add_return(LAST_INO_BATCH, &shared_last_ino);

		res = next - LAST_INO_BATCH;
	}
#endif

	res++;
	/* get_next_ino should not provide a 0 inode number */
	if (unlikely(!res))
		res++;
	*p = res;
	put_cpu_var(last_ino);
	return res;
}
EXPORT_SYMBOL(get_next_ino);

inode_init_owner 根据POSIX标准初始化新inode的uid、gid和mode

c 复制代码
/**
* inode_init_owner - 根据POSIX标准初始化新inode的uid、gid和mode  
* @idmap: 创建该inode的挂载点的idmap  
* @inode: 新的inode  
* @dir: 目录inode  
* @mode: 新inode的模式  
* 如果通过idmapped挂载创建了inode,则必须通过@idmap传递vfsmount的idmap。此函数将负责根据@idmap映射inode,然后检查权限并初始化i_uid和i_gid。在非idmapped挂载或需要对原始inode执行权限检查的情况下,只需传递@nop_mnt_idmap。*/
void inode_init_owner(struct mnt_idmap *idmap, struct inode *inode,
		      const struct inode *dir, umode_t mode)
{
	/* 据挂载点的 ID 映射(idmap)设置 inode 的用户 ID(i_uid) */
	inode_fsuid_set(inode, idmap);
	/* 新 inode 的父目录(dir)启用了 S_ISGID 标志(组 ID 设置标志),新 inode 的组 ID(i_gid)继承父目录的组 ID */
	if (dir && dir->i_mode & S_ISGID) {
		inode->i_gid = dir->i_gid;

		/* 新 inode 是目录(S_ISDIR(mode)),则继承 S_ISGID 标志,确保新目录的子文件或子目录也继承组 ID */
		if (S_ISDIR(mode))
			mode |= S_ISGID;
	} else
		inode_fsgid_set(inode, idmap);
	inode->i_mode = mode;
}
EXPORT_SYMBOL(inode_init_owner);

VFS Inode时间戳管理:高精度与并发安全的更新机制

本代码片段是Linux VFS层中负责管理inode时间戳的核心函数,重点关注ctime(change time,状态改变时间)的更新。其核心功能是提供一套健壮、高效且并发安全的API,用于将inode的ctime设置为特定值或当前时间。这套机制通过引入时间精度(granularity)处理、性能优化的"多粒度时间"(multigrain time)概念以及原子操作,解决了在现代多核、高精度时钟环境下,正确且高效地更新文件元数据的复杂问题。

实现原理分析

该机制的实现是分层且高度优化的,旨在平衡精度、性能和并发安全性。

  1. 基础设置器 (inode_set_ctime_to_ts):

    • 这是最底层的API,负责将一个明确给定的timespec64值设置到inode的i_ctime_seci_ctime_nsec字段中。它不包含任何复杂逻辑,仅仅是执行赋值操作,是其他高层函数的基础。
  2. 精度裁剪 (timestamp_truncate):

    • 不同的文件系统(如ext4, vfat, ntfs)对时间戳的支持精度不同。例如,FAT32的精度只有2秒。此函数的作用就是将一个高精度的时间戳,根据其所在文件系统的能力(由inode->i_sb->s_time_gran定义),"裁剪"到合法的精度。它通过取模运算(%)实现,确保写入存储介质的时间戳不会超出文件系统的表示范围。
  3. 高性能当前时间更新 (inode_set_ctime_current):

    • 这是最核心和最复杂的函数,其目标是以最小的开销将ctime更新为"现在"。它实现了一个"多粒度时间"(multigrain time)的优化策略:
      a. 优先使用粗粒度时间 : 它首先获取一个低精度的"粗粒度"当前时间(ktime_get_coarse_real_ts64_mg)。这是一个非常快速的操作,因为它读取的是一个被内核定期更新的缓存值,通常不需要昂贵的时钟源访问。
      b. 惰性获取高精度时间 : 只有在必要时,它才会去获取高精度的当前时间(ktime_get_real_ts64_mg),这是一个相对较慢的操作。那么何时是"必要"的呢?当且仅当:1) 已经有人查询过这个inode的精确时间戳(通过I_CTIME_QUERIED标志位判断);并且 2) 当前的粗粒度时间并不比inode上已有的时间戳更新。这个逻辑避免了在绝大多数情况下(即短时间内连续的、无人关心的更新)调用昂贵的高精度时间函数。
      c. 并发安全更新 : 在多核系统中,多个CPU可能同时尝试更新同一个inode的ctime。一个简单的赋值操作会导致"最后写入者获胜"的竞争,并可能丢失更新。为了解决这个问题,该函数不直接赋值,而是使用原子操作try_cmpxchg来尝试交换纳秒字段。如果交换成功,说明我们成功地写入了新值。如果失败,意味着在cmpxchg执行的瞬间,已有另一个CPU写入了一个更新的时间戳。在这种情况下,函数会接受这个已经存在的新值,因为它保证了时间的单调递增,从而确保了ctime的正确性。

代码分析

c 复制代码
// inode_set_ctime_to_ts: 将inode的ctime设置为一个指定的timespec值。
struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts)
{
	trace_inode_set_ctime_to_ts(inode, &ts); // 内核追踪点。
	// 规范化timespec,确保tv_nsec在[0, 999999999]范围内。
	set_normalized_timespec64(&ts, ts.tv_sec, ts.tv_nsec);
	// 直接赋值。
	inode->i_ctime_sec = ts.tv_sec;
	inode->i_ctime_nsec = ts.tv_nsec;
	return ts;
}
EXPORT_SYMBOL(inode_set_ctime_to_ts);

// timestamp_truncate: 将timespec裁剪到文件系统支持的精度。
// @t: 待裁剪的timespec。
// @inode: 正在被更新的inode。
struct timespec64 timestamp_truncate(struct timespec64 t, struct inode *inode)
{
	struct super_block *sb = inode->i_sb;
	unsigned int gran = sb->s_time_gran; // 获取文件系统的时间精度(单位:纳秒)。

	// 将秒数限制在文件系统支持的最大和最小时间范围内。
	t.tv_sec = clamp(t.tv_sec, sb->s_time_min, sb->s_time_max);
	if (unlikely(t.tv_sec == sb->s_time_max || t.tv_sec == sb->s_time_min))
		t.tv_nsec = 0;

	// 针对常见的精度值进行快速处理,避免除法运算。
	if (gran == 1)
		; /* 1纳秒精度,无需操作 */
	else if (gran == NSEC_PER_SEC)
		t.tv_nsec = 0; // 1秒精度,纳秒部分清零。
	else if (gran > 1 && gran < NSEC_PER_SEC)
		// 对于其他精度,通过取模运算将纳秒部分向下舍入到精度的倍数。
		t.tv_nsec -= t.tv_nsec % gran;
	else
		WARN(1, "invalid file time granularity: %u", gran); // 警告无效的精度值。
	return t;
}
EXPORT_SYMBOL(timestamp_truncate);

// inode_set_ctime_current: 将inode的ctime设置为当前时间。
struct timespec64 inode_set_ctime_current(struct inode *inode)
{
	struct timespec64 now;
	u32 cns, cur;

	// 首先获取一个低精度的"粗粒度"当前时间,这是一个快速操作。
	ktime_get_coarse_real_ts64_mg(&now);
	// 将获取的时间裁剪到文件系统支持的精度。
	now = timestamp_truncate(now, inode);

	// 如果文件系统不支持多粒度时间,则直接设置并返回。
	if (!is_mgtime(inode)) {
		inode_set_ctime_to_ts(inode, now);
		goto out;
	}

	// 使用带有acquire内存屏障的加载,确保后续操作能看到最新的值。
	cns = smp_load_acquire(&inode->i_ctime_nsec);
	// 检查I_CTIME_QUERIED标志位,判断是否需要获取高精度时间。
	if (cns & I_CTIME_QUERIED) {
		struct timespec64 ctime = { .tv_sec = inode->i_ctime_sec,
					    .tv_nsec = cns & ~I_CTIME_QUERIED };

		// 如果粗粒度时间不比现有时间更新,则获取高精度时间。
		if (timespec64_compare(&now, &ctime) <= 0) {
			ktime_get_real_ts64_mg(&now);
			now = timestamp_truncate(now, inode);
			mgtime_counter_inc(mg_fine_stamps); // 统计高精度时间戳使用次数。
		}
	}
	mgtime_counter_inc(mg_ctime_updates);

	// 如果新旧时间完全相同,则无需更新,直接跳出。
	if (cns == now.tv_nsec && inode->i_ctime_sec == now.tv_sec) {
		trace_ctime_xchg_skip(inode, &now);
		goto out;
	}
	cur = cns;
retry:
	// 尝试以原子方式将新的纳秒值交换到inode->i_ctime_nsec中。
	if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now.tv_nsec)) {
		// 如果交换成功,则更新秒部分。
		inode->i_ctime_sec = now.tv_sec;
		trace_ctime_ns_xchg(inode, cns, now.tv_nsec, cur);
		mgtime_counter_inc(mg_ctime_swaps);
	} else {
		/*
		 * 交换失败,检查失败原因是否是其他线程刚刚设置了QUERIED位。
		 * 如果是,则可以安全地重试交换。
		 */
		if (!(cns & I_CTIME_QUERIED) && (cns | I_CTIME_QUERIED) == cur) {
			cns = cur;
			goto retry;
		}
		// 如果是其他原因(意味着已有更新的时间戳被写入),则接受现有值。
		now.tv_sec = inode->i_ctime_sec;
		now.tv_nsec = cur & ~I_CTIME_QUERIED;
	}
out:
	return now;
}
EXPORT_SYMBOL(inode_set_ctime_current);
相关推荐
AI视觉网奇2 小时前
ue 蓝图动画学习笔记
笔记·学习·ue5
二十画~书生2 小时前
电路基础知识
笔记
freexyn2 小时前
Matlab速成笔记七十:使用多项式函数进行曲线拟合
开发语言·笔记·matlab
iCxhust2 小时前
linux /etc 目录 etc是什么缩写
linux·运维·服务器·php
潲爺2 小时前
Java-多线程
java·笔记·学习
果汁底线2 小时前
UFS Auto Hibernate介绍
linux·ufs
lkbhua莱克瓦242 小时前
基础-SQL-DQL
java·开发语言·数据库·笔记·mysql·dql
lkbhua莱克瓦242 小时前
基础-SQL-DCL
开发语言·数据库·笔记·mysql·dcl
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之mc命令(实操篇)
linux·运维·服务器·前端·笔记