[TOC]

kernfs 伪文件系统核心框架(Pseudo Filesystem Core Framework) sysfs和cgroupfs的底层基石

历史与背景

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

kernfs (Kernel File System) 是一个为了解决在实现伪文件系统(pseudo filesystem)时遇到的内部复杂性和锁竞争问题而被创造出来的内核核心框架。它的诞生主要源于其前身及主要用户——sysfs——所暴露出的设计缺陷:

  • 锁机制复杂且易于死锁:在 kernfs 出现之前,sysfs 的实现与 VFS(虚拟文件系统)层紧密耦合。sysfs 中的目录和文件直接对应于内核的 dentryinode 对象。VFS 自身的锁机制(特别是 d_locki_mutex)非常复杂,当 sysfs 的属性文件读写操作需要回调到驱动程序,而驱动程序又可能需要获取其他与 VFS 相关的锁时,就极易形成复杂的锁依赖链,导致死锁(deadlock)。这是 sysfs 长期以来最头疼的问题之一。
  • 数据结构臃肿:直接使用 dentryinode 来表示 sysfs 中的每一个节点,对于这种只有元数据、没有实际数据的伪文件系统来说,是一种浪费。这些 VFS 结构体包含了大量 sysfs 根本用不到的字段,增加了内存消耗。
  • 生命周期管理困难sysfs 节点的生命周期与 VFS 对象的生命周期紧密绑定,而 VFS 的缓存机制(dcache)使得节点的销毁时机变得不确定,这给需要精确控制节点创建和移除的驱动开发者带来了困难。
  • 缺乏清晰的抽象sysfs 的实现混杂了通用逻辑和 VFS 的特定实现细节,使得代码难以理解和维护,也无法方便地被其他需要类似功能的伪文件系统(如 cgroupfs)复用。

kernfs 的诞生就是为了将伪文件系统的内部逻辑实现VFS 的表现层彻底分离开,从而解决上述所有问题。

它的发展经历了哪些重要的里程碑或版本迭代?

kernfs 的发展是一个典型的内核重构过程:

  1. 构思与实现:由内核开发者 Tejun Heo 主导,旨在创建一个通用的、轻量级的框架来构建层次化的伪文件系统,其首要目标就是修复 sysfs 的锁问题。
  2. 内核引入:kernfs 在 Linux 内核 3.14 版本中被正式引入。这是一个重要的里程碑,标志着内核拥有了一个专门用于构建伪文件系统的标准工具集。
  3. sysfs 迁移:在引入 kernfs 之后,一个庞大的工程就是将现有的 sysfs 实现完全迁移到 kernfs 之上。这意味着 sysfs 不再直接操作 dentryinode,而是通过 kernfs 作为中间层。这个迁移工作跨越了多个内核版本。
  4. cgroupfs v2 的采用:在统一的 cgroup 层次结构(cgroup v2)的设计和实现中,kernfs 被选为其底层的文件系统实现。这证明了 kernfs 作为一个通用框架的成功,不再仅仅是 sysfs 的“后台”。

目前该技术的社区活跃度和主流应用情况如何?

kernfs 是一个非常稳定和成熟的内核核心组件。它不是一个用户可以直接挂载或使用的文件系统,而是一个内部框架。它的活跃度体现在其用户的活跃度上:

  • sysfs:作为 Linux 设备模型的核心部分,sysfs 遍布于所有现代 Linux 系统中,因此 kernfs 也在所有这些系统上稳定运行。
  • cgroupfs v2:作为现代容器技术(如 Docker, systemd)和资源管理的基础,cgroup v2 的普及也意味着 kernfs 被广泛部署在服务器和数据中心环境中。

社区对 kernfs 的维护主要集中在细微的优化和修复上,其核心架构已无需大的改动。

核心原理与设计

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

kernfs 的核心设计思想是分离:将伪文件系统的内部数据结构和层次关系(由 kernfs 自己管理)与它在用户空间所呈现的VFS 视图(标准的目录和文件)分离开来。

  1. 独立的内部数据结构:kernfs 定义了自己的核心节点结构 struct kernfs_node。这个结构体非常轻量,只包含维护层次结构所必需的信息,如父节点、子节点和兄弟节点的指针、节点名称、类型(目录或文件)以及访问权限等。所有 sysfscgroupfs 的节点在内部都表现为一个 kernfs_node 树。
  2. 解耦的锁机制:kernfs 实现了一个非常简单的全局读写信号量(kernfs_rwsem)。所有改变 kernfs 树结构的操作(如创建、删除、重命名节点)都需要获取这个信号量的写锁。而所有遍历或查找操作只需要获取读锁。这种“一个锁管所有结构变化”的模式,虽然看起来粗暴,但彻底切断了与 VFS 锁的复杂依赖,从根本上消除了死锁的风险。属性文件的读写操作则不触及这个锁。
  3. 按需实例化的VFS对象:kernfs 节点树独立存在于内核中。只有当用户空间的进程通过 ls, cat 等命令实际访问到某个 sysfs 路径时,VFS 层才会回调到 kernfs,kernfs 此时才会按需地为被访问的 kernfs_node 创建一个对应的 inodedentry,从而在用户面前呈现为一个正常的文件或目录。这些 VFS 对象仅仅是 kernfs 内部节点的“视图”或“代理”。
  4. 操作的委托:当用户对 sysfs 文件进行读写时,VFS 调用会通过实例化的 inode 找到其背后的 kernfs_node,kernfs 再将操作委托给当初创建这个节点时所注册的回调函数(struct kernfs_ops),最终调用到设备驱动中相应的 show/store 函数。

