自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux對(duì)象分配器 Slab 算法

系統(tǒng) Linux 算法
為了解決小塊內(nèi)存申請(qǐng)的問(wèn)題, Linux內(nèi)核引入了 SLAB 分配算法. Linux 所使用的 SLAB 分配算法 的基礎(chǔ)是 Jeff Bonwick 為SunOS 操作系統(tǒng)首次引入的一種算法。

[[414991]]

本文轉(zhuǎn)載自微信公眾號(hào)「Linux內(nèi)核那些事」,作者songsong001。轉(zhuǎn)載本文請(qǐng)聯(lián)系Linux內(nèi)核那些事公眾號(hào)。

由于這篇文章寫得比較早, 當(dāng)時(shí)使用的是Linux-2.4.16版本內(nèi)核, 但不影響整體的源碼分析.

SLAB分配算法

上一節(jié)說(shuō)過(guò)Linux內(nèi)核使用伙伴系統(tǒng)算法來(lái)管理內(nèi)存頁(yè), 但伙伴系統(tǒng)算法分配的單位是內(nèi)存頁(yè), 就是至少要分配一個(gè)或以上的內(nèi)存塊. 但很多時(shí)候我們并不需要分配一個(gè)內(nèi)存頁(yè), 例如我們要申請(qǐng)一個(gè)大小為200字節(jié)的結(jié)構(gòu)體時(shí), 如果使用伙伴系統(tǒng)分配算法至少申請(qǐng)一個(gè)內(nèi)存頁(yè), 但只使用了200字節(jié)的內(nèi)存, 那么剩余的3896字節(jié)就被浪費(fèi)掉了.

為了解決小塊內(nèi)存申請(qǐng)的問(wèn)題, Linux內(nèi)核引入了 SLAB 分配算法. Linux 所使用的 SLAB 分配算法 的基礎(chǔ)是 Jeff Bonwick 為SunOS 操作系統(tǒng)首次引入的一種算法。在內(nèi)核中,會(huì)為有限的對(duì)象集(例如文件描述符和其他常見(jiàn)結(jié)構(gòu))分配大量?jī)?nèi)存。Jeff發(fā)現(xiàn)對(duì)內(nèi)核中普通對(duì)象進(jìn)行初始化所需的時(shí)間超過(guò)了對(duì)其進(jìn)行分配和釋放所需的時(shí)間。因此他的結(jié)論是不應(yīng)該將內(nèi)存釋放回一個(gè)全局的內(nèi)存池,而是將內(nèi)存保持為針對(duì)特定目而初始化的狀態(tài)。

為了更好的理解 SLAB分配算法, 我們先來(lái)介紹一下算法使用到的數(shù)據(jù)結(jié)構(gòu).

相關(guān)數(shù)據(jù)結(jié)構(gòu)

SLAB分配算法 有兩個(gè)重要的數(shù)據(jù)結(jié)構(gòu), 一個(gè)是 kmem_cache_t,另外一個(gè)是 slab_t . 下面我們先來(lái)看看 kmem_cache_t 結(jié)構(gòu):

  1. struct kmem_cache_s { 
  2. /* 1) each alloc & free */ 
  3.     /* fullpartial firstthen free */ 
  4.     struct list_head    slabs_full; 
  5.     struct list_head    slabs_partial; 
  6.     struct list_head    slabs_free; 
  7.     unsigned int        objsize; 
  8.     unsigned int        flags;  /* constant flags */ 
  9.     unsigned int        num;    /* # of objs per slab */ 
  10.     spinlock_t      spinlock; 
  11. #ifdef CONFIG_SMP 
  12.     unsigned int        batchcount; 
  13. #endif 
  14.  
  15. /* 2) slab additions /removals */ 
  16.     /* order of pgs per slab (2^n) */ 
  17.     unsigned int        gfporder; 
  18.  
  19.     /* force GFP flags, e.g. GFP_DMA */ 
  20.     unsigned int        gfpflags; 
  21.  
  22.     size_t          colour;     /* cache colouring range */ 
  23.     unsigned int        colour_off; /* colour offset */ 
  24.     unsigned int        colour_next;    /* cache colouring */ 
  25.     kmem_cache_t        *slabp_cache; 
  26.     unsigned int        growing; 
  27.     unsigned int        dflags;     /* dynamic flags */ 
  28.  
  29.     /* constructor func */ 
  30.     void (*ctor)(void *, kmem_cache_t *, unsigned long); 
  31.  
  32.     /* de-constructor func */ 
  33.     void (*dtor)(void *, kmem_cache_t *, unsigned long); 
  34.  
  35.     unsigned long       failures; 
  36.  
  37. /* 3) cache creation/removal */ 
  38.     char            name[CACHE_NAMELEN]; 
  39.     struct list_head    next
  40.     ... 
  41. }; 

