【原創】(十一)Linux記憶體管理slub分配器
背景
Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基
說明:
- Kernel版本:4.14
- ARM64處理器,Contex-A53,雙核
- 使用工具:Source Insight 3.5, Visio
1. 概述
之前的文章分析的都是基於頁面的記憶體分配,而小塊記憶體的分配和管理是通過塊分配器來實現的。目前核心中,有三種方式來實現小塊記憶體分配:slab, slub, slob
,最先有slab
分配器,slub/slob
分配器是改進版,slob
分配器適用於小記憶體嵌入式裝置,而slub
slub
分配器為目標,進一步深入。
先來一個初印象:
2. 資料結構
有四個關鍵的資料結構:
struct kmem_cache
:用於管理SLAB快取
,包括該快取中物件的資訊描述,per-CPU/Node管理slab頁面等;
關鍵欄位如下:
/* * Slab cache management. */ struct kmem_cache { struct kmem_cache_cpu __percpu *cpu_slab; //每個CPU slab頁面 /* Used for retriving partial slabs etc */ unsigned long flags; unsigned long min_partial; int size; /* The size of an object including meta data */ int object_size; /* The size of an object without meta data */ int offset; /* Free pointer offset. */ #ifdef CONFIG_SLUB_CPU_PARTIAL /* Number of per cpu partial objects to keep around */ unsigned int cpu_partial; #endif struct kmem_cache_order_objects oo; //該結構體會描述申請頁面的order值,以及object的個數 /* Allocation and freeing of slabs */ struct kmem_cache_order_objects max; struct kmem_cache_order_objects min; gfp_t allocflags; /* gfp flags to use on each alloc */ int refcount; /* Refcount for slab cache destroy */ void (*ctor)(void *); // 物件建構函式 int inuse; /* Offset to metadata */ int align; /* Alignment */ int reserved; /* Reserved bytes at the end of slabs */ int red_left_pad; /* Left redzone padding size */ const char *name; /* Name (only for display!) */ struct list_head list; /* List of slab caches */ //kmem_cache最終會連結在一個全域性連結串列中 struct kmem_cache_node *node[MAX_NUMNODES]; //Node管理slab頁面 };
struct kmem_cache_cpu
:用於管理每個CPU的slab頁面
,可以使用無鎖訪問,提高快取物件分配速度;
struct kmem_cache_cpu { void **freelist; /* Pointer to next available object */ //指向空閒物件的指標 unsigned long tid; /* Globally unique transaction id */ struct page *page; /* The slab from which we are allocating */ //slab快取頁面 #ifdef CONFIG_SLUB_CPU_PARTIAL struct page *partial; /* Partially allocated frozen slabs */ #endif #ifdef CONFIG_SLUB_STATS unsigned stat[NR_SLUB_STAT_ITEMS]; #endif };
struct kmem_cache_node
:用於管理每個Node的slab頁面
,由於每個Node的訪問速度不一致,slab
頁面由Node來管理;
/*
* The slab lists for all objects.
*/
struct kmem_cache_node {
spinlock_t list_lock;
#ifdef CONFIG_SLUB
unsigned long nr_partial; //slab頁表數量
struct list_head partial; //slab頁面連結串列
#ifdef CONFIG_SLUB_DEBUG
atomic_long_t nr_slabs;
atomic_long_t total_objects;
struct list_head full;
#endif
#endif
};
struct page
:用於描述slab頁面
,struct page
結構體中很多欄位都是通過union
聯合體進行復用的。
struct page
結構中,用於slub
的成員如下:
struct page {
union {
...
void *s_mem; /* slab first object */
...
};
/* Second double word */
union {
...
void *freelist; /* sl[aou]b first free object */
...
};
union {
...
struct {
union {
...
struct { /* SLUB */
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
};
...
};
...
};
};
/*
* Third double word block
*/
union {
...
struct { /* slub per cpu partial pages */
struct page *next; /* Next partial slab */
#ifdef CONFIG_64BIT
int pages; /* Nr of partial slabs left */
int pobjects; /* Approximate # of objects */
#else
short int pages;
short int pobjects;
#endif
};
struct rcu_head rcu_head; /* Used by SLAB
* when destroying via RCU
*/
};
...
struct kmem_cache *slab_cache; /* SL[AU]B: Pointer to slab */
...
}
圖來了:
3. 流程分析
針對Slub的使用,可以從三個維度來分析:
- slub快取建立
- slub物件分配
- slub物件釋放
下邊將進一步分析。
3.1 kmem_cache_create
在核心中通過kmem_cache_create
介面來建立一個slab快取
。
先看一下這個介面的函式呼叫關係圖:
kmem_cache_create
完成的功能比較簡單,就是建立一個用於管理slab快取
的kmem_cache
結構,並對該結構體進行初始化,最終新增到全域性連結串列中。kmem_cache
結構體初始化,包括了上文中分析到的kmem_cache_cpu
和kmem_cache_node
兩個欄位結構。在建立的過程中,當發現已有的
slab快取
中,有存在物件大小相近,且具有相容標誌的slab快取
,那就只需要進行merge操作並返回,而無需進一步建立新的slab快取
。calculate_sizes
函式會根據指定的force_order
或根據物件大小去計算kmem_cache
結構體中的size/min/oo
等值,其中kmem_cache_order_objects
結構體,是由頁面分配order
值和物件數量
兩者通過位域拼接起來的。在建立
slab快取
的時候,有一個先雞後蛋的問題:kmem_cache
結構體來管理一個slab快取
,而建立kmem_cache
結構體又是從slab快取
中分配出來的物件,那麼這個問題是怎麼解決的呢?可以看一下kmem_cache_init
函式,核心中定義了兩個靜態的全域性變數kmem_cache
和kmem_cache_node
,在kmem_cache_init
函式中完成了這兩個結構體的初始化之後,相當於就是建立了兩個slab快取
,一個用於分配kmem_cache
結構體物件的快取池,一個用於分配kmem_cache_node
結構體物件的快取池。由於kmem_cache_cpu
結構體是通過__alloc_percpu
來分配的,因此不需要建立一個相關的slab快取
。
3.2 kmem_cache_alloc
kmem_cache_alloc
介面用於從slab快取池中分配物件。
看一下大體的呼叫流程圖:
從上圖中可以看出,分配slab物件與Buddy System
中分配頁面類似,存在快速路徑和慢速路徑兩種,所謂的快速路徑就是per-CPU快取
,可以無鎖訪問,因而效率更高。
整體的分配流程大體是這樣的:優先從per-CPU快取
中進行分配,如果per-CPU快取
中已經全部分配完畢,則從Node
管理的slab頁面中遷移slab頁
到per-CPU快取
中,再重新分配。當Node
管理的slab頁面也不足的情況下,則從Buddy System
中分配新的頁面,新增到per-CPU快取
中。
還是用圖來說明更清晰,分為以下幾步來分配:
fastpath
快速路徑下,以原子的方式檢索per-CPU快取的freelist列表中的第一個物件,如果freelist為空並且沒有要檢索的物件,則跳入慢速路徑操作,最後再返回到快速路徑中重試操作。
slowpath-1
將per-CPU快取中page指向的slab頁中的空閒物件遷移到freelist中,如果有空閒物件,則freeze該頁面,沒有空閒物件則跳轉到slowpath-2
。
slowpath-2
將per-CPU快取中partial連結串列中的第一個slab頁遷移到page指標中,如果partial連結串列為空,則跳轉到slowpath-3
。
slowpath-3
將Node管理的partial連結串列中的slab頁遷移到per-CPU快取中的page中,並重復第二個slab頁將其新增到per-CPU快取中的partial連結串列中。如果遷移的slab中空閒物件超過了kmem_cache.cpu_partial
的一半,則僅遷移slab頁
,並且不再重複。
如果每個Node的partial連結串列都為空,跳轉到slowpath-4
。
slowpath-4
從Buddy System
中獲取頁面,並將其新增到per-CPU的page中。
3.2 kmem_cache_free
kmem_cache_free
的操作,可以看成是kmem_cache_alloc
的逆過程,因此也分為快速路徑和慢速路徑兩種方式,同時,慢速路徑中又分為了好幾種情況,可以參考kmem_cache_alloc
的過程。
呼叫流程圖如下:
效果如下:
快速路徑釋放
快速路徑下,直接將物件返回到freelist中即可。
put_cpu_partial
put_cpu_partial
函式主要是將一個剛freeze的slab頁,放入到partial連結串列中。
在put_cpu_partial
函式中呼叫unfreeze_partials
函式,這時候會將per-CPU管理的partial連結串列中的slab頁面新增到Node管理的partial連結串列的尾部。如果超出了Node的partial連結串列,溢位的slab頁面中沒有分配物件的slab頁面將會返回到夥伴系統。
add_partial
新增slab頁到Node的partial連結串列中。
remove_partial
從Node的partial連結串列移除slab頁。
具體釋放的流程走哪個分支,跟物件的使用情況,partial連結串列的個數nr_partial/min_partial
等相關,細節就不再深入分析了