它的主要优势体现在哪些方面?

  • 死锁免疫:简单而独立的锁模型从根本上解决了与VFS纠缠不清导致的死锁问题,这是其最大的优势。
  • 轻量高效:使用专用的 kernfs_node 代替通用的 inode/dentry 来管理内部结构,减少了内存占用。
  • 清晰的抽象和复用:提供了一套清晰的API(如 kernfs_create_dir, kernfs_create_file),将伪文件系统的实现细节封装起来,使得 sysfscgroupfs 的实现都变得更加简洁,并且逻辑可以复用。
  • 精确的生命周期控制:由于内部结构独立于VFS缓存,驱动程序可以精确地控制节点的创建和销毁,其生命周期变得确定。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 性能瓶颈:单一的全局写锁在某些极端情况下可能会成为性能瓶颈。例如,如果有大量CPU核心同时、高频率地在 sysfs 中创建和删除文件,它们将会因为竞争这个锁而相互等待。但在绝大多数实际使用场景中,sysfs 的结构变化并不频繁,因此这不是一个普遍问题。
  • 非通用文件系统:kernfs 是一个高度特化的框架,它只适用于实现层次化的、主要用于展示内核状态或接收简单输入的伪文件系统。它不能用于实现存储持久化数据的常规磁盘文件系统。

使用场景

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

kernfs 作为内核内部框架,是实现特定类型伪文件系统的唯一且标准的解决方案。

  • sysfs:这是 kernfs 诞生和存在的首要理由。sysfs 将内核中的设备驱动模型层次化地暴露到用户空间。例如,/sys/class/net/eth0/ 目录下的所有文件和子目录,在内核内部都是一棵由 kernfs_node 组成的树,由 kernfs 负责管理。
  • cgroupfs (v2):统一的 cgroup 层次结构(通常挂载在 /sys/fs/cgroup)也是基于 kernfs 实现的。用户通过在这个文件系统中创建目录来创建新的 cgroup,通过读写目录中的文件(如 cpu.max, memory.high)来配置资源限制。所有这些文件系统操作都被 kernfs 转换为对内部 cgroup 数据结构的调用。

是否有不推荐使用该技术的场景?为什么?

任何需要实现通用目的存储持久化数据的文件系统,都绝不应该考虑 kernfs。

  • 磁盘文件系统(ext4, xfs, btrfs):这些文件系统需要管理磁盘块的分配、日志、数据一致性等,功能集与 kernfs 完全不同。
  • 网络文件系统(NFS, CIFS):这些文件系统需要处理网络协议、缓存、认证等,也与 kernfs 无关。
  • 其他伪文件系统(procfs, debugfs)procfsdebugfs 有其自身的、历史悠久的实现方式,虽然它们在功能上与 sysfs 有相似之处,但它们并没有被迁移到 kernfs 上,主要是因为迁移成本巨大且收益不明显。

对比分析

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

kernfs 最直接的对比对象就是它所取代的旧的、直接基于VFS的sysfs实现方法

特性 kernfs 框架 旧的直接基于VFS的实现
核心数据结构 使用轻量级的 struct kernfs_node 管理内部层次结构。inodedentry仅作为按需创建的视图。 直接使用 VFS 的 struct inodestruct dentry 作为核心数据结构。
锁模型 一个独立的全局读写信号量(kernfs_rwsem)保护所有结构性变更,与 VFS 锁完全解耦。 严重依赖并交织于 VFS 的锁,如 dcache_lock, inode->i_mutex 等,极易导致死锁。
抽象层次 。提供了清晰的API,将伪文件系统逻辑与VFS表现层分离。 。伪文件系统的实现逻辑与VFS的实现细节紧密耦合。
内存占用 较低kernfs_nodeinode+dentry 的组合更小。 较高。通用VFS数据结构中存在大量未被使用的字段。
生命周期管理 确定。节点的创建和销毁由驱动程序通过API精确控制。 不确定。受VFS的dcache缓存策略影响,销毁时机不可预测。
可复用性 。作为一个通用框架,被 sysfscgroupfs 共同使用。 。实现与 sysfs 的特定需求绑定,难以复用。

include/linux/kernfs.h

kernfs_ns_enabled 测试是否启用了命名空间

1
2
3
4
5
6
7
8
9
10
/**
* kernfs_ns_enabled - 测试是否启用了命名空间
* @kn: 要测试的节点
*
* 测试是否为 @ns 的子节点启用了命名空间过滤。
*/
static inline bool kernfs_ns_enabled(struct kernfs_node *kn)
{
return kn->flags & KERNFS_NS;
}

