找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

903

积分

0

好友

117

主题
发表于 23 小时前 | 查看: 0| 回复: 0

作者:Jonathan Corbet,2026 年 1 月 15 日

内核黑客 Al Viro 通常深耕于其核心领域——虚拟文件系统(VFS),一旦他的注意力转向其他模块,往往意味着有值得关注的动静。最近,他发布了一系列针对 slab 分配器及其部分用户的补丁,将触角伸入了内存管理领域。内核开发者们经常为微小的性能提升投入巨大精力,而这次,他们瞄准的目标是在某些内存分配的“热点路径”(hot path)中,消除一次指针解引用操作。

Slab 缓存

内核的 slab 分配器旨在为固定大小的对象提供快速分配。例如,内核使用大量的 dentry 结构(目录项)来缓存文件名信息。在我撰写本文时所用的系统上,根据 /proc/slabinfo 的报告,活跃的 dentry 结构超过 80 万个。分配和释放这些结构的请求非常频繁,因此其性能至关重要。

slab 分配器提供了 kmem_cache_create() 函数,它返回一个指向新创建并初始化的 kmem_cache 结构的指针。随后,可以通过调用 kmem_cache_alloc(),并使用这个指针来分配一个符合该缓存配置大小的新对象。例如,虚拟文件系统(VFS)层就可以使用一个 slab 缓存来分配 dentry 结构。分配器会维护一个可用结构的缓存池,并在请求时发放;它还会努力优化这些结构在从页分配器获取的内存页中的布局。即便在内核中一个简单的操作,也可能涉及多个对象的分配与释放,因此,多年来开发者在优化 slab 分配器上投入了大量精力。

虽然 slab 缓存可以动态创建和销毁,但许多缓存是与系统生命周期共存的。例如,dentry 结构的缓存是在系统引导过程中创建的,该缓存的 struct kmem_cache 指针以 dentry_cache 的形式存储在内核的只读内存区。需要分配或释放 dentry 的代码在编译后,会包含 dentry_cache 这个符号的地址,然后通过该地址获取必须传递给 slab 分配器的实际 kmem_cache 结构指针。大多数情况下,相对于分配一个新对象的开销,这次额外的解引用成本微乎其微。但根据 Viro 的说法,对于使用极其频繁的缓存,它确实产生了可测量的性能影响。

因此,消除这次解引用操作颇具吸引力,而且理论上可行。dentry_cache 指针的值是恒定的,一旦设置,在系统整个生命周期内都不会改变。我们需要做的,仅仅是在内核二进制文件中,将所有出现 dentry_cache 地址的地方,都直接替换成存储在那里的 kmem_cache 结构的实际地址。

运行时常量

然而,上述关于如何访问 dentry 缓存 slab 的描述,对当前内核来说并不完全准确。如果你查看 6.19-rc 内核中的 fs/dcache.c,会发现 slab 指针是这样声明的:

static struct kmem_cache *__dentry_cache __ro_after_init;
#define dentry_cache runtime_const_ptr(__dentry_cache)

指向 dentry 结构 slab 缓存的指针,实际上存储在一个名为 __dentry_cache 的变量中;而未加修饰的 dentry_cache 名称是由第二行的 #define 创建的。这段声明序列展示了由 Linus Torvalds 在 6.11 内核中引入的“运行时常量”(runtime constant)机制。他一直没来得及正式记录这个新功能——想必这一定排在他待办事项列表的前列——所以开发者们不得不对其进行逆向工程。简而言之,运行时常量完成了我们上文期望的工作:它在运行时直接将地址“修补”(patch)到代码指令中,从而避免了解引用操作。

要将一个指针设为运行时常量,第一步是像上面那样使用 runtime_const_ptr() 宏来声明它。该宏返回一个值,通过 #define 将其绑定到代码其余部分用于该指针的名称(本例中是 dentry_cache)。还有其他的宏用于设置运行时常量的值;对于 dentry slab,该常量在 dcache_init() 函数中使用 runtime_const_init() 进行设置:

__dentry_cache = KMEM_CACHE_USERCOPY(dentry,
                     SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|SLAB_ACCOUNT,
                     d_shortname.string);
runtime_const_init(ptr, __dentry_cache);

