[toc]

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)的线程

1
2
3
4
5
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(文件系统中的索引节点)

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

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

1
2
3
4
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 字段

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 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 的目录缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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 的早期阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* 初始化 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)的一次性设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 的一次性设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* 这些是只需要执行一次的初始化,因为这些字段在使用 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)相关的缓存和哈希表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 的引用计数

1
2
3
4
5
6
7
/*
* inode->i_lock must be held
*/
static inline void __iget(struct inode *inode)
{
atomic_inc(&inode->i_count);
}

igrab 用于增加 inode 的引用计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 缓存是一种常见的资源管理机制,用于优先保留最近使用的对象并逐步移除较少使用的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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(最近最少使用)隔离操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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(文件系统中的索引节点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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(文件系统中的索引节点),并从其关联的所有列表中移除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
* 释放传入的 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 的最后一个引用被释放的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 决定是否可以从缓存中移除一个 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 的引用计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 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 的等待队列头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
*从 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 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 列表中

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
*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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 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 结构的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
* 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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
* 每个 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 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的正确性。

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// 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);