kernfs_type 获取 kernfs_node 的类型

1
2
3
4
static inline enum kernfs_node_type kernfs_type(struct kernfs_node *kn)
{
return kn->flags & KERNFS_TYPE_MASK;
}

fs/kernfs/kernfs-internal.h

kernfs_rcu_name 获取 kernfs_node 的名称

1
2
3
4
static inline const char *kernfs_rcu_name(const struct kernfs_node *kn)
{
return rcu_dereference_check(kn->name, kernfs_root_is_locked(kn));
}

kernfs_inc_rev 增加 kernfs_node 的版本号

1
2
3
4
static inline void kernfs_inc_rev(struct kernfs_node *parent)
{
parent->dir.rev++;
}

fs/kernfs/mount.c

kernfs_initkernfs_init

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
static void __init kernfs_mutex_init(void)
{
int count;

for (count = 0; count < NR_KERNFS_LOCKS; count++)
mutex_init(&kernfs_locks->open_file_mutex[count]);
}

static void __init kernfs_lock_init(void)
{
kernfs_locks = kmalloc(sizeof(struct kernfs_global_locks), GFP_KERNEL);
WARN_ON(!kernfs_locks);

kernfs_mutex_init();
}

void __init kernfs_init(void)
{
kernfs_node_cache = kmem_cache_create("kernfs_node_cache",
sizeof(struct kernfs_node),
0, SLAB_PANIC, NULL);

/* Creates slab cache for kernfs inode attributes */
kernfs_iattrs_cache = kmem_cache_create("kernfs_iattrs_cache",
sizeof(struct kernfs_iattrs),
0, SLAB_PANIC, NULL);

kernfs_lock_init();
}

fs/kernfs/inode.c

__kernfs_setattr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __kernfs_setattr(struct kernfs_node *kn, const struct iattr *iattr)
{
struct kernfs_iattrs *attrs;
unsigned int ia_valid = iattr->ia_valid;

attrs = kernfs_iattrs(kn);
if (!attrs)
return -ENOMEM;

if (ia_valid & ATTR_UID)
attrs->ia_uid = iattr->ia_uid;
if (ia_valid & ATTR_GID)
attrs->ia_gid = iattr->ia_gid;
if (ia_valid & ATTR_ATIME)
attrs->ia_atime = iattr->ia_atime;
if (ia_valid & ATTR_MTIME)
attrs->ia_mtime = iattr->ia_mtime;
if (ia_valid & ATTR_CTIME)
attrs->ia_ctime = iattr->ia_ctime;
if (ia_valid & ATTR_MODE)
kn->mode = iattr->ia_mode;
return 0;
}

fs/kernfs/dir.c

__kernfs_new_node 创建一个新的 kernfs_node 节点

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
static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root,
struct kernfs_node *parent,
const char *name, umode_t mode,
kuid_t uid, kgid_t gid,
unsigned flags)
{
struct kernfs_node *kn;
u32 id_highbits;
int ret;
/* 分配并复制节点名称 */
name = kstrdup_const(name, GFP_KERNEL);
if (!name)
return NULL;

kn = kmem_cache_zalloc(kernfs_node_cache, GFP_KERNEL);
if (!kn)
goto err_out1;

idr_preload(GFP_KERNEL);
spin_lock(&kernfs_idr_lock);
/* 分配一个唯一的 inode ID */
ret = idr_alloc_cyclic(&root->ino_idr, kn, 1, 0, GFP_ATOMIC);
/* 说明 ID 分配已经循环回到范围的起点 */
if (ret >= 0 && ret < root->last_id_lowbits)
/* 表示高位 ID 的生成值需要更新,以确保整体 ID 的唯一性 */
root->id_highbits++;
id_highbits = root->id_highbits;
/* 更新 last_id_lowbits,记录最近分配的低位 ID */
root->last_id_lowbits = ret;
spin_unlock(&kernfs_idr_lock);
idr_preload_end();
if (ret < 0)
goto err_out2;
/* 使用位或操作(|)将高位生成值和低位 ID 组合成一个 64 位的唯一标识符。
这种组合方式确保 ID 的唯一性,即使在低位 ID 范围内发生循环分配 */
kn->id = (u64)id_highbits << 32 | ret;

atomic_set(&kn->count, 1);
atomic_set(&kn->active, KN_DEACTIVATED_BIAS);
/* 红黑树节点 */
RB_CLEAR_NODE(&kn->rb);

/* 设置节点名称 */
rcu_assign_pointer(kn->name, name);
kn->mode = mode;
kn->flags = flags;

/* 用户或组属性与默认值不同,调用 __kernfs_setattr 设置属性 */
/* kuid_t(用户 ID):
kuid_t 是内核中用于表示用户 ID 的数据类型。
它封装了用户 ID,并支持在不同命名空间中进行映射。
通过这种封装,内核可以确保用户 ID 在不同命名空间之间的隔离和正确转换。
kgid_t(组 ID):

kgid_t 是内核中用于表示组 ID 的数据类型。
类似于 kuid_t,它支持组 ID 在不同命名空间中的映射和隔离。
这种设计允许内核在多用户环境中安全地管理组权限。 */
if (!uid_eq(uid, GLOBAL_ROOT_UID) || !gid_eq(gid, GLOBAL_ROOT_GID)) {
struct iattr iattr = {
.ia_valid = ATTR_UID | ATTR_GID,
.ia_uid = uid,
.ia_gid = gid,
};

ret = __kernfs_setattr(kn, &iattr);
if (ret < 0)
goto err_out3;
}
/* 节点有父节点,初始化安全属性 */
if (parent) {.
/* return 0 */
ret = security_kernfs_init_security(parent, kn);
if (ret)
goto err_out3;
}

return kn;

err_out3:
spin_lock(&kernfs_idr_lock);
idr_remove(&root->ino_idr, (u32)kernfs_ino(kn));
spin_unlock(&kernfs_idr_lock);
err_out2:
kmem_cache_free(kernfs_node_cache, kn);
err_out1:
kfree_const(name);
return NULL;
}

