最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

一文給你解決linux內(nèi)存源碼分析- SLAB分配器概述(超詳細(xì))

2022-02-20 11:27 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

SLAB分配器概述

  • 管理區(qū)頁(yè)框分配器,這里我們簡(jiǎn)稱為頁(yè)框分配器,在頁(yè)框分配器中主要是管理物理內(nèi)存,將物理內(nèi)存的頁(yè)框分配給申請(qǐng)者,而且我們知道也可頁(yè)框大小為4K(也可設(shè)置為4M),這時(shí)候就會(huì)有個(gè)問(wèn)題,如果我只需要1KB大小的內(nèi)存,頁(yè)框分配器也不得不分配一個(gè)4KB的頁(yè)框給申請(qǐng)者,這樣就會(huì)有3KB被白白浪費(fèi)掉了。為了應(yīng)對(duì)這種情況,在頁(yè)框分配器上一層又做了一層SLAB層,SLAB分配器的作用就是從頁(yè)框分配器中拿出一些頁(yè)框,專門把這些頁(yè)框拆分成一小塊一小塊的小內(nèi)存,當(dāng)申請(qǐng)者申請(qǐng)的是小內(nèi)存時(shí),系統(tǒng)就會(huì)從SLAB中獲取一小塊分配給申請(qǐng)者。它們的整個(gè)關(guān)系如下圖:




  • 可以看出,SLAB分配器和頁(yè)框分配器并沒(méi)有什么直接的聯(lián)系,對(duì)于頁(yè)框分配器來(lái)說(shuō),SLAB分配器也只是一個(gè)從它那里申請(qǐng)頁(yè)框的申請(qǐng)者而已。

  • 在SLAB分配器中將SLAB分為兩大類:專用SLAB和普通SLAB。專用SLAB用于特定的場(chǎng)合(比如TCP有自己專用的SLAB,當(dāng)TCP模塊需要小內(nèi)存時(shí),會(huì)從自己的SLAB中分配),而普通SLAB就是用于常規(guī)分配的時(shí)候。我們可以使用命令查看SLAB的狀態(tài)

cat /proc/slabinfo

命令結(jié)果如下:



  • 如剛才所有,我們看到有些SLAB的名字比較特別,如TCP,UDP,dquot這些,它們都是專用SLAB,專屬于它們自己的模塊。而后面這張圖,如kmalloc-8,kmalloc-16...還有dma-kmalloc-96,dma-kmalloc-192...這些都是普通SLAB,當(dāng)需要為一些小數(shù)據(jù)分配內(nèi)存時(shí)(比如一個(gè)結(jié)構(gòu)體),就會(huì)從這些普通SLAB中獲取內(nèi)存。值得注意的是,對(duì)于kmalloc-8這些普通SLAB,都有一個(gè)對(duì)應(yīng)的dma-kmalloc-8這種類型的普通SLAB,這種類型是專門使用了ZONE-DMA區(qū)域的內(nèi)存,方便用于DMA模式申請(qǐng)內(nèi)存。

  • 在SLAB中,可分配的內(nèi)存塊稱之為對(duì)象,在后面那張圖中,如kmalloc-8這個(gè)普通SLAB,里面所有的對(duì)象都是8B大小,同理,kmalloc-16中的對(duì)象都是以16B為大小。當(dāng)你申請(qǐng)1B~8B的內(nèi)存時(shí),系統(tǒng)會(huì)從kmalloc-8中分配一個(gè)對(duì)象給你,當(dāng)你申請(qǐng)8B~16B的內(nèi)存時(shí),系統(tǒng)會(huì)從kmalloc-16里給你分配。雖然即使申請(qǐng)5B,分配了一個(gè)8B的對(duì)象,還有3B空閑,但這樣設(shè)計(jì)已經(jīng)大大減小了內(nèi)存碎片化了,保證了碎片內(nèi)存不會(huì)超過(guò)50%(kmalloc-8除外)。需要注意,在kmalloc-8中申請(qǐng)到的對(duì)象,釋放時(shí)也會(huì)回到kmalloc-8中。

  • 除了減小了內(nèi)存碎片化,SLAB還有一個(gè)作用,提高了系統(tǒng)的效率,當(dāng)對(duì)象擁有者釋放一個(gè)對(duì)象后,SLAB的處理是僅僅標(biāo)記對(duì)象為空閑,并不做多少處理,而又有申請(qǐng)者申請(qǐng)相應(yīng)大小的對(duì)象時(shí),SLAB會(huì)優(yōu)先分配最近釋放的對(duì)象,這樣這個(gè)對(duì)象甚至有可能還在硬件高速緩存中,有點(diǎn)類似管理區(qū)頁(yè)框分配器中每CPU高速緩存的做法。

