Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基說明:node
以前的文章分析的都是基於頁面的內存分配,而小塊內存的分配和管理是經過塊分配器來實現的。目前內核中,有三種方式來實現小塊內存分配:slab, slub, slob
,最早有slab
分配器,slub/slob
分配器是改進版,slob
分配器適用於小內存嵌入式設備,而slub
分配器目前已逐漸成爲主流塊分配器。接下來的文章,就是以slub
分配器爲目標,進一步深刻。緩存
先來一個初印象:
數據結構
有四個關鍵的數據結構:函數
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 */ ... }
圖來了:
工具
針對Slub的使用,能夠從三個維度來分析:atom
下邊將進一步分析。3d
在內核中經過kmem_cache_create
接口來建立一個slab緩存
。指針
先看一下這個接口的函數調用關係圖:
code
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緩存
。
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中。
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
等相關,細節就再也不深刻分析了。