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


title: kernfs

categories:

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

文章目录

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 测试是否启用了命名空间

c 复制代码
/**
 * kernfs_ns_enabled - 测试是否启用了命名空间
 * @kn: 要测试的节点
 *
 * 测试是否为 @ns 的子节点启用了命名空间过滤。
 */
static inline bool kernfs_ns_enabled(struct kernfs_node *kn)
{
	return kn->flags & KERNFS_NS;
}

kernfs_type 获取 kernfs_node 的类型

c 复制代码
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 的名称

c 复制代码
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 的版本号

c 复制代码
static inline void kernfs_inc_rev(struct kernfs_node *parent)
{
	parent->dir.rev++;
}

fs/kernfs/mount.c

kernfs_initkernfs_init

c 复制代码
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

c 复制代码
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 节点

c 复制代码
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 节点的最左后代节点

c 复制代码
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 是否处于激活状态

c 复制代码
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 节点

c 复制代码
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 文件系统中用于遍历指定节点的所有后代节点,并最终访问根节点
c 复制代码
/**
 * 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 及其子树节点

c 复制代码
/**
 * 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 的层级结构

c 复制代码
/**
 * 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 节点

c 复制代码
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)的哈希值

c 复制代码
/**
 *	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 节点的名称和命名空间

c 复制代码
/* -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);
}
c 复制代码
/**
 *	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 到父节点

c 复制代码
/**
 *	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 的引用计数

c 复制代码
/**
 * 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 创建一个目录

c 复制代码
/**
 * 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(内核文件节点)结构体,用调用者提供的所有元数据(名称、权限、所有者、大小、操作回调函数等)来填充它,然后将这个新创建的节点原子地链接到父目录的层级结构中。

c 复制代码
/**
 * __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;
}
相关推荐
代码游侠2 小时前
学习笔记——IO多路复用技术
linux·运维·数据库·笔记·网络协议·学习
比奇堡派星星2 小时前
Linux Hotplug 机制详解
linux·开发语言·驱动开发
m0_485614673 小时前
Linux-容器基础2
linux·运维·服务器
QT 小鲜肉3 小时前
【Linux命令大全】001.文件管理之mattrib命令(实操篇)
linux·运维·服务器·chrome·笔记
华舞灵瞳3 小时前
学习FPGA(八)快速傅里叶变换
学习·fpga开发
鸠摩智首席音效师3 小时前
如何在 Linux 上自动清理 Journalctl 日志 ?
linux·运维·服务器
褪色的博客3 小时前
强化学习入门:价值学习——从“试错”到“预判”的飞跃
学习
鸠摩智首席音效师4 小时前
如何在 Linux 下以 www-data 用户运行 Crontab ?
linux·运维·服务器
wdfk_prog4 小时前
[Linux]学习笔记系列 -- [fs]inode
linux·笔记·学习