下面介紹一下kmem_cache_t結(jié)構(gòu)中比較重要的字段:

  • slab_full:完全分配的slab
  • slab_partial:部分分配的slab
  • slab_free:沒(méi)有被分配過(guò)的slab
  • objsize:存儲(chǔ)的對(duì)象大小
  • num:一個(gè)slab能夠存儲(chǔ)的對(duì)象個(gè)數(shù)
  • gfporder:一個(gè)slab由2gfporder個(gè)內(nèi)存頁(yè)組成
  • colour/colour_off/colour_next:著色區(qū)大小(后面會(huì)講到)

slab_t結(jié)構(gòu)定義如下:

  1. typedef struct slab_s { 
  2.     struct list_head    list; 
  3.     unsigned long       colouroff; 
  4.     void                *s_mem;     /* including colour offset */ 
  5.     unsigned int        inuse;      /* num of objs active in slab */ 
  6.     kmem_bufctl_t       free
  7. } slab_t; 

slab_t結(jié)構(gòu)各個(gè)字段的用途如下:

  • list:連接(全滿/部分/全空)鏈
  • colouroff:著色補(bǔ)償
  • s_mem:存儲(chǔ)對(duì)象的起始內(nèi)存地址
  • inuse:已經(jīng)分配多少個(gè)對(duì)象
  • free:用于連接空閑的對(duì)象

用圖來(lái)表示它們之間的關(guān)系,如下:

SLAB分配算法初始化

SLAB分配算法的初始化由 kmem_cache_init() 函數(shù)完成,如下:

  1. void __init kmem_cache_init(void) 
  2.     size_t left_over; 
  3.  
  4.     init_MUTEX(&cache_chain_sem); 
  5.     INIT_LIST_HEAD(&cache_chain); 
  6.  
  7.     kmem_cache_estimate(0, cache_cache.objsize, 0, 
  8.             &left_over, &cache_cache.num); 
  9.     if (!cache_cache.num) 
  10.         BUG(); 
  11.  
  12.     cache_cache.colour = left_over/cache_cache.colour_off; 
  13.     cache_cache.colour_next = 0; 

這個(gè)函數(shù)主要用來(lái)初始化 cache_cache 這個(gè)變量,cache_cache 是一個(gè)類型為 kmem_cache_t 的結(jié)構(gòu)體變量,定義如下:

  1. static kmem_cache_t cache_cache = { 
  2.     slabs_full: LIST_HEAD_INIT(cache_cache.slabs_full), 
  3.     slabs_partial:  LIST_HEAD_INIT(cache_cache.slabs_partial), 
  4.     slabs_free: LIST_HEAD_INIT(cache_cache.slabs_free), 
  5.     objsize:    sizeof(kmem_cache_t), 
  6.     flags:      SLAB_NO_REAP, 
  7.     spinlock:   SPIN_LOCK_UNLOCKED, 
  8.     colour_off: L1_CACHE_BYTES, 
  9.     name:       "kmem_cache"
  10. }; 