kmem_cache結(jié)構(gòu)

  • 雖然叫SLAB分配器,但是在SLAB分配器中,最頂層的數(shù)據(jù)結(jié)構(gòu)卻不是SLAB,而是kmem_cache,我們暫且叫它SLAB緩存吧,每個(gè)SLAB緩存都有它自己的名字,就是上圖中的kmalloc-8,kmalloc-16等。總的來(lái)說(shuō),kmem_cache結(jié)構(gòu)用于描述一種SLAB,并且管理著這種SLAB中所有的對(duì)象。所有的kmem_cache結(jié)構(gòu)會(huì)保存在以slab_caches作為頭的鏈表中。在內(nèi)核模塊中可以通過(guò)kmem_cache_create自行創(chuàng)建一個(gè)kmem_cache用于管理屬于自己模塊的SLAB。

  • 我們先看看kmem_cache結(jié)構(gòu):

/* slab分配器中的SLAB高速緩存 */
struct kmem_cache {
 ? ?/* 指向包含空閑對(duì)象的本地高速緩存,每個(gè)CPU有一個(gè)該結(jié)構(gòu),當(dāng)有對(duì)象釋放時(shí),優(yōu)先放入本地CPU高速緩存中 */
 ? ?struct array_cache __percpu *cpu_cache;

/* 1) Cache tunables. Protected by slab_mutex */
 ? ?/* 要轉(zhuǎn)移進(jìn)本地高速緩存或從本地高速緩存中轉(zhuǎn)移出去的對(duì)象的數(shù)量 */
 ? ?unsigned int batchcount;
 ? ?/* 本地高速緩存中空閑對(duì)象的最大數(shù)目 */
 ? ?unsigned int limit;
 ? ?/* 是否存在CPU共享高速緩存,CPU共享高速緩存指針保存在kmem_cache_node結(jié)構(gòu)中 */
 ? ?unsigned int shared;

 ? ?/* 對(duì)象長(zhǎng)度 + 填充字節(jié) */
 ? ?unsigned int size;
 ? ?/* size的倒數(shù),加快計(jì)算 */
 ? ?struct reciprocal_value reciprocal_buffer_size;

 ? ?
/* 2) touched by every alloc & free from the backend */
 ? ?/* 高速緩存永久屬性的標(biāo)識(shí),如果SLAB描述符放在外部(不放在SLAB中),則CFLAGS_OFF_SLAB置1 */
 ? ?unsigned int flags; ? ? ? ?/* constant flags */
 ? ?/* 每個(gè)SLAB中對(duì)象的個(gè)數(shù)(在同一個(gè)高速緩存中slab中對(duì)象個(gè)數(shù)相同) */
 ? ?unsigned int num; ? ? ? ?/* # of objs per slab */


/* 3) cache_grow/shrink */
 ? ?/* 一個(gè)單獨(dú)SLAB中包含的連續(xù)頁(yè)框數(shù)目的對(duì)數(shù) */
 ? ?unsigned int gfporder;

 ? ?/* 分配頁(yè)框時(shí)傳遞給伙伴系統(tǒng)的一組標(biāo)識(shí) */
 ? ?gfp_t allocflags;

 ? ?/* SLAB使用的顏色個(gè)數(shù) */
 ? ?size_t colour; ? ? ? ? ? ?
 ? ?/* SLAB中基本對(duì)齊偏移,當(dāng)新SLAB著色時(shí),偏移量的值需要乘上這個(gè)基本對(duì)齊偏移量,理解就是1個(gè)偏移量等于多少個(gè)B大小的值 */
 ? ?unsigned int colour_off; ? ?
 ? ?/* 空閑對(duì)象鏈表放在外部時(shí)使用,其指向的SLAB高速緩存來(lái)存儲(chǔ)空閑對(duì)象鏈表 */
 ? ?struct kmem_cache *freelist_cache;
 ? ?/* 空閑對(duì)象鏈表的大小 */
 ? ?unsigned int freelist_size;

