1. 程式人生 > 其它 >Linux記憶體管理:slub分配器【轉】

Linux記憶體管理:slub分配器【轉】

轉自:https://zhuanlan.zhihu.com/p/166649492

概述:

我們知道核心中的實體記憶體由夥伴系統(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。

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資料結構之間關係,希望能幫助理解:

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

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的內部結構:

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            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavai
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詳細瞭解,下面簡單兩個常用的命令。

  1. 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官方網站

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

作者:Yann Xu

編輯於 2020-08-04 【作者】張昺華 【出處】http://www.cnblogs.com/sky-heaven/ 【部落格園】 http://www.cnblogs.com/sky-heaven/ 【知乎】 http://www.zhihu.com/people/zhang-bing-hua 【我的作品---旋轉倒立擺】 http://v.youku.com/v_show/id_XODM5NDAzNjQw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【我的作品---自平衡自動循跡車】 http://v.youku.com/v_show/id_XODM5MzYyNTIw.html?spm=a2hzp.8253869.0.0&from=y1.7-2 【大餅教你學系列】https://edu.csdn.net/course/detail/10393 【新浪微博】 張昺華--sky 【twitter】 @sky2030_ 【微信公眾號】 張昺華 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利.