理解memcached原始碼 - Slab II
原文發表在:
ofollow,noindex"> https:// holmeshe.me/understandi ng-memcached-source-code-II/Slab分配器 是這個快取系統的核心,並在很大程度上決定了核心資源 - 記憶體 - 的利用效率。其它的三個部分,
用來淘汰(超時)物件的LRU演算法;和
基於lebevent的事件驅動;以及
用於分佈資料的一致性雜湊,
可以看作是圍繞Slab來開發的。
這次我們繼續看用於Slab的記憶體是如何分配的。
首先我們繼續看 slabs_init
的兩個實參。第一個是 settings.maxbytes
- 控制這個memcached可以使用的總記憶體大小。在傳入 slabs_init
之前,這個引數被賦值為全域性變數 mem_limit
。
void slabs_init(const size_t limit, const double factor, const bool prealloc, const uint32_t *slab_sizes) { ... mem_limit = limit; // scr: here ...
static size_t mem_limit = 0; ... settings.maxbytes = 64 * 1024 * 1024; /* default is 64MB */ ... case 'm': settings.maxbytes = ((size_t)atoi(optarg)) * 1024 * 1024; break; ...
另外一個怎是 preallocate
。它決定了是否為(各個) Slab組 ( slab class ) 預分配 記憶體。這個引數的值由 L
命令列引數來決定。
... bool preallocate = false; ... case 'L' : if (enable_large_pages() == 0) { preallocate = true; } else { fprintf(stderr, "Cannot enable large pages on this system\n" "(There is no Linux support as of this version)\n"); return 1; } break; ...
下面我們來看 slabs 的記憶體分配函式。
New slab
具體來說,這個函式用於給 Slab組 分配大小為1M的記憶體塊( slab )。而 Slab組 由引數 id
指定。
static int do_slabs_newslab(const unsigned int id) { slabclass_t *p = &slabclass[id]; // scr: ----------------------------> 1) slabclass_t *g = &slabclass[SLAB_GLOBAL_PAGE_POOL]; // scr: ---------> *) int len = settings.slab_reassign ? settings.item_size_max // scr: ---> 2) : p->size * p->perslab; char *ptr; if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0 // -> 3) && g->slabs == 0)) { mem_limit_reached = true; MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id); return 0; } if ((grow_slab_list(id) == 0) || // scr: ----------------------------> 4) (((ptr = get_page_from_global_pool()) == NULL) && // scr: -------> *) ((ptr = memory_allocate((size_t)len)) == 0))) { // scr: ---------> 5) MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id); return 0; } memset(ptr, 0, (size_t)len); split_slab_page_into_freelist(ptr, id); // scr: ---------------------> 6) p->slab_list[p->slabs++] = ptr; // scr: -----------------------------> 7) MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id); return 1; }
1) slabclass[id]
是 Slab組 的資料結構。上篇討論了這個陣列的初始化。
2) settings.slab_reassign
決定是否啟用 再平衡 策略。如果啟用,未使用的 slabs 不會被立即釋放,而是分配給其他 Slab組 使用,這就產生了一個問題,即所有 Slab組 都需要使用統一大小的 slab 。所以這個設定同時也決定了是否使用 同種 slab (大小為 settings.item_size_max
,或者上述的1M),還是 異種slab ( p->size * p->perslab
)。除了用命令列引數“slab_reassign”以外,“modern”也會設定這個值,而本文也會用1M作為 slab 的大小。
3)檢查記憶體使用是否超出上線。
4) grow_slab_list
檢查是否增長 slabclass_t.slab_list
,如果需要,則增長之。
static int grow_slab_list (const unsigned int id) { slabclass_t *p = &slabclass[id]; if (p->slabs == p->list_size) { size_t new_size =(p->list_size != 0) ? p->list_size * 2 : 16; void *new_list = realloc(p->slab_list, new_size * sizeof(void *)); if (new_list == 0) return 0; p->list_size = new_size; p->slab_list = new_list; } return 1; }
5) memory_allocate
是真正分配 slab 記憶體的函式。如上述,這裡的 len
是1M。
6) split_slab_page_into_freelist
初始化 (或者是 free)剛剛分配的 slab 記憶體用作物件儲存。這個函式會在下一節討論。
7)將剛剛分配的 slab 加入到 slabclass_t.slab_list
。
下圖總結了這個過程(我們想象 do_slabs_newslab(n)
被呼叫了兩次)

接下來我們來看在第6)步中一塊 slab 是如何被初始化的。
split_slab_page_into_freelist
static void split_slab_page_into_freelist(char *ptr, const unsigned int id) { slabclass_t *p = &slabclass[id]; int x; for (x = 0; x < p->perslab; x++) { do_slabs_free(ptr, 0, id); ptr += p->size; } }
這個函式會遍歷 slab 裡的所有 item 塊( slabclass_t.size
),然後呼叫 do_slabs_free
來初始化每個 item 塊的元資料。換一個說法,就是 “拆分 slab 到 待分配列表 ”-“split a slab into item free list”。你也許已經猜到了,這個 待分配列表 會被直接用於 物件分配 ,這個過程後面會詳細討論。
do_slabs_free
static void do_slabs_free(void *ptr, const size_t size, unsigned int id) { slabclass_t *p; item *it; ... p = &slabclass[id]; it = (item *)ptr; it->it_flags = ITEM_SLABBED; // scr: ---------------> 1) it->slabs_clsid = 0; it->prev = 0; // scr: ------------------------------> 2) it->next = p->slots; if (it->next) it->next->prev = it; p->slots = it; p->sl_curr++; // scr: ------------------------------> 3) p->requested -= size; return; }
技術上來說,這個函式處理的元資料存在於每個 item 塊的開始。
1)初始化一些域。這裡 item
是另一個核心資料結構,後續會討論。
2)將 item 加入到上述的 待分配列表 ,並且更新連結串列表頭, slabclass_t.slots
。
3)更新可分配專案數量, slabclass_t.sl_curr
;並且更新 slabclass_t.requested
負責統計。注意這裡並沒有真正的釋放物件,所以傳入的 size
是0。

Slab 預分配
下面我們來看 do_slabs_newslab
怎麼使用。其中一個地方是之前看到過的 slabs_init
( preallocate
設定為 true
),
void slabs_init(const size_t limit, const double factor, const bool prealloc, const uint32_t *slab_sizes) { ... if (prealloc) { slabs_preallocate(power_largest); } }
static void slabs_preallocate (const unsigned int maxslabs) { int i; unsigned int prealloc = 0; /* pre-allocate a 1MB slab in every size class so people don't get confused by non-intuitive "SERVER_ERROR out of memory" messages.this is the most common question on the mailing list.if you really don't want this, you can rebuild without these three lines.*/ for (i = POWER_SMALLEST /* scr: 1 */; i < MAX_NUMBER_OF_SLAB_CLASSES; i++) { if (++prealloc > maxslabs) return; if (do_slabs_newslab(i) == 0) { fprintf(stderr, "Error while preallocating slab memory!\n" "If using -L or other prealloc options, max memory must be " "at least %d megabytes.\n", power_largest); exit(1); } } }
這個方法從 POWER_SMALLEST
(1)開始遍歷所有的 slabclass
,然後給每個 Slab組 預分配一個 slab 。(下標為0的 Slab組 是一個特殊的組,儲存空閒的 slab 用於上面提到的 再平衡 策略)。
References
和上文一樣。