成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux對象分配器 Slab 算法

系統 Linux 算法
為了解決小塊內存申請的問題, Linux內核引入了 SLAB 分配算法. Linux 所使用的 SLAB 分配算法 的基礎是 Jeff Bonwick 為SunOS 操作系統首次引入的一種算法。

[[414991]]

本文轉載自微信公眾號「Linux內核那些事」,作者songsong001。轉載本文請聯系Linux內核那些事公眾號。

由于這篇文章寫得比較早, 當時使用的是Linux-2.4.16版本內核, 但不影響整體的源碼分析.

SLAB分配算法

上一節說過Linux內核使用伙伴系統算法來管理內存頁, 但伙伴系統算法分配的單位是內存頁, 就是至少要分配一個或以上的內存塊. 但很多時候我們并不需要分配一個內存頁, 例如我們要申請一個大小為200字節的結構體時, 如果使用伙伴系統分配算法至少申請一個內存頁, 但只使用了200字節的內存, 那么剩余的3896字節就被浪費掉了.

為了解決小塊內存申請的問題, Linux內核引入了 SLAB 分配算法. Linux 所使用的 SLAB 分配算法 的基礎是 Jeff Bonwick 為SunOS 操作系統首次引入的一種算法。在內核中,會為有限的對象集(例如文件描述符和其他常見結構)分配大量內存。Jeff發現對內核中普通對象進行初始化所需的時間超過了對其進行分配和釋放所需的時間。因此他的結論是不應該將內存釋放回一個全局的內存池,而是將內存保持為針對特定目而初始化的狀態。

為了更好的理解 SLAB分配算法, 我們先來介紹一下算法使用到的數據結構.

相關數據結構

SLAB分配算法 有兩個重要的數據結構, 一個是 kmem_cache_t,另外一個是 slab_t . 下面我們先來看看 kmem_cache_t 結構:

  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結構中比較重要的字段:

  • slab_full:完全分配的slab
  • slab_partial:部分分配的slab
  • slab_free:沒有被分配過的slab
  • objsize:存儲的對象大小
  • num:一個slab能夠存儲的對象個數
  • gfporder:一個slab由2gfporder個內存頁組成
  • colour/colour_off/colour_next:著色區大小(后面會講到)

slab_t結構定義如下:

  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結構各個字段的用途如下:

  • list:連接(全滿/部分/全空)鏈
  • colouroff:著色補償
  • s_mem:存儲對象的起始內存地址
  • inuse:已經分配多少個對象
  • free:用于連接空閑的對象

用圖來表示它們之間的關系,如下:

SLAB分配算法初始化

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

  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; 

這個函數主要用來初始化 cache_cache 這個變量,cache_cache 是一個類型為 kmem_cache_t 的結構體變量,定義如下:

  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. }; 

為什么需要一個這樣的對象呢?因為本身 kmem_cache_t 結構體也是小內存對象,所以也應該有slab分配器來分配的,但這樣就出現“雞蛋和雞誰先出現”的問題。在系統初始化的時候,slab分配器還沒有初始化,所以并不能使用slab分配器來分配一個 kmem_cache_t 對象,這時候只能通過定義一個 kmem_cache_t 類型的靜態變量來來管理slab分配器了,所以 cache_cache 靜態變量就是用來管理slab分配器的。

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

kmem_cache_init() 函數調用了 kmem_cache_estimate() 函數來計算一個slab能夠保存多少個大小為 cache_cache.objsize 的對象,并保存到 cache_cache.num 字段中。一個slab中不可能全部都用來分配對象的,舉個例子:一個4096字節大小的slab用來分配大小為22字節的對象,可以劃分為186個,但還剩余4字節不能使用的,所以這部分內存用來作為著色區。著色區的作用是為了錯開不同的slab,讓CPU更有效的緩存slab。當然這屬于優化部分,對slab分配算法沒有多大的影響。就是說就算不對slab進行著色操作,slab分配算法還是可以工作起來的。

kmem_cache_t對象申請

kmem_cache_t 是用來管理和分配對象的,所以要使用slab分配器時,必須先申請一個 kmem_cache_t 對象,申請 kmem_cache_t 對象由 kmem_cache_create() 函數進行:

  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() 函數比較長,所以上面代碼去掉了一些不那么重要的地方,使代碼更清晰的體現其原理。