kernfs_leftmost_descendant 找到指定 kernfs_node 节点的最左后代节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static struct kernfs_node *kernfs_leftmost_descendant(struct kernfs_node *pos)
{
/* 记录当前节点的最后访问位置 */
struct kernfs_node *last;

while (true) {
struct rb_node *rbn;

last = pos;
/* 检查当前节点是否为目录节点(KERNFS_DIR) */
if (kernfs_type(pos) != KERNFS_DIR)
break;
/* 使用 rb_first 获取红黑树中最左的子节点 */
rbn = rb_first(&pos->dir.children);
if (!rbn)
break;
/* 更新 pos 为该子节点 */
pos = rb_to_kn(rbn);
}

return last;
}

kernfs_active 检查 kernfs_node 是否处于激活状态

1
2
3
4
5
6
7
8
9
10
static bool __kernfs_active(struct kernfs_node *kn)
{
return atomic_read(&kn->active) >= 0;
}

static bool kernfs_active(struct kernfs_node *kn)
{
lockdep_assert_held(&kernfs_root(kn)->kernfs_rwsem);
return __kernfs_active(kn);
}

kernfs_activate_one 激活一个 kernfs_node 节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void kernfs_activate_one(struct kernfs_node *kn)
{
lockdep_assert_held_write(&kernfs_root(kn)->kernfs_rwsem);

kn->flags |= KERNFS_ACTIVATED;
/* 节点已经处于激活状态,或者节点被标记为隐藏(KERNFS_HIDDEN)或正在移除(KERNFS_REMOVING) */
if (kernfs_active(kn) || (kn->flags & (KERNFS_HIDDEN | KERNFS_REMOVING)))
return;
/* 如果节点有父节点但红黑树节点为空 */
WARN_ON_ONCE(rcu_access_pointer(kn->__parent) && RB_EMPTY_NODE(&kn->rb));
/* 节点的活动计数(active)不等于 KN_DEACTIVATED_BIAS */
WARN_ON_ONCE(atomic_read(&kn->active) != KN_DEACTIVATED_BIAS);
/* 减少节点的活动计数,移除 KN_DEACTIVATED_BIAS 偏移量。
这表示节点从“未激活”状态转换为“激活”状态 */
atomic_sub(KN_DEACTIVATED_BIAS, &kn->active);
}

kernfs_next_descendant_post 实现 kernfs_node 的后序遍历(post-order traversal)

  • 后序遍历是一种树结构遍历方式,按照“左子树 -> 右子树 -> 根节点”的顺序访问节点。该函数在 kernfs 文件系统中用于遍历指定节点的所有后代节点,并最终访问根节点
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
/**
* kernfs_next_descendant_post - 查找 post-order walk 的下一个后代
* @pos:当前位置(%NULL 启动遍历)
* @root:kernfs_node其后代行走
*
* 查找下一个要访问的后代,以便对 @root 的后代进行后排序遍历。
* @root 包含在迭代和要访问的最后一个节点中。
*
* Return:下一个要访问的后代,完成后为 %NULL。
*/
static struct kernfs_node *kernfs_next_descendant_post(struct kernfs_node *pos,
struct kernfs_node *root)
{
struct rb_node *rbn;

lockdep_assert_held_write(&kernfs_root(root)->kernfs_rwsem);

/* 如果是第一次迭代,则访问最左边的后代,它可能是 root */
if (!pos)
return kernfs_leftmost_descendant(root);

/* 如果我们访问了 @root,则已完成*/
if (pos == root)
return NULL;

/* 如果有未访问的同级,请访问其最左侧的后代
使用 rb_next 获取当前节点的兄弟节点(红黑树中的下一个节点)
如果存在兄弟节点,返回兄弟节点的最左后代节点 */
rbn = rb_next(&pos->rb);
if (rbn)
return kernfs_leftmost_descendant(rb_to_kn(rbn));

/* 如果没有兄弟节点,返回当前节点的父节点。
是后序遍历的特点:在访问完所有子节点后,回到父节点 */
return kernfs_parent(pos);
}