kmem_cache 是通过 KMEM_CACHE_USERCOPY() 分配的(一个封装了 kmem_cache_create() 的宏);产生的指针随后被用来设置运行时常量的值。这种初始化将导致内核代码中任何引用 dentry_cache 的指令,都直接包含那个 kmem_cache 指针的常量值。这样,额外的解引用就被消除了——这也正是最初引入运行时常量机制的目的。

看起来问题似乎已经解决了?但这种解决方案存在几个不足之处。首先,并非所有硬件架构都支持运行时常量,尽管主流的架构大多支持。更重要的是,这种机制仅在系统引导过程中有效;一旦系统完全启动,就无法再修改内核代码段来反映运行时常量的实际值。这反过来意味着,运行时常量不能在可加载的内核模块中使用。

静态 kmem_cache 结构

Viro 决定另辟蹊径,专门针对 slab 问题提出解决方案,而不是尝试修补运行时常量机制的缺陷。他的思路是:如果在调用者端静态分配 kmem_cache 结构,那么让内核代码直接包含指向所需结构的指针就变得轻而易举,完全无需运行时的代码修补。这个结构的地址在编译时就成为常量。即使是可加载模块也能利用这一特性,至少对于在模块内部创建和管理的 slab 缓存是这样。

需要克服的一个小障碍是,struct kmem_cache 的定义对 slab 分配器之外的代码是隐藏的,这有充分的理由(避免内部细节泄露)。但这使得在内核其他地方声明这种结构变得困难。解决问题的关键在于认识到,这段代码实际上只需要分配一块足够容纳 struct kmem_cache 结构的内存。因此,Viro 的补丁系列引入了一个新类型 struct kmem_cache_opaque,其定义方式确保它与 struct kmem_cache 大小相同,但不会暴露后者的任何内部细节。此外,还有一个新的宏 to_kmem_cache(),用于将指向这个“不透明”形式结构的指针,转换为 slab 子系统所期望的常规类型。

通过这些修改,dentry_cache 的声明变成了:

static struct kmem_cache_opaque __dentry_cache;
#define dentry_cache to_kmem_cache(&__dentry_cache)

为了将一个子系统转换为使用静态 kmem_cache 结构,还需要进行一些其他更改。通常对 kmem_cache_create() 的调用,现在要改为携带相同参数的 kmem_cache_setup() 调用。(在 dentry 缓存的例子中,更专业的 KMEM_CACHE_USERCOPY() 宏变成了 KMEM_CACHE_SETUP_USERCOPY())。除此之外,用于分配和释放对象的代码无需任何改动即可正常工作。

为了让这一特性在内核模块中发挥作用,还需要进行一些额外的铺垫工作,以确保在卸载创建了这些静态缓存的模块之前,相关的清理工作已经完成。在 slab 分配器内部,主要的改动是记录这些预分配的 kmem_slab 结构,这样 slab 代码就不会尝试自行分配或释放它们。当然,静态分配的 slab 缓存也无法与其他缓存合并。

该补丁系列已经将核心内核和多个文件系统子系统中的相当一部分缓存,转换成了这种静态变体。目前还没有公开的基准测试结果来展示具体的性能提升幅度。Linus Torvalds 对这个补丁集表示赞赏,称其在处理这类事务上比之前的 runtime_const 机制“好得多”。到目前为止,内存管理领域的其他核心开发者还没有发表太多反对意见。假设后续没有重大异议,这项改进进入主线内核的道路看起来相对平坦。

社区观点聚焦

云栈社区的相关讨论中,开发者们对此话题反应热烈:

  • 许多开发者认为,静态分配 kmem_cache 结构能有效简化关键路径的代码生成,提升效率。
  • 大家深入讨论了这种方案在不同硬件架构上的通用性,以及对内核模块的特殊支持是否足够完备。
  • 部分读者表达了担忧,认为静态分配可能导致内存对齐问题,并且会使缓存合并机制失效,可能带来一些限制。
  • 社区普遍认可这是一种比运行时常量更直接、也更易于长期维护的优化思路。

总体来看,社区对 Al Viro 提出的这种通过静态分配来规避解引用的方案持肯定态度,认为它在保持内核代码清晰整洁的同时,实现了极致的性能优化。

LWN 文章遵循 CC BY-SA 4.0 许可协议。




上一篇:从程序员内卷到AI出海:我在生财有术年会分享的日入1000刀实战复盘
下一篇:工业设计方法论:从工具到思维,柳冠中谈设计师的认知升维之路
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-30 23:19 , Processed in 0.274654 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表