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。