kernfs_activate 显式激活一个 kernfs_node 及其子树节点

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
/**
* kernfs_activate - 激活已启动、已停用的节点
* @kn:要激活其子树kernfs_node
*
* 如果 kernfs_root 设置了 KERNFS_ROOT_CREATE_DEACTIVATED 标志,则新创建的节点默认处于“未激活”状态。
未激活的节点对用户空间不可见,并且在移除时会跳过去激活操作。这种设计允许开发者构建原子初始化序列,在创建多个节点时确保操作的成功或失败是原子的
*
* 调用方负责确保在 @kn 上调用 kernfs_remove*() 后,该函数不被调用。
*/
void kernfs_activate(struct kernfs_node *kn)
{
struct kernfs_node *pos;
struct kernfs_root *root = kernfs_root(kn);
/* 获取 kernfs_rwsem 写锁,确保对节点及其子树的操作是线程安全的 */
down_write(&root->kernfs_rwsem);

pos = NULL;
/* 遍历目标节点 kn 的所有后代节点 */
while ((pos = kernfs_next_descendant_post(pos, kn)))
/* 将其设置为“激活”状态 */
kernfs_activate_one(pos);

/* 释放写锁 */
up_write(&root->kernfs_rwsem);
}

kernfs_create_root 创建和初始化 kernfs 的层级结构

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
/**
* kernfs_create_root - 创建新的 Kernfs 层次结构
* @scops:层次结构的可选 syscall作
* @flags:KERNFS_ROOT_* 标志
* @priv:与新目录关联的不透明数据
*
* Return:成功时为新层次结构的根,失败时为 ERR_PTR() 值。
*/
struct kernfs_root *kernfs_create_root(struct kernfs_syscall_ops *scops,
unsigned int flags, void *priv)
{
struct kernfs_root *root;
struct kernfs_node *kn;

root = kzalloc(sizeof(*root), GFP_KERNEL);
if (!root)
return ERR_PTR(-ENOMEM);

idr_init(&root->ino_idr);
init_rwsem(&root->kernfs_rwsem);
init_rwsem(&root->kernfs_iattr_rwsem);
init_rwsem(&root->kernfs_supers_rwsem);
INIT_LIST_HEAD(&root->supers);

/*
* On 64bit ino setups, id is ino. On 32bit, low 32bits are ino.
* High bits generation. The starting value for both ino and
* genenration is 1. Initialize upper 32bit allocation
* accordingly.
*/
/* 根据系统的 inode 类型(ino_t)大小设置 id_highbits,用于区分 32 位和 64 位系统 */
if (sizeof(ino_t) >= sizeof(u64))
root->id_highbits = 0;
else
root->id_highbits = 1;
/* 创建一个新的 kernfs_node,作为层级结构的根节点 */
kn = __kernfs_new_node(root, NULL, "", S_IFDIR | S_IRUGO | S_IXUGO,
GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
KERNFS_DIR);
if (!kn) {
idr_destroy(&root->ino_idr);
kfree(root);
return ERR_PTR(-ENOMEM);
}

kn->priv = priv;
kn->dir.root = root;

root->syscall_ops = scops;
root->flags = flags;
root->kn = kn;
init_waitqueue_head(&root->deactivate_waitq);

/* KERNFS_ROOT_CREATE_DEACTIVATED 标志,调用 kernfs_activate 激活根节点 */
if (!(root->flags & KERNFS_ROOT_CREATE_DEACTIVATED))
kernfs_activate(kn);

return root;
}

kernfs_new_node 创建一个新的 kernfs_node 节点

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
struct kernfs_node *kernfs_new_node(struct kernfs_node *parent,
const char *name, umode_t mode,
kuid_t uid, kgid_t gid,
unsigned flags)
{
struct kernfs_node *kn;

/* S_ISGID 是一个文件权限标志,表示设置组 ID(Set Group ID)
如果一个可执行文件设置了 S_ISGID 标志,当用户执行该文件时,
进程会继承文件所属组的权限,而不是执行用户的默认组权限
如果一个目录设置了 S_ISGID 标志,目录中创建的新文件会继承目录的所属组,而不是创建用户的默认组*/
if (parent->mode & S_ISGID) {
/* 该代码块模仿了 inode_init_owner() 的行为,
* 用于 kernfs。
*/
if (parent->iattr)
gid = parent->iattr->ia_gid;

if (flags & KERNFS_DIR)
mode |= S_ISGID;
}

kn = __kernfs_new_node(kernfs_root(parent), parent,
name, mode, uid, gid, flags);
if (kn) {
kernfs_get(parent);
rcu_assign_pointer(kn->__parent, parent);
}
return kn;
}