為什么需要一個(gè)這樣的對(duì)象呢?因?yàn)楸旧?kmem_cache_t 結(jié)構(gòu)體也是小內(nèi)存對(duì)象,所以也應(yīng)該有slab分配器來(lái)分配的,但這樣就出現(xiàn)“雞蛋和雞誰(shuí)先出現(xiàn)”的問(wèn)題。在系統(tǒng)初始化的時(shí)候,slab分配器還沒(méi)有初始化,所以并不能使用slab分配器來(lái)分配一個(gè) kmem_cache_t 對(duì)象,這時(shí)候只能通過(guò)定義一個(gè) kmem_cache_t 類型的靜態(tài)變量來(lái)來(lái)管理slab分配器了,所以 cache_cache 靜態(tài)變量就是用來(lái)管理slab分配器的。

從上面的代碼可知,cache_cache 的 objsize字段 被設(shè)置為 sizeof(kmem_cache_t) 的大小,所以這個(gè)對(duì)象主要是用來(lái)分配不同類型的 kmem_cache_t 對(duì)象的。

kmem_cache_init() 函數(shù)調(diào)用了 kmem_cache_estimate() 函數(shù)來(lái)計(jì)算一個(gè)slab能夠保存多少個(gè)大小為 cache_cache.objsize 的對(duì)象,并保存到 cache_cache.num 字段中。一個(gè)slab中不可能全部都用來(lái)分配對(duì)象的,舉個(gè)例子:一個(gè)4096字節(jié)大小的slab用來(lái)分配大小為22字節(jié)的對(duì)象,可以劃分為186個(gè),但還剩余4字節(jié)不能使用的,所以這部分內(nèi)存用來(lái)作為著色區(qū)。著色區(qū)的作用是為了錯(cuò)開(kāi)不同的slab,讓CPU更有效的緩存slab。當(dāng)然這屬于優(yōu)化部分,對(duì)slab分配算法沒(méi)有多大的影響。就是說(shuō)就算不對(duì)slab進(jìn)行著色操作,slab分配算法還是可以工作起來(lái)的。

kmem_cache_t對(duì)象申請(qǐng)

