LWN:5.17 中的struct slab!
关注了就能看到更多这么棒的文章哦~
Struct slab comes to 5.17
By Jonathan Corbet
January 14, 2022
DeepL assisted translation
https://lwn.net/Articles/881039/
正在进行的 memory folio 工作已经在内核的大部分地方产生了波及,并激发出了一些辅助项目(side project),其中之一就是从 struct page 中移除 slab 特定的一些字段。这项工作在 5.17 版本的内核中已被合入 mainline,因此,现在是一个很好的时机来了解 struct slab 的状况,以及了解为什么这项工作很重要了。
struct page and struct slab
page structure 是内存管理子系统的核心。系统中的每一个物理内存 page 都对应着这样一个结构,用它们来跟踪相应内存的状态,随着内存在系统的生命周期中被使用、释放和重用,都需要用到它。随着时间的推移,物理 page 可以采用许多不同的形式了,它们可以放置用户空间的数据,或者内核数据结构,又或者是 DMA buffer 等等。不管某个 page 是如何使用的,都是使用 struct page 来作为跟踪其状态的数据结构。这些 struct 存放在一个 discontinuous array 中(也称为 system memory map )。
这种做法也出现了一些问题。4.18 版本对 page structure 进行了大规模的重写,但 struct page 的定义仍然包含很多 #ifdefs 并且使用了 unions 来构成的一个复杂的混合物,没有任何机制来确保人们总是能使用正确的字段(field )。开发者如果需要在这个结构中利用一些字节的话,很不幸,很难确定出哪些 bit 可以安全地使用起来。子系统通常在设计上都会隐藏其内部的数据结构,但是 struct page 却在整个内核里面到处都在使用,因此导致在内存管理中做任何改变都面临着更加复杂的情况。人们希望通过确保不用每个 page 都分配一个 structure 来减少 page struct 所占用的内存总量,不过在目前的代码组织情况下只是一个遥远的梦想。
因此,有很多好理由来推动人们要从 struct page 里面删除掉更多的内容,并将仍然保留的信息隐藏在内存管理子系统内部。对 folio 的讨论得出的结果之一是,人们希望重新处理一下 struct page,但这个工作是一件很可怕的事情,或者说不耐烦的人无法完成的工作。要达成这个目标就需要很多步骤。在 5.16 版本中把 folio 的初始 patch 合并,就是其中的一个步骤。5.17 版本中的 struct slab 出现,则是另一个步骤。
内核中有许多内存分配器(memory allocator),其中有两个是内存管理子系统的最核心功能,负责了正在运行的系统中的大部分内存分配工作。page allocator 只会以 page 为单位进行内存管理,正如它的名字所示,因此当需要大量的内存时就会使用它来分配。而 slab allocator 则可以高效处理较小对象的分配,包括那些用 kmalloc()等函数来进行的内存分配。slab allocator 会从 page allocator 中获得一个或多个 page 组成的一块连续内存,然后把这个大块内存分割成很小块内存,返回给申请内存的用户。实际上,内核里支持三种 slab allocator(下面会提到),但必须在 kernel config 的时候就从中确定选择哪一个。
当 slab allocator 分配 page 时,这些 page 在相关的 struct page 里面都会标记成 slab page,而这些结构中的许多字段的含义也会因此而改变。slab 相关的特定信息实际上并不是必须要在 struct page 中,而且 slab allocator 也不应该需要访问该结构中的其他信息,但事实是目前这些信息都被混在了一起。
Changes for 5.17
人们想要纠正这种情况,第一步就是把 struct slab 分离独立出来。目前,slab struct 实际上是 page struct 的一个 overlay,因此事实上仍在使用同一片内存空间,但新的 slab struct 就把 struct page 隐藏了起来,并限制 slab allocator 只使用存储在那里的 slab 相关的特定数据。这项工作最初是由 Matthew Wilcox 完成的,作为了 folio 工作的一部分,后来由 Vlastimil Babka 接手并推动其完成。
内核目前支持三种板块分配器。SLAB(最早版本的 allocator),SLUB(一个较新开发的分配器,专注于提高可扩展性,通常用在除了嵌入式应用之外的地方),以及 SLOB(一个用于内存非常有限的系统的很小的 allocator)。内核会使用的 allocator 需要在构建时通过 config 选项来指明。Babka 对 patch set 所做的改变之一就是进一步缩小了 slab struct 的定义,使其只包括当前系统中选中的 allocator 所需的那些字段。目前还是使用带有若干 #ifdef 代码的唯一一个 struct slab 定义,但建议大家去看看去掉 #ifdef 之后的最终样子。如果选择了 SLAB allocator,struct slab 的最终结果是这样的:
struct slab {
unsigned long __page_flags;
union {
struct list_head slab_list;
struct rcu_head rcu_head;
};
struct kmem_cache *slab_cache;
void *freelist; /* array of free object indexes */
void *s_mem; /* first object */
unsigned int active;
atomic_t __page_refcount;
#ifdef CONFIG_MEMCG
unsigned long memcg_data;
#endif
};
(在LWN原文,跟具体 allocator 相关的特有字段以粗体显示)。如果选择的是 SLUB,这个结构就变成了:
struct slab {
unsigned long __page_flags;
union {
struct list_head slab_list;
struct rcu_head rcu_head;
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct {
struct slab *next;
int slabs; /* Nr of slabs left */
};
#endif
};
struct kmem_cache *slab_cache;
/* Double-word boundary */
void *freelist; /* first free object */
union {
unsigned long counters;
struct {
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
};
};
unsigned int __unused;
atomic_t __page_refcount;
#ifdef CONFIG_MEMCG
unsigned long memcg_data;
#endif
};
如果用的是 SLOB,则是:
struct slab {
unsigned long __page_flags;
struct list_head slab_list;
void *__unused_1;
void *freelist; /* first free block */
long units;
unsigned int __unused_2;
atomic_t __page_refcount;
#ifdef CONFIG_MEMCG
unsigned long memcg_data;
#endif
};
这种组织结构有助于确保一个 slab allocator 不会去意外地使用属于另一个 slab allocator 的特有字段,从而在类型安全(type safety)方面带来更多好处。
新改过的这个结构定义在了 mm/slab.h 中,它不在 include 之下,因此它不能被内存管理子系统之外的代码所用。这给 x86 bootmem allocator 以及 zsmalloc() 带来了麻烦,因为它们都在使用 page struct 中的 slab 相关的一些字段,尽管这些代码并不是一个 slab allocator。这部分代码已经被改为使用 struct page 中的其他字段,而且还添加了注释,提醒说这种用法应该在今后的某一天被清理掉。
同时,slab allocator 中的代码也被修改为使用新的结构,在调用栈的一开始就改成使用新的这个 struct。这就将 slab 的大部分代码与 struct page 隔离开来,为今后的工作铺平了道路,也就是未来可以完全分离这两种 struct 了,并允许根据需要来动态分配 slab structure。
最终结果是为 slab allocator 在系统的 memory map 中有了更清晰的视图,开始将它们与底层的内存管理细节分开,并增加了 type safety (类型安全)。同时,Linux 用户应该不会受到任何影响,并且如果幸运的话,今后的 bug 数量也会有所减少。在更远的将来,可能会有这样一个时刻:struct slab 可以被动态分配并完全从 memory map 中分离出来。不过这种改动还需要一段时间。同时,清理内存管理的核心类型则是朝着正确方向迈出的一步。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~