kernfs_name_hash 计算命名空间(ns)和名称字符串(name)的哈希值

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
/**
* kernfs_name_hash - 计算 @ns + @name 的哈希值
* @name: 需要哈希的以空字符结尾的字符串
* @ns: 需要哈希的命名空间标签
*
* 返回值: ns + name 的 31 位哈希值(适合存储在 off_t 中)
*/
static unsigned int kernfs_name_hash(const char *name, const void *ns)
{
/* 使用命名空间(ns)初始化哈希值 */
unsigned long hash = init_name_hash(ns);
unsigned int len = strlen(name);
while (len--)
/* 逐字符计算哈希值 */
hash = partial_name_hash(*name++, hash);
/* 完成哈希计算,生成最终的哈希值 */
hash = end_name_hash(hash);
/* 屏蔽高位,确保哈希值为 31 位 */
hash &= 0x7fffffffU;
/*保留哈希值 0、1 和 INT_MAX 用于特殊目录条目 */
if (hash < 2)
hash += 2;
if (hash >= INT_MAX)
hash = INT_MAX - 1;
return hash;
}

kernfs_sd_compare 比较两个 kernfs_node 节点的名称和命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* -1:当前节点小于目标节点。
1:当前节点大于目标节点。
0:两个节点相等。 */
static int kernfs_name_compare(unsigned int hash, const char *name,
const void *ns, const struct kernfs_node *kn)
{
if (hash < kn->hash)
return -1;
if (hash > kn->hash)
return 1;
if (ns < kn->ns)
return -1;
if (ns > kn->ns)
return 1;
/* 哈希值相同,比较命名空间(ns)。命名空间用于支持节点的隔离和分组 */
return strcmp(name, kernfs_rcu_name(kn));
}

static int kernfs_sd_compare(const struct kernfs_node *left,
const struct kernfs_node *right)
{
/* 提取左节点的哈希值、名称和命名空间,与右节点进行比较 */
return kernfs_name_compare(left->hash, kernfs_rcu_name(left), left->ns, right);
}
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
/**
* kernfs_link_sibling - 将 kernfs_node 链接到兄弟红黑树
* @kn: 相关的 kernfs_node
*
* 将 @kn 链接到以 @kn->parent->dir.children 开始的兄弟红黑树中。
*
* 锁定:
* 独占持有 kernfs_rwsem
*
* 返回值:
* 成功时返回 %0,失败时返回 -EEXIST。
*/
static int kernfs_link_sibling(struct kernfs_node *kn)
{
struct rb_node *parent = NULL;
struct kernfs_node *kn_parent;
struct rb_node **node;

kn_parent = kernfs_parent(kn);
node = &kn_parent->dir.children.rb_node;

while (*node) {
struct kernfs_node *pos;
int result;

pos = rb_to_kn(*node);
parent = *node;
/* 比较当前节点与树中的节点,决定向左子树或右子树移动 */
result = kernfs_sd_compare(kn, pos);
if (result < 0)
node = &pos->rb.rb_left;
else if (result > 0)
node = &pos->rb.rb_right;
else
/* 如果发现重复的节点(比较结果为 0),返回 -EEXIST,表示插入失败 */
return -EEXIST;
}

/* 添加新节点并重新平衡树 */
rb_link_node(&kn->rb, parent, node);
rb_insert_color(&kn->rb, &kn_parent->dir.children);

/* 成功添加,账户子目录编号 */
down_write(&kernfs_root(kn)->kernfs_iattr_rwsem);
/* 插入的节点是目录类型(KERNFS_DIR),增加父节点的子目录计数(subdirs) */
if (kernfs_type(kn) == KERNFS_DIR)
kn_parent->dir.subdirs++;
/* 更新父节点的版本号,反映树结构的变化 */
kernfs_inc_rev(kn_parent);
up_write(&kernfs_root(kn)->kernfs_iattr_rwsem);

return 0;
}