在 kmem_cache_create() 函數中,首先調用 kmem_cache_alloc() 函數申請一個 kmem_cache_t 對象,我們看到調用 kmem_cache_alloc() 時,傳入的就是 cache_cache 變量。申請完 kmem_cache_t對象 后需要對其進行初始化操作,主要是對 kmem_cache_t對象 的所有字段進行初始化:

  • 計算需要多少個頁面來作為slab的大小。
  • 計算一個slab能夠分配多少個對象。
  • 計算著色區信息。
  • 初始化 slab_full / slab_partial / slab_free 鏈表。
  • 把申請的 kmem_cache_t對象 保存到 cache_chain 鏈表中。

對象分配

申請完 kmem_cache_t對象 后,就使用通過調用 kmem_cache_alloc() 函數來申請指定的對象。kmem_cache_alloc() 函數代碼如下:

  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() 函數被我展開之后如上代碼,kmem_cache_alloc() 函數的主要步驟是:

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

如果 slab_partial 列表中沒有可用的slab,那么就從 slab_free 列表中查找可用的slab,如果有可用slab,就從slab分配一個對象,并且把此slab放置到 slab_partial 列表中。

如果 slab_free 列表中沒有可用的slab,那么就調用 kmem_cache_grow() 函數申請新的slab來進行對象的分配。kmem_cache_grow() 函數會調用 __get_free_pages() 函數來申請內存頁并且初始化slab.

一個slab的結構如下圖:

灰色部分是著色區,綠色部分是slab管理結構,黃色部分是空閑對象鏈表的索引,紅色部分是對象的實體。我們可以看到slab結構的s_mem字段指向了對象實體列表的起始地址。

分配對象的時候就是先通過slab結構的free字段查看是否有空閑的對象可用,free字段保存了空閑對象鏈表的首節點索引。

對象釋放

對象的釋放比較簡單,主要通過調用 kmem_cache_free() 函數完成,而 kmem_cache_free() 函數最終會調用 kmem_cache_free_one() 函數,代碼如下:

  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.     } 

對象釋放的時候首先會把對象的索引添加到slab的空閑對象鏈表中,然后根據slab的使用情況移動slab到合適的列表中。

如果slab所有對象都被釋放完時,把slab放置到 slab_free 列表中。

 

如果對象所在的slab原來在 slab_full 中,那么就把slab移動到 slab_partial 中。

 

責任編輯:武曉燕 來源: Linux內核那些事
相關推薦

2009-12-25 15:34:54

slab分配器

2024-12-11 08:18:11

2023-04-03 08:25:02

Linux內存slub

2013-10-12 11:15:09

Linux運維內存管理

2020-12-15 08:54:06

Linux內存碎片化

2024-10-11 10:00:20

2025-04-11 00:44:00

2025-05-27 02:45:45

2014-09-01 10:09:44

Linux

2013-10-14 10:41:41

分配器buddy syste

2017-02-08 08:40:21

C++固定內存塊

2017-01-17 16:17:48

C++固定分配器

2017-01-20 14:21:35

內存分配器存儲

2020-03-11 13:44:20

編程語言PythonJava

2023-12-22 07:55:38

Go語言分配策略

2018-12-06 10:22:54

Linux內核內存

2023-04-13 14:42:26

PoE供電器PoE交換機

2025-02-10 07:30:00

malloc內存分配器內存

2021-05-27 05:28:18

Linux 內存管理

2022-02-23 16:49:19

Linux內存數據結構
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美一级毛片在线播放 | 欧美精产国品一二三区 | 久久国内精品 | 在线观看视频福利 | 欧美另类视频在线 | 日韩a视频 | 激情五月婷婷综合 | 在线日韩欧美 | 日韩精品无码一区二区三区 | 国产欧美久久精品 | 婷婷久久一区 | 国产91色在线 | 亚洲 | 国产一级片久久久 | 久久久久9999亚洲精品 | 国产日韩视频在线 | 久久av一区 | 成人免费视频网站在线观看 | 国产99精品| 午夜影视网 | 欧美精品第一区 | xnxx 日本免费 | 一级日批片| 正在播放国产精品 | 亚洲一区二区 | 三级视频久久 | www成年人视频 | 一区二区精品 | 福利网址 | 成人网视频 | 成年网站在线观看 | 亚洲精品91 | 亚洲精品av在线 | 国产日韩欧美在线观看 | 国产一级在线 | 欧美成人a| 国产成人精品免费视频大全最热 | 免费一区 | 国产精品久久毛片av大全日韩 | 亚洲精品国产偷自在线观看 | 麻豆国产一区二区三区四区 | 国产蜜臀 |