 ? ?/* 構(gòu)造函數(shù),一般用于初始化這個(gè)SLAB高速緩存中的對(duì)象 */
 ? ?void (*ctor)(void *obj);


/* 4) cache creation/removal */
 ? ?/* 存放高速緩存名字 */
 ? ?const char *name;
 ? ?/* 高速緩存描述符雙向鏈表指針 */
 ? ?struct list_head list;
 ? ?int refcount;
 ? ?/* 高速緩存中對(duì)象的大小 */
 ? ?int object_size;
 ? ?int align;


/* 5) statistics */
 ? ?/* 統(tǒng)計(jì) */
#ifdef CONFIG_DEBUG_SLAB
 ? ?unsigned long num_active;
 ? ?unsigned long num_allocations;
 ? ?unsigned long high_mark;
 ? ?unsigned long grown;
 ? ?unsigned long reaped;
 ? ?unsigned long errors;
 ? ?unsigned long max_freeable;
 ? ?unsigned long node_allocs;
 ? ?unsigned long node_frees;
 ? ?unsigned long node_overflow;
 ? ?atomic_t allochit;
 ? ?atomic_t allocmiss;
 ? ?atomic_t freehit;
 ? ?atomic_t freemiss;

 ? ?/* 對(duì)象間的偏移 */
 ? ?int obj_offset;
#endif /* CONFIG_DEBUG_SLAB */
#ifdef CONFIG_MEMCG_KMEM
 ? ?/* 用于分組資源限制 */
 ? ?struct memcg_cache_params *memcg_params;
#endif
 ? ?/* 結(jié)點(diǎn)鏈表,此高速緩存可能在不同NUMA的結(jié)點(diǎn)都有SLAB鏈表 */
 ? ?struct kmem_cache_node *node[MAX_NUMNODES];
};
  • 從結(jié)構(gòu)中可以看出,在這個(gè)kmem_cache中所有對(duì)象的大小是相同的(object_size),并且此kmem_cache中所有SLAB的大小也是相同的(gfporder、num)。

  • 在這個(gè)結(jié)構(gòu)中,最重要的可能就屬struct kmem_cache_node * node[Max_NUMNODES]這個(gè)指針數(shù)組了,指向的struct kmem_cache_node中保存著slab鏈表,在NUMA架構(gòu)中每個(gè)node對(duì)應(yīng)數(shù)組中的一個(gè)元素,因?yàn)槊總€(gè)SLAB高速緩存都有可能在不同結(jié)點(diǎn)維護(hù)有自己的SLAB用于這個(gè)結(jié)點(diǎn)的分配。我們看看struct kmem_cache_node:

/* SLAB鏈表結(jié)構(gòu) */
struct kmem_cache_node {
 ? ?/* 鎖 */
 ? ?spinlock_t list_lock;

/* SLAB用 */
#ifdef CONFIG_SLAB
 ? ?/* 只使用了部分對(duì)象的SLAB描述符的雙向循環(huán)鏈表 */
 ? ?struct list_head slabs_partial; ? ?/* partial list first, better asm code */
 ? ?/* 不包含空閑對(duì)象的SLAB描述符的雙向循環(huán)鏈表 */
 ? ?struct list_head slabs_full;
 ? ?/* 只包含空閑對(duì)象的SLAB描述符的雙向循環(huán)鏈表 */
 ? ?struct list_head slabs_free;
 ? ?/* 高速緩存中空閑對(duì)象個(gè)數(shù)(包括slabs_partial鏈表中和slabs_free鏈表中所有的空閑對(duì)象) */
 ? ?unsigned long free_objects;
 ? ?/* 高速緩存中空閑對(duì)象的上限 */
 ? ?unsigned int free_limit;
 ? ?/* 下一個(gè)被分配的SLAB使用的顏色 */
 ? ?unsigned int colour_next; ? ?/* Per-node cache coloring */
 ? ?/* 指向這個(gè)結(jié)點(diǎn)上所有CPU共享的一個(gè)本地高速緩存 */
 ? ?struct array_cache *shared; ? ?/* shared per node */
 ? ?struct alien_cache **alien; ? ?/* on other nodes */
 ? ?/* 兩次緩存收縮時(shí)的間隔,降低次數(shù),提高性能 */
 ? ?unsigned long next_reap; ? ?
 ? ?/* 0:收縮 ?1:獲取一個(gè)對(duì)象 */
 ? ?int free_touched; ? ? ? ?/* updated without locking */
#endif

/* SLUB用 */
#ifdef CONFIG_SLUB
 ? ?unsigned long nr_partial;
 ? ?struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
 ? ?atomic_long_t nr_slabs;
 ? ?atomic_long_t total_objects;
 ? ?struct list_head full;
#endif
#endif

};
  • 在這個(gè)結(jié)構(gòu)中,最重要的就是slabs_partial、slabs_full、slabs_free這三個(gè)鏈表頭。

  1. slabs_partial:維護(hù)部分對(duì)象被使用了的SLAB鏈表,保存的是SLAB描述符。

  2. slabs_full:維護(hù)所有對(duì)象都被使用了的SLAB鏈表,保存的是SLAB描述符。

  3. slabs_free:維護(hù)所有對(duì)象都沒(méi)被使用的SLAB鏈表,保存的是SLAB描述符。

  • 可能到這里大家會(huì)比較郁悶,怎么又有SLAB鏈表,SLAB到底是什么東西?SLAB就是一組連續(xù)的頁(yè)框,它的描述符結(jié)合在頁(yè)描述符中,也就是頁(yè)描述符描述SLAB的時(shí)候,就是SLAB描述符。這三個(gè)鏈表保存的是這組頁(yè)框的首頁(yè)框的SLAB描述符。鏈表的組織形式與伙伴系統(tǒng)的組織頁(yè)框的形式一樣。

  • 剛開(kāi)始創(chuàng)建kmem_cache完成后,這三個(gè)鏈表都為空,只有在申請(qǐng)對(duì)象時(shí)發(fā)現(xiàn)沒(méi)有可用的slab時(shí)才會(huì)創(chuàng)建一個(gè)新的SLAB,并加入到這三個(gè)鏈表中的一個(gè)中。也就是說(shuō)kmem_cache中的SLAB數(shù)量是動(dòng)態(tài)變化的,當(dāng)SLAB數(shù)量太多時(shí),kmem_cache會(huì)將一些SLAB釋放回頁(yè)框分配器中。

  • 我們看看SLAB描述符中相關(guān)字段:

struct page {
 ? ?/* First double word block */
 ? ?/* 用于頁(yè)描述符,一組標(biāo)志(如PG_locked、PG_error),也對(duì)頁(yè)框所在的管理區(qū)和node進(jìn)行編號(hào) */
 ? ?unsigned long flags; /
 ? ?union {
 ? ? ? ?/* 用于頁(yè)描述符,當(dāng)頁(yè)被插入頁(yè)高速緩存中時(shí)使用,或者當(dāng)頁(yè)屬于匿名區(qū)時(shí)使用 */
 ? ? ? ?struct address_space *mapping; 
 ? ? ? ?/* 用于SLAB描述符,指向第一個(gè)對(duì)象的地址 */
 ? ? ? ?void *s_mem; ? ? ? ? ? ?/* slab first object */
 ? ?};