kernfs_add_one 添加 kernfs_node 到父节点

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
/**
* kernfs_add_one - 将 kernfs_node 添加到父节点(无警告)
* @kn: 要添加的 kernfs_node
*
* 调用者必须已经初始化好 @kn->parent。
* 如果 @kn 是目录节点,则该函数会增加父节点 inode 的 nlink,并将其链接到父节点的子节点列表中。
*
* 返回值:
* 成功时返回 0;如果已存在同名条目,则返回 -EEXIST。
*/
int kernfs_add_one(struct kernfs_node *kn)
{
struct kernfs_root *root = kernfs_root(kn);
struct kernfs_iattrs *ps_iattr;
struct kernfs_node *parent;
bool has_ns;
int ret;

down_write(&root->kernfs_rwsem);
parent = kernfs_parent(kn);

ret = -EINVAL;
/* 检查父节点是否启用了命名空间(has_ns) */
has_ns = kernfs_ns_enabled(parent);
/* ,并验证新节点的命名空间是否匹配 */
if (WARN(has_ns != (bool)kn->ns, KERN_WARNING "kernfs: ns %s in '%s' for '%s'\n",
has_ns ? "required" : "invalid",
kernfs_rcu_name(parent), kernfs_rcu_name(kn)))
goto out_unlock;

/* 确保父节点是目录类型(KERNFS_DIR),因为只有目录才能包含子节点 */
if (kernfs_type(parent) != KERNFS_DIR)
goto out_unlock;

ret = -ENOENT;
/* 检查父节点是否处于移除或空目录状态。如果是,则无法添加子节点 */
if (parent->flags & (KERNFS_REMOVING | KERNFS_EMPTY_DIR))
goto out_unlock;

/* 计算新节点的名称哈希值,用于快速查找 */
kn->hash = kernfs_name_hash(kernfs_rcu_name(kn), kn->ns);

/* 将新节点链接到父节点的子节点列表中 */
ret = kernfs_link_sibling(kn);
if (ret)
goto out_unlock;

/* 更新父节点时间戳 */
down_write(&root->kernfs_iattr_rwsem);

ps_iattr = parent->iattr;
if (ps_iattr) {
/* 更新父节点的修改时间(mtime)和创建时间(ctime),反映子节点的添加操作 */
ktime_get_real_ts64(&ps_iattr->ia_ctime);
ps_iattr->ia_mtime = ps_iattr->ia_ctime;
}

up_write(&root->kernfs_iattr_rwsem);
up_write(&root->kernfs_rwsem);

/*
* 除非设置了 CREATE_DEACTIVATED 标志,否则激活新节点。
* 如果此处未激活,则由 kernfs 的使用者负责调用 kernfs_activate() 激活该节点。
* 未激活的节点对用户空间不可见,并且移除时不会触发去激活操作。
*/
if (!(kernfs_root(kn)->flags & KERNFS_ROOT_CREATE_DEACTIVATED))
kernfs_activate(kn);
return 0;

out_unlock:
up_write(&root->kernfs_rwsem);
return ret;
}

kernfs_put 减少 kernfs_node 的引用计数

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
/**
* kernfs_put - 减少 kernfs_node 的引用计数
* @kn: 目标 kernfs_node
*
* 减少 @kn 的引用计数,如果计数达到零则销毁它。
*/
void kernfs_put(struct kernfs_node *kn)
{
struct kernfs_node *parent;
struct kernfs_root *root;

/* 使用 atomic_dec_and_test 原子操作减少引用计数,并检查是否降为零 */
if (!kn || !atomic_dec_and_test(&kn->count))
return;
root = kernfs_root(kn);
repeat:
/*
* Moving/renaming is always done while holding reference.
* kn->parent won't change beneath us.
*/
parent = kernfs_parent(kn);

WARN_ONCE(atomic_read(&kn->active) != KN_DEACTIVATED_BIAS,
"kernfs_put: %s/%s: released with incorrect active_ref %d\n",
parent ? rcu_dereference(parent->name) : "",
rcu_dereference(kn->name), atomic_read(&kn->active));

if (kernfs_type(kn) == KERNFS_LINK)
kernfs_put(kn->symlink.target_kn);

spin_lock(&kernfs_idr_lock);
idr_remove(&root->ino_idr, (u32)kernfs_ino(kn));
spin_unlock(&kernfs_idr_lock);

call_rcu(&kn->rcu, kernfs_free_rcu);

kn = parent;
if (kn) {
if (atomic_dec_and_test(&kn->count))
goto repeat;
} else {
/* just released the root kn, free @root too */
idr_destroy(&root->ino_idr);
kfree_rcu(root, rcu);
}
}
EXPORT_SYMBOL_GPL(kernfs_put);

kernfs_create_dir_ns 创建一个目录

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
/**
* kernfs_create_dir_ns - 创建一个目录
* @parent: 要创建新目录的父目录
* @name: 新目录的名称
* @mode: 新目录的权限模式
* @uid: 新目录的用户 ID
* @gid: 新目录的组 ID
* @priv: 与新目录关联的不透明数据
* @ns: 目录的可选命名空间标签
*
* 返回: 成功时返回创建的节点,失败时返回 ERR_PTR() 值。
*/
struct kernfs_node *kernfs_create_dir_ns(struct kernfs_node *parent,
const char *name, umode_t mode,
kuid_t uid, kgid_t gid,
void *priv, const void *ns)
{
struct kernfs_node *kn;
int rc;

/* allocate */
kn = kernfs_new_node(parent, name, mode | S_IFDIR,
uid, gid, KERNFS_DIR);
if (!kn)
return ERR_PTR(-ENOMEM);

kn->dir.root = parent->dir.root;
kn->ns = ns;
kn->priv = priv;

/* link in */
rc = kernfs_add_one(kn);
if (!rc)
return kn;

kernfs_put(kn);
return ERR_PTR(rc);
}

fs/kernfs/file.c

__kernfs_create_file: kernfs内部的文件创建函数

