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 **zpdesczpdesc 物件駐留在 zspage 中
unsigned int *obj_idx物件索引
-
unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx)¶
從 (<zpdesc>, <obj_idx>) 獲取編碼的 obj 值
引數
struct zpdesc *zpdesczpdesc 物件駐留在 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。