 ? ?/* Second double word */
 ? ?struct {
 ? ? ? ?union {
 ? ? ? ? ? ?/* 作為不同的含義被幾種內(nèi)核成分使用。例如,它在頁(yè)磁盤映像或匿名區(qū)中標(biāo)識(shí)存放在頁(yè)框中的數(shù)據(jù)的位置,或者它存放一個(gè)換出頁(yè)標(biāo)識(shí)符 */
 ? ? ? ? ? ?pgoff_t index; ? ? ? ?/* Our offset within mapping. */
 ? ? ? ? ? ?/* 用于SLAB描述符,指向空閑對(duì)象鏈表 */
 ? ? ? ? ? ?void *freelist; ? ?
 ? ? ? ? ? ?/* 當(dāng)管理區(qū)頁(yè)框分配器壓力過(guò)大時(shí),設(shè)置這個(gè)標(biāo)志就確保這個(gè)頁(yè)框?qū)iT用于釋放其他頁(yè)框時(shí)使用 */
 ? ? ? ? ? ?bool pfmemalloc; 
 ? ? ? ?};

 ? ? ? ?union {
#if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \
 ? ?defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)
 ? ? ? ? ? ?/* Used for cmpxchg_double in slub */
 ? ? ? ? ? ?/* SLUB使用 */
 ? ? ? ? ? ?unsigned long counters;
#else
 ? ? ? ? ? ?/* SLUB使用 */
 ? ? ? ? ? ?unsigned counters;
#endif

 ? ? ? ? ? ?struct {

 ? ? ? ? ? ? ? ?union {
 ? ? ? ? ? ? ? ? ? ?/* 頁(yè)框中的頁(yè)表項(xiàng)計(jì)數(shù),如果沒(méi)有為-1,如果為PAGE_BUDDY_MAPCOUNT_VALUE(-128),說(shuō)明此頁(yè)及其后的一共2的private次方個(gè)數(shù)頁(yè)框處于伙伴系統(tǒng)中,正在使用時(shí)應(yīng)該是0 */
 ? ? ? ? ? ? ? ? ? ?atomic_t _mapcount;

 ? ? ? ? ? ? ? ? ? ?struct { /* SLUB使用 */
 ? ? ? ? ? ? ? ? ? ? ? ?unsigned inuse:16;
 ? ? ? ? ? ? ? ? ? ? ? ?unsigned objects:15;
 ? ? ? ? ? ? ? ? ? ? ? ?unsigned frozen:1;
 ? ? ? ? ? ? ? ? ? ?};
 ? ? ? ? ? ? ? ? ? ?int units; ? ?/* SLOB */
 ? ? ? ? ? ? ? ?};
 ? ? ? ? ? ? ? ?/* 頁(yè)框的引用計(jì)數(shù),如果為-1,則此頁(yè)框空閑,并可分配給任一進(jìn)程或內(nèi)核;如果大于或等于0,則說(shuō)明頁(yè)框被分配給了一個(gè)或多個(gè)進(jìn)程,或用于存放內(nèi)核數(shù)據(jù)。page_count()返回_count加1的值,也就是該頁(yè)的使用者數(shù)目 */
 ? ? ? ? ? ? ? ?atomic_t _count; ? ? ? ?/* Usage count, see below. */
 ? ? ? ? ? ?};
 ? ? ? ? ? ?/* 用于SLAB時(shí)描述當(dāng)前SLAB已經(jīng)使用的對(duì)象 */
 ? ? ? ? ? ?unsigned int active; ? ?/* SLAB */
 ? ? ? ?};
 ? ?};


 ? ?/* Third double word block */
 ? ?union {
 ? ? ? ?/* 包含到頁(yè)的最近最少使用(LRU)雙向鏈表的指針,用于插入伙伴系統(tǒng)的空閑鏈表中,只有塊中頭頁(yè)框要被插入。也用于SLAB,加入到kmem_cache中的SLAB鏈表中 */
 ? ? ? ?struct list_head lru; ? ?


 ? ? ? ?/* SLAB使用 */
 ? ? ? ?struct { ? ? ? ?/* slub per cpu partial pages */
 ? ? ? ? ? ?struct page *next; ? ?/* Next partial slab */
#ifdef CONFIG_64BIT
 ? ? ? ? ? ?int pages; ? ?/* Nr of partial slabs left */
 ? ? ? ? ? ?int pobjects; ? ?/* Approximate # of objects */
#else
 ? ? ? ? ? ?short int pages;
 ? ? ? ? ? ?short int pobjects;
#endif
 ? ? ? ?};

 ? ? ? ?/* SLAB使用 */
 ? ? ? ?struct slab *slab_page; /* slab fields */
 ? ? ? ?struct rcu_head rcu_head; ? ?/* Used by SLAB
 ? ? ? ? ? ? ? ? ? ? ? ? * when destroying via RCU
 ? ? ? ? ? ? ? ? ? ? ? ? */
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && USE_SPLIT_PMD_PTLOCKS
 ? ? ? ?pgtable_t pmd_huge_pte; /* protected by page->ptl */
#endif
 ? ?};


 ? ?/* Remainder is not double word aligned */
 ? ?union {
 ? ? ? ?/* 可用于正在使用頁(yè)的內(nèi)核成分(例如: 在緩沖頁(yè)的情況下它是一個(gè)緩沖器頭指針,如果頁(yè)是空閑的,則該字段由伙伴系統(tǒng)使用,在給伙伴系統(tǒng)使用時(shí),表明的是塊的2的次方數(shù),只有塊的第一個(gè)頁(yè)框會(huì)使用) */
 ? ? ? ?unsigned long private; ? ? ? ?
#if USE_SPLIT_PTE_PTLOCKS
#if ALLOC_SPLIT_PTLOCKS
 ? ? ? ?spinlock_t *ptl;
#else
 ? ? ? ?spinlock_t ptl;
#endif
#endif
 ? ? ? ?/* SLAB描述符使用,指向SLAB的高速緩存 */
 ? ? ? ?struct kmem_cache *slab_cache; ? ?/* SL[AU]B: Pointer to slab */
 ? ? ? ?struct page *first_page; ? ?/* Compound tail pages */
 ? ?};