kmem_cache_t 是用來(lái)管理和分配對(duì)象的,所以要使用slab分配器時(shí),必須先申請(qǐng)一個(gè) kmem_cache_t 對(duì)象,申請(qǐng) kmem_cache_t 對(duì)象由 kmem_cache_create() 函數(shù)進(jìn)行:

  1. kmem_cache_t * 
  2. kmem_cache_create (const char *name, size_t size, size_t offset, 
  3.     unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long), 
  4.     void (*dtor)(void*, kmem_cache_t *, unsigned long)) 
  5.     const char *func_nm = KERN_ERR "kmem_create: "
  6.     size_t left_over, align, slab_size; 
  7.     kmem_cache_t *cachep = NULL
  8.  
  9.     ... 
  10.  
  11.     cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL); 
  12.     if (!cachep) 
  13.         goto opps; 
  14.     memset(cachep, 0, sizeof(kmem_cache_t)); 
  15.  
  16.     ... 
  17.  
  18.     do { 
  19.         unsigned int break_flag = 0; 
  20. cal_wastage: 
  21.         kmem_cache_estimate(cachep->gfporder, size, flags, 
  22.                         &left_over, &cachep->num); 
  23.         if (break_flag) 
  24.             break; 
  25.         if (cachep->gfporder >= MAX_GFP_ORDER) 
  26.             break; 
  27.         if (!cachep->num) 
  28.             goto next
  29.         if (flags & CFLGS_OFF_SLAB && cachep->num > offslab_limit) { 
  30.             cachep->gfporder--; 
  31.             break_flag++; 
  32.             goto cal_wastage; 
  33.         } 
  34.  
  35.         if (cachep->gfporder >= slab_break_gfp_order) 
  36.             break; 
  37.  
  38.         if ((left_over*8) <= (PAGE_SIZE<<cachep->gfporder)) 
  39.             break;  /* Acceptable internal fragmentation. */ 
  40. next
  41.         cachep->gfporder++; 
  42.     } while (1); 
  43.  
  44.     if (!cachep->num) { 
  45.         printk("kmem_cache_create: couldn't create cache %s.\n"name); 
  46.         kmem_cache_free(&cache_cache, cachep); 
  47.         cachep = NULL
  48.         goto opps; 
  49.     } 
  50.     slab_size = L1_CACHE_ALIGN(cachep->num*sizeof(kmem_bufctl_t)+sizeof(slab_t)); 
  51.  
  52.     if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) { 
  53.         flags &= ~CFLGS_OFF_SLAB; 
  54.         left_over -= slab_size; 
  55.     } 
  56.  
  57.     offset += (align-1); 
  58.     offset &= ~(align-1); 
  59.     if (!offset) 
  60.         offset = L1_CACHE_BYTES; 
  61.     cachep->colour_off = offset; 
  62.     cachep->colour = left_over/offset; 
  63.  
  64.     if (!cachep->gfporder && !(flags & CFLGS_OFF_SLAB)) 
  65.         flags |= CFLGS_OPTIMIZE; 
  66.  
  67.     cachep->flags = flags; 
  68.     cachep->gfpflags = 0; 
  69.     if (flags & SLAB_CACHE_DMA) 
  70.         cachep->gfpflags |= GFP_DMA; 
  71.     spin_lock_init(&cachep->spinlock); 
  72.     cachep->objsize = size
  73.     INIT_LIST_HEAD(&cachep->slabs_full); 
  74.     INIT_LIST_HEAD(&cachep->slabs_partial); 
  75.     INIT_LIST_HEAD(&cachep->slabs_free); 
  76.  
  77.     if (flags & CFLGS_OFF_SLAB) 
  78.         cachep->slabp_cache = kmem_find_general_cachep(slab_size,0); 
  79.     cachep->ctor = ctor; 
  80.     cachep->dtor = dtor; 
  81.     strcpy(cachep->namename); 
  82.  
  83. #ifdef CONFIG_SMP 
  84.     if (g_cpucache_up) 
  85.         enable_cpucache(cachep); 
  86. #endif 
  87.  
  88.     down(&cache_chain_sem); 
  89.     { 
  90.         struct list_head *p; 
  91.  
  92.         list_for_each(p, &cache_chain) { 
  93.             kmem_cache_t *pc = list_entry(p, kmem_cache_t, next); 
  94.  
  95.             if (!strcmp(pc->namename)) 
  96.                 BUG(); 
  97.         } 
  98.     } 
  99.  
  100.     list_add(&cachep->next, &cache_chain); 
  101.     up(&cache_chain_sem); 
  102. opps: 
  103.     return cachep; 

kmem_cache_create() 函數(shù)比較長(zhǎng),所以上面代碼去掉了一些不那么重要的地方,使代碼更清晰的體現(xiàn)其原理。

在 kmem_cache_create() 函數(shù)中,首先調(diào)用 kmem_cache_alloc() 函數(shù)申請(qǐng)一個(gè) kmem_cache_t 對(duì)象,我們看到調(diào)用 kmem_cache_alloc() 時(shí),傳入的就是 cache_cache 變量。申請(qǐng)完 kmem_cache_t對(duì)象 后需要對(duì)其進(jìn)行初始化操作,主要是對(duì) kmem_cache_t對(duì)象 的所有字段進(jìn)行初始化:

  • 計(jì)算需要多少個(gè)頁(yè)面來(lái)作為slab的大小。
  • 計(jì)算一個(gè)slab能夠分配多少個(gè)對(duì)象。
  • 計(jì)算著色區(qū)信息。
  • 初始化 slab_full / slab_partial / slab_free 鏈表。
  • 把申請(qǐng)的 kmem_cache_t對(duì)象 保存到 cache_chain 鏈表中。

