slab原始碼分析--主要資料結構分析
基本原理
Linux 在保護模式下,由夥伴系統負責記憶體分配,分配記憶體的最小單位是頁。在夥伴系統之上,Linux 又增加了 slab 機制,其工作是針對一些經常分配並釋放的物件,如 task_struct 結構體等,這種物件的大小一般比較小,如果直接採用夥伴系統來進行分配和釋放,不僅會造成大量的內部碎片,而且處理速度也太慢。而 slab 機制是基於物件進行管理的,相同型別的物件歸為一類(如 tast_struct 就是一類),每次要申請這樣一個物件,slab 快取器就從一個 slab 列表中分配一個這樣大小的單元出去,而當要釋放時,將其重新歸還給 slab 快取器,而不是直接歸還給夥伴系統。slab 分配物件時,會使用最近釋放的物件記憶體塊,因此其駐留在 CPU 快取記憶體的概率較高。並且 slab 機制針對 SMP 和 NUMA 架構進行了處理,還考慮到了硬體 cache 方面的優化。
記憶體管理這一部分的處理框架就是下圖:
管理機構圖示
slab 機制管理機構如圖:
主要資料結構
kmem_cache快取器結構體
slab 機制為每種物件建立單獨的 kmem_cache(下文統稱快取器),每個 快取器實際上就是指定了一種規則,符合該快取器描述規則的物件都將從該快取器分配。快取器自身被組織成了一個雙向連結串列,它的頭節點是 cache_chain。
實際上,有兩種快取器,general cache(通用快取器) 和 specific cache(專用快取器),通用快取器包括:
- 快取 kmem_cache 的快取器(呵呵,有點玄乎,kmem_cache 即快取器,它自己也是一個小記憶體,也需要使用 slab 機制來分配,所以這就是一個雞與蛋的問題,我們需要“手工”製造一個快取器,名曰 cache_cache 來快取 kmem_cache 這個通用快取器,完了再取而代之就行)。
- kmalloc 使用的物件按照 malloc_sizes[] 表的大小分屬不同的快取器,32、64、128…,每種大小對應兩個兩個快取器,一個對應DMA,一個用於普通分配,這也是通用快取器。
通用快取器是按照大小進行劃分,所以大小就是通用快取器的主要規則。
專用快取器為系統特定結構建立的物件,比如 struct file,此類快取器物件來源於同一個結構。
下面來看快取器的結構體:
//快取器
struct kmem_cache {
/* 1) per-cpu data, touched during every alloc/free */
//per-cpu資料,本地快取,記錄了本地快取記憶體的資訊,也用於跟蹤最近釋放的物件,每次分配和釋放都先訪問它
struct array_cache *array[NR_CPUS];
/* 2) Cache tunables. Protected by cache_chain_mutex */
unsigned int batchcount; //本地快取轉入或轉出的大批物件數目
unsigned int limit; //本地快取空閒物件的最大數目
unsigned int shared; //是否支援本節點共享一部分cache的標誌,如果支援,那就存在本地共享快取
unsigned int buffer_size; //管理的物件大小
u32 reciprocal_buffer_size; //上面這個大小的倒數,貌似利用這個可用牛頓迭代法求什麼:)
/* 3) touched by every alloc & free from the backend */
unsigned int flags; //cache 的永久標誌
unsigned int num; //一個 slab 所包含的物件數目!!! 也就是說,kmem_cache 控制了它所管轄的所有物件大小數目及其他屬性
/* 4) cache_grow/shrink */
/* order of pgs per slab (2^n) */
unsigned int gfporder; //一個slab所包含的 page 的對數,也就是一個slab分配 2^gfporder 個 page
/* force GFP flags, e.g. GFP_DMA */
gfp_t gfpflags; //與夥伴系統互動時所提供的分配標識
size_t colour; /* cache colouring range */ //著色的範圍吧
unsigned int colour_off; /* colour offset */ //著色的偏移量
struct kmem_cache *slabp_cache;//如果將slab描述符儲存在外部,該指標指向slab描述符的 cache,否則為 NULL
unsigned int slab_size; // slab 的大小
unsigned int dflags; /* dynamic flags */ //FIXME: 動態標誌
/* constructor func */
void (*ctor) (void *, struct kmem_cache *, unsigned long);
/* 5) cache creation/removal */
const char *name; //名字:)
struct list_head next; //構造連結串列所用
/* 6) statistics */
#if STATS
//都是除錯資訊,略去
#endif
/*
* We put nodelists[] at the end of kmem_cache, because we want to size
* this array to nr_node_ids slots instead of MAX_NUMNODES
* (see kmem_cache_init())
* We still use [MAX_NUMNODES] and not [1] or [0] because cache_cache
* is statically defined, so we reserve the max number of nodes.
*/
//nodelists 用於組織所有節點的 slab,每個節點尋找自己擁有的 cache 將自己作為 nodelists 的下標就可以訪問了
//不過從這裡訪問的只是每個節點的 slab 管理的 cache 以及每個節點的共享 cache ,per-cpu cache 是上面的array陣列管理的
//當然,針對同一個快取器kmem_cache,它管理的是同一種物件,所以通過本 kmem_cache 結構體的 nodelists 成員訪問的也就只是同種物件的 cache
struct kmem_list3 *nodelists[MAX_NUMNODES];
/*
* Do not add fields after nodelists[]?
*/
};
始終記住一點:快取器的職責就是為它負責快取的物件指定規則! 符合某個規則的物件就會從相應的快取器去申請記憶體。
從上面的結構體可以看出,同一個快取器中所有物件大小是相同的(buffer_size),並且同一快取器中所有 slab 大小也是相同的(gfporder、num)。
快取器最重要有三個成員:
- array[] 陣列。它是每 CPU(per_cpu) 資料,也就是我們的本地快取。這是為了減小 SMP 架構下自旋鎖而設定的成員,無論是物件的分配還是回收都優先考慮本地快取。
- shared 標誌。該標誌如果使能的話,快取器成員 nodelist 對應的三鏈就會啟用 struct array_cache* 型別的成員 shared,它用來串接共享的快取物件。這就是所謂的本地共享快取,用於 CPU 之間的物件共享。
- nodelist[] 陣列。該陣列的 index 是 NUMP 架構下的記憶體結點 node,陣列成員都是三鏈。針對每個記憶體結點,快取器都為它維持一個三鏈。三鏈是指 struct kmem_list3 結構體,該結構體內部有滿(full)、部分滿(partial)、空(free)三種以 slab 內部成員分配情況為依據建立的連結串列,所有的 slab 快取都維繫在三鏈上。
為什麼要引入本地共享快取?
考慮到下面的場景:
CPU1 收到大量的網路報文,分配 struct sk_buff 物件,報文處理完成後,由 CPU2 發出並釋放,這樣物件就被 CPU2 正好回收。這會造成 CPU1 的本地快取耗盡,需要從三鏈中分配物件。而 CPU2 的本地快取過多需要釋放物件到三鏈中。此時本地共享快取相當於給它們架了一座橋樑,有了它,上述情形執行由本地共享快取負責互動即可,無需再訪問三鏈。
array_cache結構體
本地快取其實就是一個 array_cache 結構體。不過由於處於多 CPU 環境,因此就組建了一個該型別的 array 陣列,將各個 CPU 的本地快取組織在一起,作為快取器的成員,統一起來方便管理。
/*
* struct array_cache
*
* Purpose:
* - LIFO ordering, to hand out cache-warm objects from _alloc
* - reduce the number of linked list operations
* - reduce spinlock operations
*
* The limit is stored in the per-cpu structure to reduce the data cache
* footprint.
*
*/ //array_cache中都是per-cpu資料,不會共享,這可以減少NUMA架構中多CPU的自旋鎖競爭
struct array_cache {
unsigned int avail; //本地快取中可用的空閒物件數
unsigned int limit; //本地快取空閒物件數目上限
unsigned int batchcount; //本地快取一次性轉入和轉出的物件數量
unsigned int touched; //標識本地物件是否最近被使用
spinlock_t lock; //自旋鎖
void *entry[0]; /* //這是一個柔性陣列,便於對後面用於跟蹤空閒物件的指標陣列的訪問
* Must have this definition in here for the proper
* alignment of array_cache. Also simplifies accessing
* the entries.
* [0] is for gcc 2.95. It should really be [].
*/
};
注意該結構體的最後一個元素 void* entry[0],這就是它能串接快取物件的原因。
kmem_list3三鏈結構體
三鏈結構體如下:
/*
* The slab lists for all objects.
*/
struct kmem_list3 {
struct list_head slabs_partial;//部分滿的slab連結串列,也就是部分物件唄分配出去的slab
struct list_head slabs_full; //滿slab連結串列
struct list_head slabs_free; //空slab連結串列
unsigned long free_objects; //空閒物件的個數
unsigned int free_limit; //空閒物件的上限數目
unsigned int colour_next; /* Per-node cache coloring */ //每個節點下一個slab使用的顏色
spinlock_t list_lock;
struct array_cache *shared; /* shared per node */ //每個節點共享出去的快取
struct array_cache **alien; /* on other nodes */ //FIXME: 其他節點的快取,應該是共享的
unsigned long next_reap; /* updated without locking */
int free_touched; /* updated without locking */
};
申請物件三部曲,先從本地快取申請,本地快取沒有就找本地共享快取,如果還沒有,就來找三鏈了。最差的情況是,三鏈也沒有,那就只能去夥伴系統要點 page,新建 slab 了。
當空閒物件比較富餘時,free 連結串列的部分 slab 可能被定期回收。
slab結構體
struct slab 結構體如下,它的另外一個名字是 slab 描述符(與kmem_bufclt陣列組成 slab 管理者(manager)。
/*
* struct slab
*
* Manages the objs in a slab. Placed either at the beginning of mem allocated
* for a slab, or allocated from an general cache. //該管理者可以在slab頭部申請記憶體,也可以從general cache處申請記憶體。
* Slabs are chained into three list: fully used, partial, fully free slabs.
*/
struct slab {
struct list_head list; //用於將slab納入三鏈之中
unsigned long colouroff; //該slab的著色偏移,是一大塊,不是單位
void *s_mem; //指向slab中的第一個物件
unsigned int inuse; //slab中已分配出去的物件數目
kmem_bufctl_t free; //下一個空閒物件的下標
unsigned short nodeid; //NUMA架構節點標識號
};
這個結構體負責描述一個 slab 的情況。slab 機制還採用 kmem_bufctl_t (unsigned int) 型別陣列來儲存物件的下標(在這個結構體內沒出現該陣列,是在外部和它組合的),它們的儲存位置是相連的。將它們兩個統稱為“slab管理者”。
slab 管理者的自身儲存位置有兩種,一種是 on-slab(內建式),一種是 off-slab (外接式)。內建式需要佔據 slab 內部空間,所以計算偏移量什麼的要加上。而外接式是重新申請一塊 slab 專門用來存放 slab 管理者(總共兩塊 slab)。該陣列使用指標強制轉化方式訪問,所以沒有設為結構體成員。
通常物件小於 512 的小物件採用內建式 slab,大於等於 512 的大物件採用外接式 slab。
內建式:
外接式:
參考:
後記
先默哀一秒鐘,從 9 號晚上開始看 slab,這幾天暈暈乎乎的,尤其是所謂的快取器的快取 cache_cache,我至少想了一天才想明白套路。核心確實不好剖析,主要還是沒有人出相應的書吧,期待有一天有相關的書,那就可以節約學習核心的人大量時間了。
相關文章
- slab原始碼分析--kmalloc函式分析原始碼函式
- Redis資料結構概覽(原始碼分析)Redis資料結構原始碼
- slab原始碼分析--從slab初始化說起原始碼
- slab原始碼分析--銷燬函式原始碼函式
- slab原始碼分析--快取器的建立原始碼快取
- Redis原始碼分析-底層資料結構盤點Redis原始碼資料結構
- slab原始碼分析--setup_cpu_cache函式原始碼函式
- Android 原始碼結構分析Android原始碼
- 以太坊原始碼分析(25)core-txlist交易池的一些資料結構原始碼分析原始碼資料結構
- Linux 4.x MTD原始碼分析-核心資料結構Linux原始碼資料結構
- Redis資料結構—跳躍表 skiplist 實現原始碼分析Redis資料結構原始碼
- Faiss原始碼剖析:類結構分析AI原始碼
- ArrayList 資料結構分析資料結構
- Unbound資料結構分析資料結構
- EasyTransaction主要原始碼分析原始碼
- EOS原始碼分析(7)目錄結構原始碼
- ArrayList底層結構和原始碼分析原始碼
- 原始碼分析:Vue的雙向資料繫結原始碼Vue
- 以太坊原始碼分析(12)交易資料分析原始碼
- LinkedList 資料結構分析資料結構
- Dedecms 資料庫 結構分析資料庫
- 【MyBatis原始碼分析】select原始碼分析及小結MyBatis原始碼
- Giraph 原始碼分析(五)—— 載入資料+同步總結原始碼
- Ruby 2.x 原始碼學習:語法分析 & 中間程式碼生成 之 資料結構原始碼語法分析資料結構
- elastic-job-lite 資料結構分析AST資料結構
- 前端資料結構---複雜度分析前端資料結構複雜度
- JAVA常用資料結構及原理分析Java資料結構
- 資料結構和演算法分析資料結構演算法
- Vue原始碼分析之資料驅動Vue原始碼
- redis原始碼分析(五):資料持久化Redis原始碼持久化
- Presto原始碼分析之資料型別REST原始碼資料型別
- 原始碼分析:Exchanger之資料交換器原始碼
- MPTCP 原始碼分析(六) 資料重發TCP原始碼
- RocketMq 拉取資料流程原始碼分析MQ原始碼
- libevent原始碼初識及目錄結構分析原始碼
- Retrofit原始碼分析三 原始碼分析原始碼
- FastText總結,fastText 原始碼分析AST原始碼
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