#if defined(WANT_PAGE_VIRTUAL)
 ? ?/* 線性地址,如果是沒(méi)有映射的高端內(nèi)存的頁(yè)框,則為空 */
 ? ?void *virtual; ? ? ? ? ? ?/* Kernel virtual address (NULL if
 ? ? ? ? ? ? ? ? ? ? ? not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
#ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS
 ? ?unsigned long debug_flags; ? ?/* Use atomic bitops on this */
#endif

#ifdef CONFIG_KMEMCHECK
 ? ?void *shadow;
#endif

#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
 ? ?int _last_cpupid;
#endif
}
  • 在SLAB描述符中,最重要的可能就是s_mem和freelist這兩個(gè)指針。s_mem用于指向這段連續(xù)頁(yè)框中第一個(gè)對(duì)象,freelist指向空閑對(duì)象鏈表。

  • 空閑對(duì)象鏈表是一個(gè)由數(shù)組制成的簡(jiǎn)單鏈表,它保存的地方有兩種情況:

  1. 保存在外部,會(huì)從SLAB中分配一個(gè)對(duì)象用于保存新的SLAB的空閑對(duì)象鏈表。

  2. 保存在內(nèi)部,保存在這個(gè)SLAB所代表的連續(xù)頁(yè)框的頭部。

  • 不過(guò)一般沒(méi)有什么其他情況空閑對(duì)象鏈表都是保存在內(nèi)部居多,這里我們只討論將空閑對(duì)象鏈表保存在內(nèi)部的情況,這種情況下,這個(gè)SLAB所代表的連續(xù)頁(yè)框的頭部首先放的就是空閑對(duì)象鏈表,后面接著放的是對(duì)象描述符數(shù)組(1,2個(gè)字節(jié)大小),之后緊接著就是對(duì)象所代表的內(nèi)存了,如下圖:



  • 我們看看freelist數(shù)組是怎么形成一個(gè)鏈表的,之前我們也說(shuō)了分配時(shí)會(huì)優(yōu)先分配最近釋放的對(duì)象,整個(gè)freelist跟struct page中的active有很大聯(lián)系,可以說(shuō)active決定了下個(gè)分配的對(duì)象是誰(shuí),在freelist數(shù)組制作成的鏈表中,active作為下標(biāo),保存目標(biāo)空閑對(duì)象的對(duì)象號(hào),在活動(dòng)過(guò)程中,動(dòng)態(tài)修改這個(gè)數(shù)組中的值。我們用一幅圖可以很清楚看出freelist是如何實(shí)現(xiàn):



  • SLAB中的連續(xù)頁(yè)框個(gè)數(shù)與kmem_cache結(jié)構(gòu)中的gfporder有關(guān),而這個(gè)gfporder在初始化時(shí)通過(guò)對(duì)象數(shù)量、大小、freelist大小、對(duì)象描述符數(shù)組大小和著色區(qū)計(jì)算出來(lái)的。而對(duì)于對(duì)象的大小,也并不是你創(chuàng)建時(shí)打算使用的大小,比如,我打算創(chuàng)建一個(gè)kmem_cache的對(duì)象大小是10字節(jié),而在創(chuàng)建過(guò)程中,系統(tǒng)會(huì)幫你優(yōu)化和初始化這些對(duì)象,包括將你的對(duì)象保存地址放在內(nèi)存對(duì)其標(biāo)志,在對(duì)象的兩邊放入一些填充區(qū)域(RED_ZONE)進(jìn)行防止越界等工作。