對(duì)象分配

申請(qǐng)完 kmem_cache_t對(duì)象 后,就使用通過(guò)調(diào)用 kmem_cache_alloc() 函數(shù)來(lái)申請(qǐng)指定的對(duì)象。kmem_cache_alloc() 函數(shù)代碼如下:

  1. static inline void * kmem_cache_alloc_one_tail (kmem_cache_t *cachep, slab_t *slabp) 
  2.     void *objp; 
  3.  
  4.     ... 
  5.  
  6.     slabp->inuse++; 
  7.     objp = slabp->s_mem + slabp->free*cachep->objsize; 
  8.     slabp->free=slab_bufctl(slabp)[slabp->free]; 
  9.  
  10.     if (unlikely(slabp->free == BUFCTL_END)) { 
  11.         list_del(&slabp->list); 
  12.         list_add(&slabp->list, &cachep->slabs_full); 
  13.     } 
  14.  
  15.     return objp; 
  16.  
  17. #define kmem_cache_alloc_one(cachep)                \ 
  18. ({                                                  \ 
  19.     struct list_head * slabs_partial, * entry;      \ 
  20.     slab_t *slabp;                                  \ 
  21.                                                     \ 
  22.     slabs_partial = &(cachep)->slabs_partial;       \ 
  23.     entry = slabs_partial->next;                    \ 
  24.     if (unlikely(entry == slabs_partial)) {         \ 
  25.         struct list_head * slabs_free;              \ 
  26.         slabs_free = &(cachep)->slabs_free;         \ 
  27.         entry = slabs_free->next;                   \ 
  28.         if (unlikely(entry == slabs_free))          \ 
  29.             goto alloc_new_slab;                    \ 
  30.         list_del(entry);                            \ 
  31.         list_add(entry, slabs_partial);             \ 
  32.     }                                               \ 
  33.     slabp = list_entry(entry, slab_t, list);        \ 
  34.     kmem_cache_alloc_one_tail(cachep, slabp);       \ 
  35. }) 
  36.  
  37. static inline void * __kmem_cache_alloc (kmem_cache_t *cachep, int flags) 
  38.     unsigned long save_flags; 
  39.     void* objp; 
  40.  
  41.     kmem_cache_alloc_head(cachep, flags); 
  42. try_again: 
  43.     local_irq_save(save_flags); 
  44.     ... 
  45.     objp = kmem_cache_alloc_one(cachep); 
  46.  
  47.     local_irq_restore(save_flags); 
  48.     return objp; 
  49. alloc_new_slab: 
  50.     ... 
  51.     local_irq_restore(save_flags); 
  52.     if (kmem_cache_grow(cachep, flags)) 
  53.         goto try_again; 
  54.     return NULL
  55.  
  56. void * kmem_cache_alloc (kmem_cache_t *cachep, int flags) 
  57.     return __kmem_cache_alloc(cachep, flags); 

kmem_cache_alloc() 函數(shù)被我展開(kāi)之后如上代碼,kmem_cache_alloc() 函數(shù)的主要步驟是:

從kmem_cache_t對(duì)象 的 slab_partial 列表中查找是否有slab可用,如果有就直接從slab中分配一個(gè)對(duì)象。

如果 slab_partial 列表中沒(méi)有可用的slab,那么就從 slab_free 列表中查找可用的slab,如果有可用slab,就從slab分配一個(gè)對(duì)象,并且把此slab放置到 slab_partial 列表中。

如果 slab_free 列表中沒(méi)有可用的slab,那么就調(diào)用 kmem_cache_grow() 函數(shù)申請(qǐng)新的slab來(lái)進(jìn)行對(duì)象的分配。kmem_cache_grow() 函數(shù)會(huì)調(diào)用 __get_free_pages() 函數(shù)來(lái)申請(qǐng)內(nèi)存頁(yè)并且初始化slab.

