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 伪文件系统核心框架(Pseudo Filesystem Core Framework) sysfs和cgroupfs的底层基石)
- include/linux/kernfs.h
-
- [kernfs_ns_enabled 测试是否启用了命名空间](#kernfs_ns_enabled 测试是否启用了命名空间)
- [kernfs_type 获取 kernfs_node 的类型](#kernfs_type 获取 kernfs_node 的类型)
- fs/kernfs/kernfs-internal.h
-
- [kernfs_rcu_name 获取 kernfs_node 的名称](#kernfs_rcu_name 获取 kernfs_node 的名称)
- [kernfs_inc_rev 增加 kernfs_node 的版本号](#kernfs_inc_rev 增加 kernfs_node 的版本号)
- fs/kernfs/mount.c
- fs/kernfs/inode.c
- fs/kernfs/dir.c
-
- [__kernfs_new_node 创建一个新的 kernfs_node 节点](#__kernfs_new_node 创建一个新的 kernfs_node 节点)
- [kernfs_leftmost_descendant 找到指定 kernfs_node 节点的最左后代节点](#kernfs_leftmost_descendant 找到指定 kernfs_node 节点的最左后代节点)
- [kernfs_active 检查 kernfs_node 是否处于激活状态](#kernfs_active 检查 kernfs_node 是否处于激活状态)
- [kernfs_activate_one 激活一个 kernfs_node 节点](#kernfs_activate_one 激活一个 kernfs_node 节点)
- [kernfs_next_descendant_post 实现 kernfs_node 的后序遍历(post-order traversal)](#kernfs_next_descendant_post 实现 kernfs_node 的后序遍历(post-order traversal))
- [kernfs_activate 显式激活一个 kernfs_node 及其子树节点](#kernfs_activate 显式激活一个 kernfs_node 及其子树节点)
- [kernfs_create_root 创建和初始化 kernfs 的层级结构](#kernfs_create_root 创建和初始化 kernfs 的层级结构)
- [kernfs_new_node 创建一个新的 kernfs_node 节点](#kernfs_new_node 创建一个新的 kernfs_node 节点)
- [kernfs_name_hash 计算命名空间(ns)和名称字符串(name)的哈希值](#kernfs_name_hash 计算命名空间(ns)和名称字符串(name)的哈希值)
- [kernfs_sd_compare 比较两个 kernfs_node 节点的名称和命名空间](#kernfs_sd_compare 比较两个 kernfs_node 节点的名称和命名空间)
- [kernfs_link_sibling 将一个新的 kernfs_node 节点插入到其父节点的红黑树(rbtree)中](#kernfs_link_sibling 将一个新的 kernfs_node 节点插入到其父节点的红黑树(rbtree)中)
- [kernfs_add_one 添加 kernfs_node 到父节点](#kernfs_add_one 添加 kernfs_node 到父节点)
- [kernfs_put 减少 kernfs_node 的引用计数](#kernfs_put 减少 kernfs_node 的引用计数)
- [kernfs_create_dir_ns 创建一个目录](#kernfs_create_dir_ns 创建一个目录)
- fs/kernfs/file.c
-
- [__kernfs_create_file: kernfs内部的文件创建函数](#__kernfs_create_file: kernfs内部的文件创建函数)
kernfs 伪文件系统核心框架(Pseudo Filesystem Core Framework) sysfs和cgroupfs的底层基石
历史与背景
这项技术是为了解决什么特定问题而诞生的?
kernfs (Kernel File System) 是一个为了解决在实现伪文件系统(pseudo filesystem)时遇到的内部复杂性和锁竞争问题 而被创造出来的内核核心框架。它的诞生主要源于其前身及主要用户------sysfs------所暴露出的设计缺陷:
- 锁机制复杂且易于死锁 :在 kernfs 出现之前,
sysfs的实现与 VFS(虚拟文件系统)层紧密耦合。sysfs中的目录和文件直接对应于内核的dentry和inode对象。VFS 自身的锁机制(特别是d_lock和i_mutex)非常复杂,当sysfs的属性文件读写操作需要回调到驱动程序,而驱动程序又可能需要获取其他与 VFS 相关的锁时,就极易形成复杂的锁依赖链,导致死锁(deadlock)。这是sysfs长期以来最头疼的问题之一。 - 数据结构臃肿 :直接使用
dentry和inode来表示sysfs中的每一个节点,对于这种只有元数据、没有实际数据的伪文件系统来说,是一种浪费。这些 VFS 结构体包含了大量sysfs根本用不到的字段,增加了内存消耗。 - 生命周期管理困难 :
sysfs节点的生命周期与 VFS 对象的生命周期紧密绑定,而 VFS 的缓存机制(dcache)使得节点的销毁时机变得不确定,这给需要精确控制节点创建和移除的驱动开发者带来了困难。 - 缺乏清晰的抽象 :
sysfs的实现混杂了通用逻辑和 VFS 的特定实现细节,使得代码难以理解和维护,也无法方便地被其他需要类似功能的伪文件系统(如 cgroupfs)复用。
kernfs 的诞生就是为了将伪文件系统的内部逻辑实现 与VFS 的表现层彻底分离开,从而解决上述所有问题。
它的发展经历了哪些重要的里程碑或版本迭代?
kernfs 的发展是一个典型的内核重构过程:
- 构思与实现 :由内核开发者 Tejun Heo 主导,旨在创建一个通用的、轻量级的框架来构建层次化的伪文件系统,其首要目标就是修复
sysfs的锁问题。 - 内核引入:kernfs 在 Linux 内核 3.14 版本中被正式引入。这是一个重要的里程碑,标志着内核拥有了一个专门用于构建伪文件系统的标准工具集。
- sysfs 迁移 :在引入 kernfs 之后,一个庞大的工程就是将现有的
sysfs实现完全迁移到 kernfs 之上。这意味着sysfs不再直接操作dentry和inode,而是通过 kernfs 作为中间层。这个迁移工作跨越了多个内核版本。 - 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 视图(标准的目录和文件)分离开来。
- 独立的内部数据结构 :kernfs 定义了自己的核心节点结构
struct kernfs_node。这个结构体非常轻量,只包含维护层次结构所必需的信息,如父节点、子节点和兄弟节点的指针、节点名称、类型(目录或文件)以及访问权限等。所有sysfs或cgroupfs的节点在内部都表现为一个kernfs_node树。 - 解耦的锁机制 :kernfs 实现了一个非常简单的全局读写信号量(
kernfs_rwsem)。所有改变 kernfs 树结构的操作(如创建、删除、重命名节点)都需要获取这个信号量的写锁。而所有遍历或查找操作只需要获取读锁。这种"一个锁管所有结构变化"的模式,虽然看起来粗暴,但彻底切断了与 VFS 锁的复杂依赖,从根本上消除了死锁的风险。属性文件的读写操作则不触及这个锁。 - 按需实例化的VFS对象 :kernfs 节点树独立存在于内核中。只有当用户空间的进程通过
ls,cat等命令实际访问到某个sysfs路径时,VFS 层才会回调到 kernfs,kernfs 此时才会按需 地为被访问的kernfs_node创建一个对应的inode和dentry,从而在用户面前呈现为一个正常的文件或目录。这些 VFS 对象仅仅是 kernfs 内部节点的"视图"或"代理"。 - 操作的委托 :当用户对
sysfs文件进行读写时,VFS 调用会通过实例化的inode找到其背后的kernfs_node,kernfs 再将操作委托给当初创建这个节点时所注册的回调函数(struct kernfs_ops),最终调用到设备驱动中相应的show/store函数。
它的主要优势体现在哪些方面?
- 死锁免疫:简单而独立的锁模型从根本上解决了与VFS纠缠不清导致的死锁问题,这是其最大的优势。
- 轻量高效 :使用专用的
kernfs_node代替通用的inode/dentry来管理内部结构,减少了内存占用。 - 清晰的抽象和复用 :提供了一套清晰的API(如
kernfs_create_dir,kernfs_create_file),将伪文件系统的实现细节封装起来,使得sysfs和cgroupfs的实现都变得更加简洁,并且逻辑可以复用。 - 精确的生命周期控制:由于内部结构独立于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) :
procfs和debugfs有其自身的、历史悠久的实现方式,虽然它们在功能上与sysfs有相似之处,但它们并没有被迁移到 kernfs 上,主要是因为迁移成本巨大且收益不明显。
对比分析
请将其 与 其他相似技术 进行详细对比。
kernfs 最直接的对比对象就是它所取代的旧的、直接基于VFS的sysfs实现方法。
| 特性 | kernfs 框架 | 旧的直接基于VFS的实现 |
|---|---|---|
| 核心数据结构 | 使用轻量级的 struct kernfs_node 管理内部层次结构。inode和dentry仅作为按需创建的视图。 |
直接使用 VFS 的 struct inode 和 struct dentry 作为核心数据结构。 |
| 锁模型 | 一个独立的全局读写信号量(kernfs_rwsem)保护所有结构性变更,与 VFS 锁完全解耦。 |
严重依赖并交织于 VFS 的锁,如 dcache_lock, inode->i_mutex 等,极易导致死锁。 |
| 抽象层次 | 高。提供了清晰的API,将伪文件系统逻辑与VFS表现层分离。 | 低。伪文件系统的实现逻辑与VFS的实现细节紧密耦合。 |
| 内存占用 | 较低 。kernfs_node 比 inode+dentry 的组合更小。 |
较高。通用VFS数据结构中存在大量未被使用的字段。 |
| 生命周期管理 | 确定。节点的创建和销毁由驱动程序通过API精确控制。 | 不确定。受VFS的dcache缓存策略影响,销毁时机不可预测。 |
| 可复用性 | 高 。作为一个通用框架,被 sysfs 和 cgroupfs 共同使用。 |
低 。实现与 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);
}
kernfs_link_sibling 将一个新的 kernfs_node 节点插入到其父节点的红黑树(rbtree)中
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;
}