關(guān)于SLAB著色

  • 看名字很難理解,其實(shí)又很好理解,我們知道內(nèi)存需要處理時(shí)要先放入CPU硬件高速緩存中,而CPU硬件高速緩存與內(nèi)存的映射方式有多種。在同一個(gè)kmem_cache中所有SLAB都是相同大小,都是相同連續(xù)長(zhǎng)度的頁(yè)框組成,這樣的話在不同SLAB中相同對(duì)象號(hào)對(duì)于頁(yè)框的首地址的偏移量也相同,這樣有很可能導(dǎo)致不同SLAB中相同對(duì)象號(hào)的對(duì)象放入CPU硬件高速緩存時(shí)會(huì)處于同一行,當(dāng)我們交替操作這兩個(gè)對(duì)象時(shí),CPU的cache就會(huì)交替換入換出,效率就非常差。SLAB著色就是在同一個(gè)kmem_cache中對(duì)不同的SLAB添加一個(gè)偏移量,就讓相同對(duì)象號(hào)的對(duì)象不會(huì)對(duì)齊,也就不會(huì)放入硬件高速緩存的同一行中,提高了效率,如下圖:



  • 著色空間就是前端的空閑區(qū)域,這個(gè)區(qū)有大小都是在分配新的SLAB時(shí)計(jì)算好的,計(jì)算方法很簡(jiǎn)單,node結(jié)點(diǎn)對(duì)應(yīng)的kmem_cache_node中的colour_next乘上kmem_cache中的colour_off就得到了偏移量,然后colour_next++,當(dāng)colour_next等于kmem_cache中的colour時(shí),colour_next回歸到0。

偏移量 = kmem_cache.colour_off * kmem_cache.node[NODE_ID].colour_next;

 ? ?kmem_cache.node[NODE_ID].colour_next++;
 ? ?if (kmem_cache.node[NODE_ID].colour_next == kmem_cache.colour)
 ? ? ? ?kmem_cache.node[NODE_ID].colour_next = 0;

本地CPU空閑對(duì)象鏈表

  • 現(xiàn)在說(shuō)說(shuō)本地CPU空閑對(duì)象鏈表。這個(gè)在kmem_cache結(jié)構(gòu)中用cpu_cache表示,整個(gè)數(shù)據(jù)結(jié)構(gòu)是struct array_cache,它的目的是將釋放的對(duì)象加入到這個(gè)鏈表中,我們可以先看看數(shù)據(jù)結(jié)構(gòu):

