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。