一個(gè)slab的結(jié)構(gòu)如下圖:

灰色部分是著色區(qū),綠色部分是slab管理結(jié)構(gòu),黃色部分是空閑對(duì)象鏈表的索引,紅色部分是對(duì)象的實(shí)體。我們可以看到slab結(jié)構(gòu)的s_mem字段指向了對(duì)象實(shí)體列表的起始地址。

分配對(duì)象的時(shí)候就是先通過(guò)slab結(jié)構(gòu)的free字段查看是否有空閑的對(duì)象可用,free字段保存了空閑對(duì)象鏈表的首節(jié)點(diǎn)索引。

對(duì)象釋放

對(duì)象的釋放比較簡(jiǎn)單,主要通過(guò)調(diào)用 kmem_cache_free() 函數(shù)完成,而 kmem_cache_free() 函數(shù)最終會(huì)調(diào)用 kmem_cache_free_one() 函數(shù),代碼如下:

  1. static inline void kmem_cache_free_one(kmem_cache_t *cachep, void *objp) 
  2.     slab_t* slabp; 
  3.  
  4.     CHECK_PAGE(virt_to_page(objp)); 
  5.  
  6.     slabp = GET_PAGE_SLAB(virt_to_page(objp)); 
  7.  
  8.     { 
  9.         unsigned int objnr = (objp-slabp->s_mem)/cachep->objsize; 
  10.  
  11.         slab_bufctl(slabp)[objnr] = slabp->free
  12.         slabp->free = objnr; 
  13.     } 
  14.     STATS_DEC_ACTIVE(cachep); 
  15.      
  16.     { 
  17.         int inuse = slabp->inuse; 
  18.         if (unlikely(!--slabp->inuse)) { 
  19.             list_del(&slabp->list); 
  20.             list_add(&slabp->list, &cachep->slabs_free); 
  21.         } else if (unlikely(inuse == cachep->num)) { 
  22.             list_del(&slabp->list); 
  23.             list_add(&slabp->list, &cachep->slabs_partial); 
  24.         } 
  25.     } 

對(duì)象釋放的時(shí)候首先會(huì)把對(duì)象的索引添加到slab的空閑對(duì)象鏈表中,然后根據(jù)slab的使用情況移動(dòng)slab到合適的列表中。

如果slab所有對(duì)象都被釋放完時(shí),把slab放置到 slab_free 列表中。

 

如果對(duì)象所在的slab原來(lái)在 slab_full 中,那么就把slab移動(dòng)到 slab_partial 中。

 

責(zé)任編輯:武曉燕 來(lái)源: Linux內(nèi)核那些事
相關(guān)推薦

2009-12-25 15:34:54

slab分配器

2024-12-11 08:18:11

2023-04-03 08:25:02

Linux內(nèi)存slub

2013-10-12 11:15:09

Linux運(yùn)維內(nèi)存管理

2020-12-15 08:54:06

Linux內(nèi)存碎片化

2024-10-11 10:00:20

2025-04-11 00:44:00

2014-09-01 10:09:44

Linux

2013-10-14 10:41:41

分配器buddy syste

2017-02-08 08:40:21

C++固定內(nèi)存塊

2017-01-17 16:17:48

C++固定分配器

2017-01-20 14:21:35

內(nèi)存分配器存儲(chǔ)

2020-03-11 13:44:20

編程語(yǔ)言PythonJava

2023-04-13 14:42:26

PoE供電器PoE交換機(jī)

2023-12-22 07:55:38

Go語(yǔ)言分配策略

2018-12-06 10:22:54

Linux內(nèi)核內(nèi)存

2025-02-10 07:30:00

malloc內(nèi)存分配器內(nèi)存

2021-05-27 05:28:18

Linux 內(nèi)存管理

2022-02-23 16:49:19

Linux內(nèi)存數(shù)據(jù)結(jié)構(gòu)

2024-10-28 11:25:21

豐巢快遞jemalloc
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)