Slab Allocator

問題背景

Kernel 需要反覆配置和釋放固定大小的資料結構(例如 task_structinodefile)。

如果每次都向 Buddy System 要一塊頁框再切割:

  • 速度慢(每次都要走 buddy 的分裂流程)
  • 會造成嚴重的 external fragmentation

Jeff Bonwick 在 1994 年提出 slab allocator,核心直覺是:

既然 kernel 編譯後物件種類就固定了,不如替每種物件預留一批「槽位」,用完放回來,不要每次都重新向底層申請。


三層架構

把 slab allocator 想成一間「預製零件工廠」:

Cache(某種零件的倉庫)
└── Slab(倉庫裡的一塊貨架,由連續頁框組成)
    └── Object(貨架上切好的一個槽位)
層級說明
Object某個 kernel 資料結構的一個實例,大小 = sizeof(struct xxx),固定不變
Slab一塊或多塊實體連續的頁框(physically contiguous pages),被切成一格一格,每格容納一個 object
Cache管理某種 kernel 資料結構的 metadata,由一個或多個 slab 組成,例如 inode_cachedentry_cache

Cache ≠ 預先填滿的 objects

Cache 建立時(kmem_cache_create())只是初始化 metadata,尚無任何 slab 或 object。 Slab 是「按需建立」的:第一次 kmem_cache_alloc() 時,cache 發現沒有 free slot,才向 Buddy System 申請頁框並建立第一個 slab。


運作流程

flowchart LR
    A[需要 object] --> B{cache 有 free slot?}
    B -- 有 --> C[標記為 used,回傳指標]
    B -- 沒有 --> D{有空的 slab?}
    D -- 有 --> E[從空 slab 配置]
    D -- 沒有 --> F[向 Buddy System 申請新頁框]
    F --> G[建新 slab] --> E --> C
  1. 配置:需要某個物件 → 從 cache 取一個 free object → 標記為 used → 回傳指標
  2. 釋放:用完 → object 標記回 free → 留在 cache 裡,不還給 buddy system
  3. 擴張:所有 slab 都滿 → 向 Buddy System 申請新頁框,建新的 slab

優點

1. 減少 Internal Fragmentation

Buddy System 給的是 bytes 的 page,object size 不一定整除。但 slab 把同一種大小的 object 打包在一起,浪費被壓縮到最小。

2. 快速配置

需要 object 時只要把一個 free slot 標記為 used 再回傳指標,省去:

  • 向 buddy system 申請頁框
  • 複雜的切割計算
  • 重新初始化(某些實作保留物件的部分初始化狀態,稱為 object reuse

變體

變體適用場景特點
SLAB原始設計完整功能,metadata 較複雜
SLUB現代 Linux 預設簡化 per-slab metadata,多核心效能更好
SLOB嵌入式 / 記憶體極度有限用簡單 linked list,佔用最少記憶體