您當前的位置:首頁 > 攝影

Linux記憶體管理:slub分配器

作者:由 Yann 發表于 攝影時間:2020-08-04

概述:

我們知道核心中的物理記憶體由夥伴系統(buddy system)進行管理,它的分配粒度是以物理頁幀(page)為單位的,但核心中有大量的資料結構只需要若干bytes的空間,倘若仍按頁來分配,勢必會造成大量的記憶體被浪費掉。slab分配器的出現就是為了解決核心中這些小塊記憶體分配與管理的難題。這個概念首先在sun公司的SunOS5。4作業系統中得以實現。slab分配器是基於buddy頁分配器,在它上面實現了一層面向物件的快取管理機制(是不是感覺有點像malloc函式在glibc中實現的記憶體池)。

關於本文,slab分配器的主要內容大致可分為三部分,第一部分包括基本概念,第二部分是slab的使用與原理,第三部分如何除錯slab。

slub機制:

雖然核心是用面對過程的C語言實現的,但是核心的在許多功能的設計上也運用了面對物件的思想,slab分配器就是其中之一,它把常用的資料結構都看成一個個物件。我們知道buddy分配器的分配單元是以頁為單位的,然後將不同order的空閒物理頁幀串成若干連結串列,分配時從對應連結串列裡取出。而slab分配器則是以目標資料結構為單分配單元,且會將目標資料結構提前分配並串成連結串列,分配時從中取用。

古時候slab分配器就單指slab分配器,從2。6核心開始對slab分配器的實現添加了兩個備選方案slub和slob,其實現在用slub比較多,包括筆者本人的板子和裝置上的核心也都是slub。我認為slub就是在之前slab上最佳化後的一個產物,去除了許多臃腫的實現,逐漸會完全替代老的slab;而slob則是一個很輕量級的slab實現,程式碼量不大,官方說適合一些嵌入式裝置。接下來我們詳細介紹一下slub機制。

設計思想:

首先我們要知道是slab分配器是基於buddy分配器的,即slab需要從buddy分配器獲取連續的物理頁幀作為製造物件的原材料。簡單來說,就是基於buddy分配器獲得連續的pages,作為某資料結構物件的快取,再將這段連續的pages從內部切割成一個個對齊的物件,使用時從中取用,這樣一段連續的page我們稱為一個slab。

Linux記憶體管理:slub分配器

slub分配器的使用:

/*分配一塊給某個資料結構使用的快取描述符

name:物件的名字 size:物件的實際大小 align:對齊要求,通常填0,建立是自動選擇。 flags:可選標誌位 ctor: 建構函式 */

struct kmem_cache *kmem_cache_create( const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void*));

/*銷燬kmem_cache_create分配的kmem_cache*/

int kmem_cache_destroy( struct kmem_cache *cachep);

/*從kmem_cache中分配一個object flags引數:GFP_KERNEL為常用的可睡眠的,GFP_ATOMIC從不睡眠 GFP_NOFS等等等*/

void* kmem_cache_alloc(struct kmem_cache* cachep, gfp_t flags);

/*釋放object,把它返還給原先的slab*/

void kmem_cache_free(struct kmem_cache* cachep, void* objp);

slab分配器使用起來很簡單,透過上面的4個介面即可以為我們需要的object建立快取並從中申請object。

1。先透過kmem_cache_create建立一個快取管理描述符kmem_cache。

2。使用kmem_cache_alloc從快取kmem_cache中申請object使用。

這裡有個複雜且重要的結構體:struct kmem_cache,即快取描述符。準確的來說它並不包含實際的快取空間,而是包含了一些快取的管理資料,和指向實際快取空間的指標。

關鍵資料結構:kmem_cache

從上面的框圖頁我們可以看出kmem_cache在slab分配器中有著舉足輕重的地位, slub設計思想和實現都藏在了struct kmem_cache中。核心中有著大量的資料結構都是透過slab分配器分配,它們申請並維護自己的kmem_cache,所有的kmem_cache又都被串在一個名為slab_caches的雙向連結串列上。

kmem_cache資料結構中包含著許多的slab,其中一部存在於kmem_cache_node->partial中,每個node(若UMA架構則只有一個node)對應kmem_cache_node陣列中的一項。另一部分slab位於per cpu的kmem_cache_cpu變數的partial成員中,kmem_cache_cpu為每個cpu提供一份本地的slab快取(其實,這部分slab也是來源於kmem_cache_node中的slab)

