zsmalloc¶
此分配器设计用于 zram。因此,该分配器应在低内存条件下良好工作。特别是,它从不尝试高阶页面分配,这在内存压力下很可能失败。另一方面,如果我们只使用单个(0阶)页面,则会遭受非常高的碎片化 - 任何大小为 PAGE_SIZE/2 或更大的对象都将占用整个页面。这是其前身 (xvmalloc) 的主要问题之一。
为了克服这些问题,zsmalloc 分配了一堆 0 阶页面,并使用各种“struct page”字段将它们链接在一起。这些链接的页面充当单个高阶页面,即一个对象可以跨越 0 阶页面边界。代码将这些链接的页面称为一个名为 zspage 的实体。
为简单起见,zsmalloc 只能分配大小最大为 PAGE_SIZE 的对象,因为这满足了其当前所有用户的需求(在最坏的情况下,页面是不可压缩的,因此“按原样”存储,即以未压缩形式存储)。对于大于此大小的分配请求,将返回失败(请参阅 zs_malloc)。
此外,zs_malloc()
不返回可解引用的指针。相反,它返回一个不透明的句柄(无符号长整型),该句柄编码了已分配对象的实际位置。这种间接的原因是 zsmalloc 不会永久保持 zspage 的映射,因为这会在内核空间映射的 VA 区域非常小的 32 位系统上引起问题。因此,在使用分配的内存之前,必须使用 zs_map_object()
映射该对象以获得可用的指针,随后使用 zs_unmap_object() 解除映射。
stat¶
使用 CONFIG_ZSMALLOC_STAT,我们可以通过 /sys/kernel/debug/zsmalloc/<用户名>
查看 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
...
...
- 类
索引
- 大小
对象大小 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 的对象数等关键特征也会发生变化。这导致较少的类合并,从而形成更紧凑的类分组,从而减少内存浪费。
让我们仔细看看 /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 链的大小也会导致巨大的大小类的高水位线和更少的巨大类。这允许更有效地存储大型对象。
对于 zspage 链大小为 8,巨大的类高水位线变为 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
...
对于 zspage 链大小为 16,巨大的类高水位线变为 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 page **page, unsigned int *obj_idx)¶
从编码的对象值获取 (<页>, <obj_idx>)
参数
unsigned long obj
编码的对象值
struct page **page
对象驻留在 zspage 中的页
unsigned int *obj_idx
对象索引
参数
struct page *page
对象驻留在 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
的索引。
-
void *zs_map_object(struct zs_pool *pool, unsigned long handle, enum zs_mapmode mm)¶
从句柄获取已分配对象的地址。
参数
struct zs_pool *pool
从中分配对象的池
unsigned long handle
从 zs_malloc 返回的句柄
enum zs_mapmode mm
要使用的映射模式
描述
在使用从 zs_malloc 分配的对象之前,必须使用此函数进行映射。当使用完对象后,必须使用 zs_unmap_object 取消映射。
每次每个 CPU 只能映射一个对象。没有针对嵌套映射的保护。
此函数在禁用抢占和页面错误的情况下返回。
-
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)¶
从池中分配给定大小的块。
参数
struct zs_pool *pool
从中分配的池
size_t size
要分配的块的大小
gfp_t gfp
分配对象时的 gfp 标志
描述
成功时,返回已分配对象的句柄,否则返回 ERR_PTR()
。 大小 > ZS_MAX_ALLOC_SIZE 的分配请求将失败。
-
struct zs_pool *zs_create_pool(const char *name)¶
创建要使用的分配池。
参数
const char *name
要创建的池的名称
描述
使用 zsmalloc 分配器时,必须先调用此函数。
成功时,返回指向新创建的池的指针,否则返回 NULL。