zsmalloc¶
这个分配器是为 zram 设计的。因此,该分配器应该在低内存条件下工作良好。特别地,它从不尝试高阶页面分配,因为在高内存压力下,这很可能失败。另一方面,如果我们只使用单个(0阶)页面,它将遭受非常高的碎片化——任何大小为 PAGE_SIZE/2 或更大的对象将占据整个页面。这是其前身 (xvmalloc) 的主要问题之一。
为了克服这些问题,zsmalloc 分配了一堆 0 阶页面,并使用各种“struct page”字段将它们链接在一起。这些链接的页面充当单个高阶页面,即一个对象可以跨越 0 阶页面边界。代码将这些链接的页面称为一个单一实体 zspage。
为了简单起见,zsmalloc 只能分配大小最大为 PAGE_SIZE 的对象,因为这满足了其所有当前用户的要求(在最坏的情况下,页面是不可压缩的,因此以“原样”形式存储,即以未压缩的形式存储)。对于大于此大小的分配请求,将返回失败(请参阅 zs_malloc)。
此外,zs_malloc()
不返回可解引用的指针。相反,它返回一个不透明的句柄(无符号长整型),该句柄对已分配对象的实际位置进行编码。这种间接的原因是 zsmalloc 不会永久映射 zspages,因为这会在 32 位系统上导致问题,在 32 位系统中,内核空间映射的 VA 区域非常小。因此,使用分配的内存应该通过基于句柄的适当 API 来完成。
stat¶
使用 CONFIG_ZSMALLOC_STAT,我们可以通过 /sys/kernel/debug/zsmalloc/<user name>
查看 zsmalloc 内部信息。以下是一个 stat 输出的示例
# cat /sys/kernel/debug/zsmalloc/zram0/classes
class size 10% 20% 30% 40% 50% 60% 70% 80% 90% 99% 100% obj_allocated obj_used pages_used pages_per_zspage freeable
...
...
30 512 0 12 4 1 0 1 0 0 1 0 414 3464 3346 433 1 14
31 528 2 7 2 2 1 0 1 0 0 2 117 4154 3793 536 4 44
32 544 6 3 4 1 2 1 0 0 0 1 260 4170 3965 556 2 26
...
...
- class
index
- size
对象大小 zspage 存储
- 10%
使用率低于 10% 的 zspage 数量(见下文)
- 20%
使用率在 10% 到 20% 之间的 zspage 数量
- 30%
使用率在 20% 到 30% 之间的 zspage 数量
- 40%
使用率在 30% 到 40% 之间的 zspage 数量
- 50%
使用率在 40% 到 50% 之间的 zspage 数量
- 60%
使用率在 50% 到 60% 之间的 zspage 数量
- 70%
使用率在 60% 到 70% 之间的 zspage 数量
- 80%
使用率在 70% 到 80% 之间的 zspage 数量
- 90%
使用率在 80% 到 90% 之间的 zspage 数量
- 99%
使用率在 90% 到 99% 之间的 zspage 数量
- 100%
使用率为 100% 的 zspage 数量
- obj_allocated
已分配的对象数量
- obj_used
分配给用户的对象数量
- pages_used
为此类分配的页面数量
- pages_per_zspage
构成一个 zspage 的 0 阶页面数量
- freeable
类压缩可以释放的页面大致数量
每个 zspage 都维护一个 inuse 计数器,该计数器跟踪存储在 zspage 中的对象数量。inuse 计数器决定了 zspage 的“完整性组”,该组计算为“inuse”对象与 zspage 可以容纳的对象总数 (objs_per_zspage) 的比率。inuse 计数器越接近 objs_per_zspage,效果越好。
内部结构¶
zsmalloc 有 255 个大小类,每个大小类可以容纳多个 zspage。每个 zspage 最多可以包含 ZSMALLOC_CHAIN_SIZE 个物理(0 阶)页面。每个大小类的最佳 zspage 链大小在创建 zsmalloc 池时计算(请参阅 calculate_zspage_chain_size())。
作为一种优化,zsmalloc 会合并在每个 zspage 的页面数量以及每个 zspage 可以存储的对象数量方面具有相似特征的大小类。
例如,考虑以下大小类:
class size 10% .... 100% obj_allocated obj_used pages_used pages_per_zspage freeable
...
94 1536 0 .... 0 0 0 0 3 0
100 1632 0 .... 0 0 0 0 2 0
...
大小类 #95-99 与大小类 #100 合并。这意味着,当我们存储一个大小(例如)为 1568 字节的对象时,我们最终使用大小类 #100 而不是大小类 #96。大小类 #100 适用于大小为 1632 字节的对象,因此每个大小为 1568 字节的对象会浪费 1632-1568=64 字节。
大小类 #100 由每个包含 2 个物理页面的 zspage 组成,这些页面总共可以容纳 5 个对象。如果我们需要存储 13 个大小为 1568 的对象,我们最终会分配三个 zspage,即 6 个物理页面。
但是,如果我们仔细查看大小类 #96(适用于大小为 1568 字节的对象)并跟踪 calculate_zspage_chain_size(),我们会发现此类的最佳 zspage 配置是 5 个物理页面的链:
pages per zspage wasted bytes used%
1 960 76
2 352 95
3 1312 89
4 704 95
5 96 99
这意味着具有 5 个物理页面的类 #96 配置可以在单个 zspage 中存储 13 个大小为 1568 的对象,总共使用 5 个物理页面。这比类 #100 配置更有效,后者将使用 6 个物理页面来存储相同数量的对象。
随着类 #96 的 zspage 链大小的增加,其关键特征(例如每个 zspage 的页面数和每个 zspage 的对象数)也会发生变化。这会导致 dewer 类合并,从而形成更紧凑的类分组,从而减少内存浪费。
让我们仔细看看 /sys/kernel/debug/zsmalloc/zramX/classes 的底部:
class size 10% .... 100% obj_allocated obj_used pages_used pages_per_zspage freeable
...
202 3264 0 .. 0 0 0 0 4 0
254 4096 0 .. 0 0 0 0 1 0
...
大小类 #202 存储大小为 3264 字节的对象,并且每个 zspage 最多有 4 个页面。任何大于 3264 字节的对象都被认为是巨大的,属于大小类 #254,该类将每个对象存储在自己的物理页面中(巨大类中的对象不共享页面)。
增加 zspage 链的大小也会导致巨大大小类的更高水印,并减少整体上的巨大类。这可以更有效地存储大型对象。
对于 8 的 zspage 链大小,巨大类水印变为 3632 字节:
class size 10% .... 100% obj_allocated obj_used pages_used pages_per_zspage freeable
...
202 3264 0 .. 0 0 0 0 4 0
211 3408 0 .. 0 0 0 0 5 0
217 3504 0 .. 0 0 0 0 6 0
222 3584 0 .. 0 0 0 0 7 0
225 3632 0 .. 0 0 0 0 8 0
254 4096 0 .. 0 0 0 0 1 0
...
对于 16 的 zspage 链大小,巨大类水印变为 3840 字节:
class size 10% .... 100% obj_allocated obj_used pages_used pages_per_zspage freeable
...
202 3264 0 .. 0 0 0 0 4 0
206 3328 0 .. 0 0 0 0 13 0
207 3344 0 .. 0 0 0 0 9 0
208 3360 0 .. 0 0 0 0 14 0
211 3408 0 .. 0 0 0 0 5 0
212 3424 0 .. 0 0 0 0 16 0
214 3456 0 .. 0 0 0 0 11 0
217 3504 0 .. 0 0 0 0 6 0
219 3536 0 .. 0 0 0 0 13 0
222 3584 0 .. 0 0 0 0 7 0
223 3600 0 .. 0 0 0 0 15 0
225 3632 0 .. 0 0 0 0 8 0
228 3680 0 .. 0 0 0 0 9 0
230 3712 0 .. 0 0 0 0 10 0
232 3744 0 .. 0 0 0 0 11 0
234 3776 0 .. 0 0 0 0 12 0
235 3792 0 .. 0 0 0 0 13 0
236 3808 0 .. 0 0 0 0 14 0
238 3840 0 .. 0 0 0 0 15 0
254 4096 0 .. 0 0 0 0 1 0
...
总体而言,zspage 链大小对 zsmalloc 池配置的综合影响:
pages per zspage number of size classes (clusters) huge size class watermark
4 69 3264
5 86 3408
6 93 3504
7 112 3584
8 123 3632
9 140 3680
10 143 3712
11 159 3744
12 164 3776
13 180 3792
14 183 3808
15 188 3840
16 191 3840
一个综合测试¶
zram 作为构建工件存储(Linux 内核编译)。
CONFIG_ZSMALLOC_CHAIN_SIZE=4
zsmalloc 类统计信息:
class size 10% .... 100% obj_allocated obj_used pages_used pages_per_zspage freeable ... Total 13 .. 51 413836 412973 159955 3
zram mm_stat:
1691783168 628083717 655175680 0 655175680 60 0 34048 34049
CONFIG_ZSMALLOC_CHAIN_SIZE=8
zsmalloc 类统计信息:
class size 10% .... 100% obj_allocated obj_used pages_used pages_per_zspage freeable ... Total 18 .. 87 414852 412978 156666 0
zram mm_stat:
1691803648 627793930 641703936 0 641703936 60 0 33591 33591
使用更大的 zspage 链可能会导致使用更少的物理页面,如示例中所示,物理页面数量从 159955 减少到 156666,同时 zsmalloc 池的最大内存使用量从 655175680 字节降至 641703936 字节。
但是,如果存在严重的内部碎片,并且 zspool 压缩无法重新定位对象和释放 zspage,则此优势可能会被增加的系统内存压力(因为某些 zspage 具有更大的链大小)所抵消。在这些情况下,建议降低 zspage 链大小的限制(由 CONFIG_ZSMALLOC_CHAIN_SIZE 选项指定)。
函数¶
-
void obj_to_location(unsigned long obj, struct zpdesc **zpdesc, unsigned int *obj_idx)¶
从编码的对象值获取 (<zpdesc>, <obj_idx>)
参数
unsigned long obj
编码的对象值
struct zpdesc **zpdesc
zpdesc 对象驻留在 zspage 中
unsigned int *obj_idx
对象索引
-
unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx)¶
从 (<zpdesc>, <obj_idx>) 获取编码的 obj 值
参数
struct zpdesc *zpdesc
zpdesc 对象驻留在 zspage 中
unsigned int obj_idx
对象索引
-
unsigned int zs_lookup_class_index(struct zs_pool *pool, unsigned int size)¶
返回保存提供大小对象的 zsmalloc
size_class
的索引。
参数
struct zs_pool *pool
要使用的 zsmalloc 池
unsigned int size
对象大小
上下文
任何上下文。
返回
保存提供大小对象的 zsmalloc size_class
的索引。
-
size_t zs_huge_class_size(struct zs_pool *pool)¶
返回第一个巨大的 zsmalloc
size_class
的大小(以字节为单位)。
参数
struct zs_pool *pool
要使用的 zsmalloc 池
描述
该函数返回第一个巨大类的大小——任何大小等于或大于它的对象都将存储在由单个物理页面组成的 zspage 中。
上下文
任何上下文。
返回
第一个巨大的 zsmalloc size_class
的大小(以字节为单位)。
-
unsigned long zs_malloc(struct zs_pool *pool, size_t size, gfp_t gfp, const int nid)¶
从池中分配给定大小的块。
参数
struct zs_pool *pool
要从中分配的池
size_t size
要分配的块的大小
gfp_t gfp
分配对象时的 gfp 标志
const int nid
分配新 zspage(如果需要)的首选节点 ID
描述
成功后,将返回已分配对象的句柄,否则返回一个 ERR_PTR()
。大小 > ZS_MAX_ALLOC_SIZE 的分配请求将失败。
-
struct zs_pool *zs_create_pool(const char *name)¶
创建一个从中工作的分配池。
参数
const char *name
要创建的池名称
描述
使用 zsmalloc 分配器时,必须在任何操作之前调用此函数。
成功后,将返回指向新创建池的指针,否则返回 NULL。