文章的開始我們也已經描述了slab就是1個或幾個連續的page,然後在內部被切分成若干個物件(objects),同一塊快取中的slab大小相同,所以切分得到的物件數量也相同。slab中沒有被使用的物件稱為空閒物件(free object),同一slab中的所有空閒物件被串成了一個單項鍊表。如何串起來的呢?每個空閒物件的內部都會存有下一個空閒物件的地址,這樣一來slab內的free objects就形成了一個單向連結串列,需要注意的是這個地址並未放在空閒物件的首地址處,而是首地址 + kmem_cache->offset的地方。

說了那麼多,畫了副圖來表達kmem_cache資料結構之間關係,希望能幫助理解:

Linux記憶體管理:slub分配器

上圖展示了各個資料結構之間的關係,接下來我們結合上圖來看一下相關資料結構成員的含義:

struct kmem_cache {

/*per-cpu變數,用來實現每個CPU上的slab快取。好處如下:

1。促使cpu_slab->freelist可以無鎖訪問,避免了競爭,提升分配速度

2。使得本地cpu快取中分配出的objects被同一cpu訪問,提升TLB對object的命中率(因為一個page中有多個object,他們共用同一個PTE)

*/

struct kmem_cache_cpu __percpu *cpu_slab;

/*下面這些是初始化kmem_cache時會設定的一些變數 */

/*分配時會用到的flags*/

slab_flags_t flags;

/*kmem_cache_shrink縮減partial slabs時,將被保有slab的最小值。由函式set_min_partial(s, ilog2(s->size)/2)設定。*/

unsigned long min_partial;

/*object的實際大小,包含元資料和對齊的空間*/

unsigned int size;

/*object中payload的大小,即目標資料結構的實際大小*/

unsigned int object_size;

/*每個free object中都存了next free object的地址,但是並未存在object的首地址,而是首地址加上offset的地方*/

unsigned int offset;

/*此結構體實際是個unsigned int,裡面存了單個slab的佔用的order數和一個slab中object的數量*/

struct kmem_cache_order_objects oo;

/* Allocation and freeing of slabs */

struct kmem_cache_order_objects max;

struct kmem_cache_order_objects min;

/*標準gfp掩碼,用於從buddy分配頁面時*/

gfp_t allocflags; /* gfp flags to use on each alloc */

int refcount; /* Refcount for slab cache destroy */

/*object的建構函式,通常不使用*/

void (*ctor)(void *);

/*object中到metadata的偏移*/

unsigned int inuse;

/*對齊大小。澄清:slab中對齊方式通常有兩種。1是按處理器字長對齊;2是按照cacheline大小對齊。*/

unsigned int align;

/*若flags中使用REDZONE時有意義*/

unsigned int red_left_pad; /* Left redzone padding size */

/*物件名稱,例:mm_struct task_struct*/

const char *name;

/*kmem_cache的連結串列結構,透過此成員串在slab_caches連結串列上*/

struct list_head list;

/*下面兩個成員用於表示物件內部的一塊空間,使userspace可以訪問其中的內容。具體可以看kmem_cache_create_usercopy的實現*/

unsigned int useroffset;

unsigned int usersize;

/*每個node對應一個數組項,kmem_cache_node中包含partial slab連結串列*/

struct kmem_cache_node *node[MAX_NUMNODES];

};

struct kmem_cache_cpu {

/*指向下面page指向的slab中的第一個free object*/

void **freelist;

/* Globally unique transaction id */

unsigned long tid;

/*指向當前正在使用的slab*/

struct page *page;

/*本地slab快取池中的partial slab連結串列*/

struct page *partial;

};

struct kmem_cache_node {

/*kmem_cache_node資料結構的自選鎖,可能涉及到多核訪問*/

spinlock_t list_lock;

/*node中slab的數量*/

unsigned long nr_partial;

/*指向partial slab連結串列*/

struct list_head partial;

};

slab的內部結構:

Linux記憶體管理:slub分配器

struct page中的slub成員:

