Slab 分配器是 Linux 內核中用于高效管理內存的機制,其核心目標是通過對象緩存減少內存碎片和分配/釋放開銷。以下詳細解析其核心組件及其協作關系:
?
一、Slab 系統的核心組件
?
組件 描述 作用場景
Slab 描述符 每個 Slab 的管理結構(如 struct slab),記錄頁幀狀態、空閑對象鏈表等。 跟蹤 Slab 的元數據(如對象數量、空閑鏈表頭、所屬緩存等)。
Slab 節點 NUMA 架構下的內存節點(struct kmem_cache_node),管理本地 Slab 資源。 優化 NUMA 系統內存訪問,減少跨節點訪問延遲。
本地對象緩沖池 每 CPU 的緩存(struct kmem_cache_cpu),存放快速分配的空閑對象。 無鎖快速分配對象,避免全局競爭。
共享對象緩沖池 節點級別的共享緩存池(struct kmem_cache_node 中的鏈表)。 當本地緩沖池耗盡時,從共享池批量獲取對象或 Slab。
三個 Slab 鏈表 每個節點維護三個鏈表:slabs_full、slabs_partial、slabs_free。 按 Slab 的填充狀態分類,優先從 slabs_partial 分配,釋放時調整鏈表位置。
N 個 Slab 物理連續的頁幀集合,劃分為多個等大小的對象。 承載實際對象存儲,一個 Slab 可分配多個對象。
Slab 緩存對象 每個 Slab 中劃分出的內存塊,用于存儲特定類型的內核對象(如 inode)。 直接被內核代碼分配和釋放,避免頻繁調用頁分配器。
?
二、組件協作流程
?
1. 對象分配流程
?
步驟 1:嘗試從 本地對象緩沖池(Per-CPU 緩存)獲取空閑對象。
步驟 2:若本地池為空,從 共享對象緩沖池(節點 slabs_partial 鏈表)批量獲取對象填充本地池。
步驟 3:若共享池耗盡,從 Slab 空閑鏈表(slabs_free)分配新 Slab,分割為對象后加入本地池。
步驟 4:若系統內存不足,觸發頁分配器(如 Buddy System)分配新頁幀創建 Slab。
?
2. 對象釋放流程
?
步驟 1:將對象歸還到 本地對象緩沖池。
步驟 2:若本地池已滿,將部分對象批量返還到 共享對象緩沖池(slabs_partial)。
步驟 3:若 Slab 中所有對象均釋放,將 Slab 移至 slabs_free 鏈表,在內存緊張時釋放回頁分配器。
三、關鍵數據結構關系
?
struct kmem_cache
├── struct kmem_cache_cpu *cpu_slab // 每 CPU 的本地緩存
├── struct kmem_cache_node *node[NUM_NODES] // NUMA 節點共享緩存
│ ├── struct list_head slabs_full // 滿 Slab 鏈表
│ ├── struct list_head slabs_partial // 部分滿 Slab 鏈表
│ └── struct list_head slabs_free // 空閑 Slab 鏈表
└── unsigned int object_size // 對象大小
?
struct slab
├── struct list_head slab_list // 所屬鏈表(full/partial/free)
├── void *freelist // 空閑對象鏈表頭
├── unsigned int active_objects // 已分配對象數
└── unsigned int num_pages // 占用的頁幀數
?
四、代碼案例分析:Slab 分配器的內部操作
?
以下偽代碼展示 Slab 分配器在對象分配時的核心邏輯:
?
// 分配對象
void *kmem_cache_alloc(struct kmem_cache *cache, gfp_t flags) {
? ? struct kmem_cache_cpu *cpu_slab = get_cpu_ptr(cache->cpu_slab);
? ? void *object;
?
? ? // 1. 嘗試從本地緩沖池獲取對象
? ? if (likely(cpu_slab->freelist)) {
? ? ? ? object = cpu_slab->freelist;
? ? ? ? cpu_slab->freelist = get_next_freepointer(object);
? ? ? ? return object;
? ? }
?
? ? // 2. 本地池為空,從節點共享池填充
? ? if (unlikely(!fill_local_cache(cpu_slab, cache, flags))) {
? ? ? ? // 3. 共享池不足,分配新 Slab
? ? ? ? struct slab *new_slab = allocate_slab(cache, flags);
? ? ? ? if (!new_slab)
? ? ? ? ? ? return NULL; // 內存不足
? ? ? ? cpu_slab->freelist = new_slab->freelist;
? ? ? ? cpu_slab->slab = new_slab;
? ? ? ? object = cpu_slab->freelist;
? ? ? ? cpu_slab->freelist = get_next_freepointer(object);
? ? ? ? return object;
? ? }
? ? // ... 其他錯誤處理
}
?
// 填充本地緩沖池
static int fill_local_cache(struct kmem_cache_cpu *cpu_slab,?
? ? ? ? ? ? ? ? ? ? ? ? ? ?struct kmem_cache *cache,?
? ? ? ? ? ? ? ? ? ? ? ? ? ?gfp_t flags) {
? ? struct kmem_cache_node *node = cache->node[cpu_to_node(smp_processor_id())];
? ? struct slab *slab;
?
? ? // 從節點的部分滿鏈表獲取 Slab
? ? spin_lock(&node->list_lock);
? ? slab = list_first_entry_or_null(&node->slabs_partial, struct slab, slab_list);
? ? if (!slab) {
? ? ? ? // 若無部分滿 Slab,嘗試從空閑鏈表分配
? ? ? ? slab = list_first_entry_or_null(&node->slabs_free, struct slab, slab_list);
? ? ? ? if (!slab) {
? ? ? ? ? ? spin_unlock(&node->list_lock);
? ? ? ? ? ? return 0; // 需要分配新 Slab
? ? ? ? }
? ? ? ? list_move(&slab->slab_list, &node->slabs_partial);
? ? }
? ? // 將 Slab 的空閑對象導入本地緩存
? ? cpu_slab->freelist = slab->freelist;
? ? slab->freelist = NULL;
? ? slab->active_objects = cache->num_objects;
? ? list_move(&slab->slab_list, &node->slabs_full);
? ? spin_unlock(&node->list_lock);
? ? cpu_slab->slab = slab;
? ? return 1;
}
?
五、關鍵設計優勢
?
減少內存碎片
Slab 按對象大小預分配內存塊,避免頻繁分割物理頁,減少外部碎片。
?
提升分配速度
通過本地緩沖池(Per-CPU 無鎖緩存)實現快速分配,避免全局鎖競爭。
?
NUMA 優化
每個節點管理本地 Slab,減少跨節點內存訪問帶來的性能損耗。
?
內存重用
釋放的對象歸還到空閑鏈表,避免頻繁調用頁分配器,降低系統開銷。
?
六、實際觀察工具
?
查看 Slab 狀態
?
cat /proc/slabinfo
輸出示例:顯示每個緩存的名稱、對象數量、活動對象數、Slab 使用情況等。
內核調試工具
使用 systemtap 或 ftrace 跟蹤特定緩存的對象分配/釋放路徑。