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

对象索引

unsigned long location_to_obj(struct page *page, unsigned int obj_idx)

从 (<页>, <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。