順便提一嘴,每個物理頁都對應一個struct page結構體,結構體中有個聯合體,其中定義了一些slab分配器要用到的成員。若該page用於slab,則下面成員將生效並被使用,程式碼如下。需要注意的是這裡也有個freelist,它指向所屬slab的第一個free object,不要和kmem_cache_cpu中的freelist混淆。

struct page {

……

struct { /* slab, slob and slub */

union {

struct list_head slab_list; /* uses lru */

struct { /* Partial pages */

struct page *next;

int pages; /* Nr of pages left */

int pobjects; /* Approximate count */

};

};

struct kmem_cache *slab_cache; /* not slob */

/* Double-word boundary */

void *freelist; /* 指向slab中第一個free object */

union {

void *s_mem; /* slab: first object */

unsigned long counters; /* SLUB */

struct { /* SLUB */

unsigned inuse:16; /*當前slab中已經分配的object數量*/

unsigned objects:15;

unsigned frozen:1;

};

};

……

}

分配物件:

object的分配透過kmem_cache_alloc()介面,實際分配object的過程會存在以下幾種情形:

fast path:即可直接從本地cpu快取中的freelist拿到可用object

kmem_cache_alloc

slab_alloc

slab_alloc_node

——>object = c->freelist //本地cpu快取的freelist有可用的object

——>void *next_object=get_freepointer_safe(s, object); //獲取next object的地址,用於後面更新freelist

——>this_cpu_cmpxchg_double //更新cpu_slab->freelist和cpu_slab->tid

——>prefetch_freepointer(s, next_object); //最佳化語句,將next object的地址放入cacheline,提高後面用到時的命中率

——>stat(s, ALLOC_FASTPATH); //設定狀態為ALLOC_FASTPATH

slow path:本地cpu快取中的freelist為NULL,但本地cpu快取中的partial中有未滿的slab

kmem_cache_alloc

slab_alloc

slab_alloc_node

__slab_alloc //分配過程關閉了本地中斷

___slab_alloc

——>page = c->page為NULL的情況下 //即本地cpu快取中當前在使用的slab的free object已經分完

——>goto new_slab; //跳轉到new_slab,從本地快取池的partial取一個slab賦給page,並跳轉到redo

——>freelist = get_freelist(s, page) //獲取page中的freelist(注意:此freelist為strcut page中的,並非本地cpu快取的freelist)

——>c->freelist = get_freepointer(s, freelist) //將freelist重新賦給kmem_cache_cpu中的freelist

very slow path:本地cpu快取中的freelist為NULL,且本地cpu快取中的partial也無slab可用。

kmem_cache_alloc

slab_alloc

slab_alloc_node

__slab_alloc //分配過程關閉了本地中斷

___slab_alloc

——>page = c->page為NULL的情況下 //即本地cpu快取中當前在使用的slab的free object已經分完

——>goto new_slab; //跳轉到new_slab,透過slub_percpu_partial(c)檢查到本地cpu快取池中partial無slab可用。

——>freelist = new_slab_objects(s, gfpflags, node, &c); //此函式中會出現兩種情況:情況1。當前node對應的kmem_cache_node中有可用partial slab,並從

中獲取slab分給本地cpu緩衝池。情況2。當前node對應的kmem_cache_node無可用的partial slab,

透過new_slab->allocate_slab->alloc_slab_page->alloc_pages從buddy分配器申請記憶體並建立新

的slab。兩種情況最終都會返回一個可用的freelist

——>c->freelist = get_freepointer(s, freelist) //將freelist重新賦給kmem_cache_cpu中的freelist

slab的回收:

object的回收和分配有些類似,也分為slow path和fast path。暫時沒有寫,後面有空的話補上。

slab除錯:

透過/proc/slabinfo

root@intel-x86-64:~# cat /proc/slabinfo

slabinfo - version: 2。1

# name : tunables : slabdata

l>

ecryptfs_key_record_cache 0 0 576 28 4 : tunables 0 0 0 : slabdata 0 0 0

ecryptfs_inode_cache 0 0 960 34 8 : tunables 0 0 0 : slabdata 0 0 0

ecryptfs_file_cache 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0

ecryptfs_auth_tok_list_item 0 0 832 39 8 : tunables 0 0 0 : slabdata 0 0 0

i915_dependency 0 0 128 32 1 : tunables 0 0 0 : slabdata 0 0 0

execute_cb 0 0 128 32 1 : tunables 0 0 0 : slabdata 0 0 0

i915_request 5 28 576 28 4 : tunables 0 0 0 : slabdata 1 1 0

intel_context 5 21 384 21 2 : tunables 0 0 0 : slabdata 1 1 0

nfsd4_delegations 0 0 248 33 2 : tunables 0 0 0 : slabdata 0 0 0

……

透過slabtop工具

Active / Total Objects (% used) : 428417 / 433991 (98。7%)

Active / Total Slabs (% used) : 12139 / 12139 (100。0%)

Active / Total Caches (% used) : 86 / 156 (55。1%)

Active / Total Size (% used) : 117938。18K / 120241。55K (98。1%)

Minimum / Average / Maximum Object : 0。01K / 0。28K / 16。25K

OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME

42944 42688 99% 0。06K 671 64 2684K anon_vma_chain

40320 38417 95% 0。19K 1920 21 7680K dentry

38752 38752 100% 0。12K 1211 32 4844K kernfs_node_cache

33138 31872 96% 0。19K 1578 21 6312K cred_jar

26880 26880 100% 0。03K 210 128 840K kmalloc-32

23976 23929 99% 0。59K 888 27 14208K inode_cache

22218 22218 100% 0。09K 483 46 1932K anon_vma

16576 16260 98% 0。25K 518 32 4144K filp

15360 15360 100% 0。01K 30 512 120K kmalloc-8

15104 15104 100% 0。02K 59 256 236K kmalloc-16

……

crash工具的kmem命令

crash中的kmem有著很多的用法,可透過help kmem詳細瞭解,下面簡單兩個常用的命令。

kmem -i:檢視系統的記憶體使用情況。可以看到slab一共佔用124。3MB

crash> kmem -i

PAGES TOTAL PERCENTAGE

TOTAL MEM 4046335 15。4 GB ——

FREE 3663809 14 GB 90% of TOTAL MEM

USED 382526 1。5 GB 9% of TOTAL MEM

SHARED 15487 60。5 MB 0% of TOTAL MEM

BUFFERS 0 0 0% of TOTAL MEM

CACHED 191471 747。9 MB 4% of TOTAL MEM

SLAB 31825 124。3 MB 0% of TOTAL MEM

TOTAL HUGE 0 0 ——

HUGE FREE 0 0 0% of TOTAL HUGE

TOTAL SWAP 0 0 ——

SWAP USED 0 0 0% of TOTAL SWAP

SWAP FREE 0 0 0% of TOTAL SWAP

COMMIT LIMIT 2023167 7。7 GB ——

COMMITTED 404292 1。5 GB 19% of TOTAL LIMIT

2。 kmem -S object_name:檢視某個kmem_cache的slab使用情況

crash> kmem -S mm_struct

CACHE OBJSIZE ALLOCATED TOTAL SLABS SSIZE NAME

ffff9275fd00a840 1056 31 240 8 32k mm_struct

CPU 0 KMEM_CACHE_CPU:

ffff9275fec2eff0

CPU 0 SLAB:

SLAB MEMORY NODE TOTAL ALLOCATED FREE

ffffcdc190f25400 ffff9275fc950000 0 30 5 25

FREE / [ALLOCATED]

[ffff9275fc950000]

[ffff9275fc950440]

ffff9275fc950880 (cpu 0 cache)

[ffff9275fc950cc0]

ffff9275fc951100 (cpu 0 cache)

ffff9275fc951540 (cpu 0 cache)

ffff9275fc951980 (cpu 0 cache)

ffff9275fc951dc0 (cpu 0 cache)

ffff9275fc952200 (cpu 0 cache)

ffff9275fc952640 (cpu 0 cache)

[ffff9275fc952a80]

ffff9275fc952ec0 (cpu 0 cache)

ffff9275fc953300 (cpu 0 cache)

……。。。

參考:

《linux核心設計與實現》第三版

《深入理解linux核心架構》

The Linux Foundation官方網站

https://

hammertux。github。io/sla

b-allocator

https://

fliphtml5。com/traq/olio

/basic

原創文章,轉載和引用請註明出處。

作者:Yann Xu

標簽: slab  cache  kmem  CPU  object