该函数是kernfs(内核文件系统)的内部核心功能,是sysfs等伪文件系统中所有文件创建操作的最终执行者。它的根本作用是:分配一个新的kernfs_node(内核文件节点)结构体,用调用者提供的所有元数据(名称、权限、所有者、大小、操作回调函数等)来填充它,然后将这个新创建的节点原子地链接到父目录的层级结构中。

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/**
* __kernfs_create_file - kernfs内部用于创建一个文件的函数
* @parent: 要在其中创建文件的目录
* @name: 文件的名称
* @mode: 文件的模式(权限)
* @uid: 文件的用户ID
* @gid: 文件的组ID
* @size: 文件的大小
* @ops: 文件的kernfs操作集(回调函数)
* @priv: 文件的私有数据
* @ns: 文件的可选命名空间标签
* @key: 用于文件active_ref的锁依赖验证密钥, %NULL表示禁用
*
* @return: 成功时返回创建的节点, 失败时返回 ERR_PTR() 编码的错误值.
*/
struct kernfs_node *__kernfs_create_file(struct kernfs_node *parent,
const char *name,
umode_t mode, kuid_t uid, kgid_t gid,
loff_t size,
const struct kernfs_ops *ops,
void *priv, const void *ns,
struct lock_class_key *key)
{
/*
* 定义一个指向 kernfs_node 的指针 kn.
* 它将用来存储新创建的文件节点对象.
*/
struct kernfs_node *kn;
/*
* 定义一个无符号整型变量 flags.
* 用于存储节点的类型标志.
*/
unsigned flags;
/*
* 定义一个整型变量 rc, 用于存储函数调用的返回值.
*/
int rc;

/*
* 设置标志位, 表明要创建的节点类型是文件.
*/
flags = KERNFS_FILE;

/*
* 调用 kernfs_new_node 来分配并初始化一个新的 kernfs_node 结构体.
* parent: 新节点的父节点.
* name: 新节点的名称.
* (mode & S_IALLUGO) | S_IFREG: 设置节点的模式和类型.
* S_IALLUGO 是一个掩码, 用于提取所有的用户/组/其他的权限位.
* S_IFREG 是一个标志, 明确指定节点类型为常规文件.
* uid, gid: 用户和组ID.
* flags: 上面设置的 KERNFS_FILE 标志.
*/
kn = kernfs_new_node(parent, name, (mode & S_IALLUGO) | S_IFREG,
uid, gid, flags);
/*
* 检查 kernfs_new_node 是否因为内存不足(ENOMEM)而失败.
*/
if (!kn)
/*
* 如果分配失败, 使用 ERR_PTR 宏将错误码 -ENOMEM 封装成一个特殊的指针值返回.
*/
return ERR_PTR(-ENOMEM);

/*
* 将驱动提供的文件操作集(ops)赋值给新节点属性(attr)中的ops字段.
*/
kn->attr.ops = ops;
/*
* 将文件大小(size)赋值给新节点属性中的size字段.
*/
kn->attr.size = size;
/*
* 将命名空间标签(ns)赋值给新节点的ns字段.
*/
kn->ns = ns;
/*
* 将私有数据(priv)赋值给新节点的priv字段.
* 这个私有数据指针将上层的sysfs属性(如struct bin_attribute)与底层的kernfs节点关联起来.
*/
kn->priv = priv;

/*
* 这是用于内核锁依赖性验证器(lockdep)的条件编译块.
*/
#ifdef CONFIG_DEBUG_LOCK_ALLOC
/*
* 如果提供了一个有效的锁密钥(key)...
*/
if (key) {
/*
* ...则为这个节点的活动引用计数锁(kn->active)初始化一个锁映射.
* 这使得lockdep可以跟踪此锁的使用情况.
*/
lockdep_init_map(&kn->dep_map, "kn->active", key, 0);
/*
* 并设置 KERNFS_LOCKDEP 标志, 表明此节点受lockdep监控.
*/
kn->flags |= KERNFS_LOCKDEP;
}
#endif

/*
* 为了优化, 将一些关键操作回调函数的存在性缓存到节点的flags字段中.
* 这样内核在处理文件操作时, 只需检查标志位, 而无需间接访问ops指针, 速度更快.
* 仅当持有活动引用(active ref)时, kn->attr.ops 才能被安全访问.
* 而这些标志可以在任何时候被检查.
*/
if (ops->seq_show)
kn->flags |= KERNFS_HAS_SEQ_SHOW;
if (ops->mmap)
kn->flags |= KERNFS_HAS_MMAP;
if (ops->release)
kn->flags |= KERNFS_HAS_RELEASE;

/*
* 调用 kernfs_add_one, 将新创建并填充好的节点 kn 原子地添加到其父节点的目录树中.
*/
rc = kernfs_add_one(kn);
/*
* 检查添加操作是否失败.
*/
if (rc) {
/*
* 如果添加失败, 必须释放之前用 kernfs_new_node 分配的节点 kn, 防止内存泄漏.
* kernfs_put 会减少节点的引用计数, 当计数为0时, 节点被释放.
*/
kernfs_put(kn);
/*
* 将返回的错误码 rc 封装成错误指针并返回.
*/
return ERR_PTR(rc);
}
/*
* 如果一切顺利, 返回指向新创建的 kernfs_node 的指针.
*/
return kn;
}