1. 程式人生 > >內存管理-slab[原理]

內存管理-slab[原理]

答案 image 存在 使用 頻繁 我們 歷史 long 怎麽

歷史簡介

linux內核運行需要動態分配內存,其中有兩種分配方案:

第一種是以頁為單位分配內存,即一次分配內存的大小必須是頁的整數倍;第二種是按需分配內存,一次分配內存的大小是隨機的。

第一種分配方案通過buddy系統實現,第二種分配方案就是通過slab子系統實現。slab子系統隨內核的發展衍生出slub和slob,最新應用於服務器的內核一般默認使用slub來實現第二種內存分配方案。slob一般用在移動端和嵌入式系統。相對較老的內核默認用slab實現第二種分配方案。slab,slob,slub就功能來說相同,內核提供了編譯選項供用戶選擇使用哪種子系統來實現第二種內存分配方案。由於slab系統是內核使用的原始方案,經過相當長一段時間的演進。因此本系列博客就以slab開始講解linux實現的第二種內存分配方案。充分理解slab後,對slub和slob就很簡單了。

slab和buddy的關系,以及slab存在的必要性

slab是基於buddy系統實現:調用slab接口分配隨機大小的內存時,slab內部會調用buddy系統以頁為單位申請整數個物理上連續的頁,然後將這些頁拆分成更小的單元,取一個合適大小的單元,將這段內存的地址返回。如圖,buddy系統實現了物理內存管理,抽象出內核如下兩個主要內核接口函數

1 static inline struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)//分配2的order次方個連續物理頁,返回第一個頁的page描述符虛的擬地址
2 void free_pages(unsigned long
addr, unsigned int order) //釋放2的order個連續物理頁,addr指向第一個物理頁第一個byte的虛擬地址

內核slab子系統對外提供了如下兩個主要接口:

static __always_inline void *kmalloc(size_t size, gfp_t flags)//分配size字節大小的內存,返回這段內存的虛擬地址 
void kfree(const void *objp)//釋放objp指向的一段內存 

技術分享圖片

既然有了buddy為什麽又基於buddy實現slab呢?答案就是處於時間效率和空間效率的考慮:

出於空間效率:舉個例子:在fork系統調用種進程管理子系統需要動態申請task_struct來作為新建進程的進程描述符,而task_struct需要的內存遠遠小於一個物理頁,如果沒有slab子系統,那進程管理子系統就只有通過buddy系統申請一個完整的物理頁。這樣一來有兩種方法處理這個頁內剩余的內存,其一:進程管理子系統自己將這個頁劃分若幹個更小的單元管理,將剩下的內存用作其他的用途,這樣有一個弊端,進程管理子系統必須自己管理剩余內存,task_struct僅僅時一個例子,試想如果內核種所有的子系統都要再設計一個“剩余內存管理的功能”顯然是不科學的。其二:就是將每一個task_struct裝到一個頁中,這個頁剩余的空間不要了,這樣會浪費內存空間,但進程管理子系統就不必自己管理剩余單元。

出處於時間的考慮:buddy系統相對於slab系統要復雜很多,每一次調用alloc_pages和free_pages需要付出慘重的代價。內核中有些代碼又必須頻繁的申請釋放內存。slab其實充當內核各個子系統和buddy系統之間的一個空閑內存的“緩沖池”。當內存通過kfree被釋放後,短時期停留在slab的”緩沖池”中,如果再次kmalloc時直接從“緩沖池”中將空閑內存取出來返回即可。這樣就避免每次內存分配都要付出alloc_pages和free_pages的代價。

slab是怎麽將通過alloc_pages申請到的頁組織成更小的單元?又是怎麽將這些個更小的單元管理起來的呢?讓我們沿著正常人的思路一步一步推演slab的設計

實現kmalloc和kfree需要達成的目標無非就是時間和空間兩方面:給定size必須快速找到適合的空閑內存段(時間方面)。適合的空閑內存段可以這麽理解:長度必須大於等於size,能等於最好,實在不行可以大於,在大於的情況下盡可能要“不要大太多,就是大的越少越好“(空間方面)

以下圖為例子:假設slab通過alloc_pages分配到了一個頁(order=0),slab會將這個頁劃分成若幹個object和一個head。head用slab描述這個頁中的哪些object是空閑的。這樣以來slab數據結構一個很直觀的設計如下:

技術分享圖片

內存管理-slab[原理]