struct array_cache {
 ? ?/* 可用對(duì)象數(shù)目 */
 ? ?unsigned int avail;
 ? ?/* 可擁有的最大對(duì)象數(shù)目,和kmem_cache中一樣 */
 ? ?unsigned int limit;
 ? ?/* 同kmem_cache,要轉(zhuǎn)移進(jìn)本地高速緩存或從本地高速緩存中轉(zhuǎn)移出去的對(duì)象的數(shù)量 */
 ? ?unsigned int batchcount;
 ? ?/* 是否在收縮后被訪問(wèn)過(guò) */
 ? ?unsigned int touched;
 ? ?/* 偽數(shù)組,初始沒(méi)有任何數(shù)據(jù)項(xiàng),之后會(huì)增加并保存釋放的對(duì)象指針 */
 ? ?void *entry[]; ? ?/*
};
  • 這個(gè)本地CPU空閑對(duì)象鏈表的存在與伙伴系統(tǒng)中的每CPU頁(yè)框分配器的存在原因一樣,都有兩點(diǎn):

  1. 每個(gè)CPU都有它們自己的硬件高速緩存,當(dāng)此CPU上釋放對(duì)象時(shí),可能這個(gè)對(duì)象很可能還在這個(gè)CPU的硬件高速緩存中,所以內(nèi)核為每個(gè)CPU維護(hù)一個(gè)這樣的鏈表,當(dāng)需要新的對(duì)象時(shí),會(huì)優(yōu)先嘗試從當(dāng)前CPU的本地CPU空閑對(duì)象鏈表獲取相應(yīng)大小的對(duì)象。

  2. 減少鎖的競(jìng)爭(zhēng),試想一下,假設(shè)多個(gè)CPU同時(shí)申請(qǐng)一個(gè)大小的slab,這時(shí)候如果沒(méi)有本地CPU空閑對(duì)象鏈表,就會(huì)導(dǎo)致分配流程是互斥的,需要上鎖,就導(dǎo)致分配效率低。

  • 這個(gè)本地CPU空閑對(duì)象鏈表在系統(tǒng)初始化完成后是一個(gè)空的鏈表,只有釋放對(duì)象時(shí)才會(huì)將對(duì)象加入這個(gè)鏈表。當(dāng)然,鏈表對(duì)象個(gè)數(shù)也是有所限制,其最大值就是limit,鏈表數(shù)超過(guò)這個(gè)值時(shí),會(huì)將batchcount個(gè)數(shù)的對(duì)象返回到所有CPU共享的空閑對(duì)象鏈表(也是這樣一個(gè)結(jié)構(gòu))中。

  • 注意在array_cache中有一個(gè)entry數(shù)組,里面保存的是指向空閑對(duì)象的首地址的指針,注意這個(gè)鏈表是在kmem_cache結(jié)構(gòu)中的,也就是kmalloc-8有它自己的本地CPU高速緩存鏈表,dquot也有它自己的本地CPU高速緩存鏈表,每種類型kmem_cache都有它自己的本地CPU空閑對(duì)象鏈表。

所有CPU共享的空閑對(duì)象鏈表

  • 原理和本地CPU空閑對(duì)象鏈表一樣,唯一的區(qū)別就是所有CPU都可以從這個(gè)鏈表中獲取對(duì)象,一個(gè)常規(guī)的對(duì)象申請(qǐng)流程是這樣的:系統(tǒng)首先會(huì)從本地CPU空閑對(duì)象鏈表中嘗試獲取一個(gè)對(duì)象用于分配;如果失敗,則嘗試來(lái)到所有CPU共享的空閑對(duì)象鏈表鏈表中嘗試獲??;如果還是失敗,就會(huì)從SLAB中分配一個(gè);這時(shí)如果還失敗,kmem_cache會(huì)嘗試從頁(yè)框分配器中獲取一組連續(xù)的頁(yè)框建立一個(gè)新的SLAB,然后從新的SLAB中獲取一個(gè)對(duì)象。對(duì)象釋放過(guò)程也類似,首先會(huì)先將對(duì)象釋放到本地CPU空閑對(duì)象鏈表中,如果本地CPU空閑對(duì)象鏈表中對(duì)象過(guò)多,kmem_cache會(huì)將本地CPU空閑對(duì)象鏈表中的batchcount個(gè)對(duì)象移動(dòng)到所有CPU共享的空閑對(duì)象鏈表鏈表中,如果所有CPU共享的空閑對(duì)象鏈表鏈表的對(duì)象也太多了,kmem_cache也會(huì)把所有CPU共享的空閑對(duì)象鏈表鏈表中batchcount個(gè)數(shù)的對(duì)象移回它們自己所屬的SLAB中,這時(shí)如果SLAB中空閑對(duì)象太多,kmem_cache會(huì)整理出一些空閑的SLAB,將這些SLAB所占用的頁(yè)框釋放回頁(yè)框分配器中。

  • 這個(gè)所有CPU共享的空閑對(duì)象鏈表也不是肯定會(huì)有的,kmem_cache中有個(gè)shared字段如果為1,則這個(gè)kmem_cache有這個(gè)高速緩存,如果為0則沒(méi)有。

總結(jié)

整個(gè)框架已經(jīng)說(shuō)明結(jié)束了,我們用一幅圖進(jìn)行整理:




一文給你解決linux內(nèi)存源碼分析- SLAB分配器概述(超詳細(xì))的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
临高县| 肥乡县| 夹江县| 新乡市| 琼海市| 彭山县| 宁明县| 北海市| 连州市| 夏邑县| 邢台县| 台山市| 江西省| 高平市| 临夏县| 隆安县| 嵊泗县| 琼中| 峨眉山市| 若羌县| 惠东县| 大田县| 佛学| 从化市| 长岭县| 定兴县| 西充县| 阜康市| 吉木乃县| 鄄城县| 冕宁县| 永胜县| 安图县| 苍溪县| 互助| 云霄县| 习水县| 镶黄旗| 济阳县| 仁寿县| 菏泽市|