关注了就能看到更多这么棒的文章哦~
Pulling slabs out of struct page
By Jonathan Corbet
October 8, 2021
DeepL assisted translation
https://lwn.net/Articles/871982/
目前,在内存管理子系统中加入 folio 概念的工作看起来似乎已经停滞不前了,但其实并不是这样的。许多关于 folio 的讨论已经产生了一些共识,比如说大家都认为内核中有过多的地方在使用 struct page 了。Matthew Wilcox 在一个由 62 部分组成的 patch 中将 slab allocator 拆分了出来,这就是如何在子系统中摆脱 struct page 的一个例子。这组 patch 的最终命运可能可以预示出内存管理子系统是否将发生变化。
内核针对它所管理的每一个物理内存 page 都维护了一个 page structure。在一个典型的具有 4KB page size 的系统中,这意味着内核需要管理数百万个这样的结构。每个 page structure 都在告知内核它所指向的 page 的状态,包括它是如何被使用的、当前存在有多少对它的引用、它在各种队列中的位置等等。内核所需要的信息各不相同,主要取决于这个 page 目前是如何被使用的。为了能适应这种用法,page structure 就定义成了一个复杂的 struct 和 union 的混杂体。struct page 当前的定义就好像是万圣节前的一本吓人的书。如果有人想真正感受一下的话,可以看看 Wilcox 针对 4.18 版本进行清理之前的样子。
struct page 的用户之一就是一组 slab allocators,它们会从内核中获取由 page 组成的 block("slab"),并以较小的、固定大小的单位(chunk)来分配出去。这些 allocator 在内核中得到了大量应用,它们的性能会影响整个系统的性能,所以不出意料它们的实现就深入到内存管理子系统中的最底层了。为了支持这种用法,page structure 中的许多字段都是专门为 slab allocator 来设计的。并且情况其实更加复杂,因为内核有三种 slab allocator:SLAB(这是最早期的分配器,经常用在 Android 中),SLUB(经常被用于桌面环境和数据中心的系统中),以及 SLOB(一个用于嵌入式系统的更小型的分配器实现)。每种分配器对 struct page 中的字段都有自己的需求。
Wilcox 的 patch set 通过移除 struct page 中的相关字段来创建一个新的 struct slab。这个新增结构的定义如下:
struct slab {
unsigned long flags;
union {
struct list_head slab_list;
struct { /* Partial pages */
struct slab *next;
#ifdef CONFIG_64BIT
int slabs; /* Nr of slabs left */
int pobjects; /* Approximate count */
#else
short int slabs;
short int pobjects;
#endif
};
struct rcu_head rcu_head;
};
struct kmem_cache *slab_cache; /* not slob */
/* Double-word boundary */
void *freelist; /* first free object */
union {
void *s_mem; /* slab: first object */
unsigned long counters; /* SLUB */
struct { /* SLUB */
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
};
};
union {
unsigned int active; /* SLAB */
int units; /* SLOB */
};
atomic_t _refcount;
#ifdef CONFIG_MEMCG
unsigned long memcg_data;
#endif
};
可以看出,这个 structure 仍然严重依赖 union 来将每种分配器中需要与 page 一起存储的信息叠加起来存放。这当然可以通过将该结构分割成三个结构分别针对不同的分配器来解决,但这将给已经很大的 patch set(并且在不断增长)增加复杂度。
值得注意的是,slab 结构实际上是一个伪装过的 page structure;slab 结构的实例将内核 memory page 中相应的 page 结构进行了 overlay 了。从本质上讲,它是内核对那些由 slab 分配器所拥有的 page 的 struct page 的视角,把它从跟对 struct page 的其他视角混杂在一起的情况中剥离出来。这意味着 slab structure 的 layout 设计必须非常谨慎,因为有些字段(例如_refcount)是与 page 结构共享的,如果算错了它的位置的话,肯定会有不幸的结果。为了确保不发生这样的灾难,Wilcox 加入了一组编译时的检查,来验证这些共享字段的偏移量是否正确。
在这之后的其余 patch 是将 slab allocator(以及其他东西)里的各种代码都转换为使用新的结构。SLUB 的转换工作进行得非常严谨,分为了 40 多个小步骤来完成。Wilcox 将其他分配器的转换工作描述为 "蜻蜓点水" 式的,只用一个 patch 就完成了。据推测,后续再更新的 patch set 中将把这些概念验证式的 patch 变成相应的合适版本,但并不完全清楚相关的负责人应该是谁。Wilcox 在 patch 开篇就提到:
我对 slab allocator 不是非常非常了解,所以如果有哪位 slab maintainer 能接手这项工作的话,我将非常感激。这有点偏离了我设计 folio 的真正目的,尽管这个过程中也已经发现了一个小 bug。
截至目前,没有任何 slab maintainer(他们的时间也可能确实非常有限)对这一请求作出回应。
把一个旧的结构放到一个新的之中,看起来是一个很大的工作,但有很多理由需要这样做。仅仅是将 slab 特定的字段从 struct page 中抽出这一点就大大简化了 struct page。而使用了一个专门的数据类型的话,就可以清楚地知道代码希望处理哪种类型的 page structure,并且在 type safety 方面也增加了一定程度的安全性,不再可能意外地访问了错误的 union 字段。
但是最大的好处完全是来自于开始将 slab allocator 与 page structure 分离开。最终有可能将 slab structure 完全从 page structure 中分离出来,并对其进行动态分配。这将是在核心的内存管理代码中对 struct page 进行封装的一小步进展,可以将其隐藏起来不再被内核的其他部分使用。这个改动将缓解目前亟待解决的任务:改进 page-level memory management。
不过,这项工作首先需要把把现有的改动合入 mainline 内核。这应该是一个比合并 folio 改动更加容易的过程,但是每次将这种大改动合入内存管理代码的过程从来都不是很容易。到目前为止,对这项工作的反馈比较少,这有点令人担忧,特别是在 Wilcox 已经在请求帮助的情况下仍然如此。但这一系列的工作还处于早期阶段。不管 struct slab 的表现如何,它为内存管理开发人员指出了一个努力的方向。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~