本文在行文的過(guò)程中,會(huì)多次提到cache或緩存的概念。如果沒(méi)有特殊在前面添加硬件的限定詞,就說(shuō)明cache指的是slab分配器使用的軟件緩存的意思。如果添加了硬件限定詞,則指的是處理器的硬件緩存,比如L1-DCache、L1-ICache之類的。
本節(jié)我們討論memory area,一段具有連續(xù)物理地址和任意長(zhǎng)度的內(nèi)存。
buddy算法將頁(yè)幀作為最基本的memory area。這對(duì)于申請(qǐng)較大內(nèi)存的請(qǐng)求是非常好的,但是,如果是很小的memory area請(qǐng)求,比如幾十或幾百字節(jié),我們將如何處理呢?
很明顯,申請(qǐng)一個(gè)完整頁(yè)幀存儲(chǔ)幾十個(gè)字節(jié)是非常浪費(fèi)資源的。如果引入新數(shù)據(jù)結(jié)構(gòu)描述在同一個(gè)頁(yè)幀內(nèi)如何分配memory area,會(huì)引入一個(gè)新問(wèn)題:內(nèi)部碎片。這是由于請(qǐng)求的內(nèi)存大小和分配的memory area大小不匹配造成。
早期Linux內(nèi)核就是采用這種經(jīng)典方案是,提供大小成幾何分布的memory area;換句話說(shuō),大小是2的冪次方,而不是要存儲(chǔ)數(shù)據(jù)的實(shí)際大小。這樣的好處是,無(wú)論請(qǐng)求的內(nèi)存是多少,我們都能保證內(nèi)存碎片小于50%?;谶@種方法,內(nèi)核創(chuàng)建了13個(gè)memory area列表,列表元素的大小從32 → 131072字節(jié)。這些列表還是用buddy系統(tǒng)申請(qǐng)內(nèi)存頁(yè)幀,或釋放不再包含memory area的頁(yè)幀。使用一個(gè)動(dòng)態(tài)列表追蹤每個(gè)頁(yè)幀內(nèi)的自由memory area數(shù)量。
1 slab分配器
在buddy系統(tǒng)之上運(yùn)行前述的memory area分配算法不是特別有效。在Sun Microsystems Solaris 2.4操作系統(tǒng)中首次引入的slab分配器方案給出了一種更好地算法:
存儲(chǔ)的數(shù)據(jù)類型影響memory area的分配方式(類似C++語(yǔ)言中的類的概念,也就是面向?qū)ο蟮?a target="_blank">編程思想)。例如,給用戶態(tài)進(jìn)程申請(qǐng)分配一個(gè)頁(yè)幀,內(nèi)核會(huì)調(diào)用get_zeroed_page()函數(shù),將該頁(yè)填充為0。
slab分配器擴(kuò)展了該思想,將memory area視為對(duì)象,該對(duì)象由一組數(shù)據(jù)結(jié)構(gòu)和一對(duì)函數(shù)組成,這對(duì)函數(shù)又稱為構(gòu)造函數(shù)和析構(gòu)函數(shù)。前者初始化memory area,而后者負(fù)責(zé)解除初始化。
為了避免重復(fù)初始化對(duì)象,slab分配器不會(huì)丟棄已經(jīng)申請(qǐng)但要釋放的對(duì)象,而是將其保存在內(nèi)存中。當(dāng)申請(qǐng)新對(duì)象時(shí),直接從內(nèi)存中獲取,而無(wú)需重新初始化。
內(nèi)核往往重復(fù)地申請(qǐng)相同類型的memory area(建立cache,重復(fù)利用)。比如,當(dāng)內(nèi)核創(chuàng)建一個(gè)新進(jìn)程時(shí),它會(huì)分配一些固定大小的memory area,每組固定大小的memory area組成一張表,用來(lái)保存進(jìn)程描述符、打開(kāi)的文件對(duì)象等等。當(dāng)進(jìn)程結(jié)束時(shí),這些memory area以及管理它們的表能夠被重復(fù)利用。因?yàn)檫M(jìn)程的創(chuàng)建和銷毀是很頻繁的,如果沒(méi)有slab分配器,內(nèi)核就會(huì)浪費(fèi)時(shí)間重復(fù)地分配和釋放包含相同大小memory area的頁(yè)幀;而slab分配器,將它們保存在緩存中,可以快速的重復(fù)利用。
memory area的申請(qǐng)可以按照它們的使用頻率來(lái)分類。通過(guò)創(chuàng)建一組具有合適大小的專用對(duì)象,可以有效處理預(yù)期特定大小的內(nèi)存申請(qǐng),從而避免內(nèi)部碎片。同時(shí),對(duì)于很少遇見(jiàn)的大小,可以按照幾何分布大?。ɡ?,2的冪次方)的對(duì)象進(jìn)行處理,盡管這種方法仍然會(huì)導(dǎo)致內(nèi)部碎片的產(chǎn)生。
引入大小不是幾何分布的對(duì)象還有一個(gè)微妙的好處:內(nèi)核數(shù)據(jù)結(jié)構(gòu)的首地址往往不是以2的冪次方大小分布的物理地址上。(通俗地講,就是數(shù)據(jù)結(jié)構(gòu)的首地址不太可能正好落在2的冪次方大小的地址上)。因此,輔以硬件cache,可以產(chǎn)生更好的性能。
硬件cache性能是限制盡可能少地調(diào)用伙伴關(guān)系分配器的另一個(gè)原因(頻繁調(diào)用buddy系統(tǒng),會(huì)降低系統(tǒng)性能)。每次調(diào)用伙伴關(guān)系系統(tǒng),都會(huì)弄臟硬件cache,也就會(huì)增加平均內(nèi)存訪問(wèn)時(shí)間。內(nèi)核函數(shù)對(duì)硬件cache的影響被稱為函數(shù)占用空間;它被定義為當(dāng)該函數(shù)終止后,覆蓋的cache百分比。很明顯,越大的占用空間比會(huì)導(dǎo)致該函數(shù)之后的代碼執(zhí)行越慢,因?yàn)榇藭r(shí)的硬件cache需要重新讀取內(nèi)存,填充自己。
slab分配器將對(duì)象分組保存到cache中,每個(gè)cache保存了相同類型的對(duì)象。比如,打開(kāi)一個(gè)文件,為了保存打開(kāi)的文件這個(gè)對(duì)象,就會(huì)從slab分配器的filp緩存對(duì)象中(文件指針的意思),申請(qǐng)memory area。
包含一個(gè)cache的memory area被分成很多個(gè)slab;每個(gè)slab包含一個(gè)或多個(gè)連續(xù)的頁(yè)幀,這些頁(yè)幀用來(lái)保存已經(jīng)分配的對(duì)象和自由空閑的對(duì)象。如下圖所示:
內(nèi)核會(huì)周期性地掃描這些cache,釋放掉那些空slab占用的頁(yè)幀。
2 cache描述符
描述cache的數(shù)據(jù)類型是kmem_cache_t(等價(jià)于struct kmem_cache_s),各字段如下表所示。其中,忽略了收集統(tǒng)計(jì)信息和調(diào)試的幾個(gè)字段。
表8-8kmem_cache_t的各個(gè)字段
類型 | 名稱 | 描述 |
---|---|---|
struct array_cache*[] | array | Per-CPU數(shù)組,包含指向本地的那些cache |
unsigned int | batchcount | 與本地cache傳送的對(duì)象數(shù)量 |
unsigned int | limit | 本地cache中空閑對(duì)象的最大數(shù)量。這是可調(diào)的 |
struct kmem_list3 | lists | 見(jiàn)下表 |
unsigned int | objsize | cache中對(duì)象的大小 |
unsigned int | flags | 描述cache永久屬性的一些標(biāo)志集 |
unsigned int | num | 單個(gè)slab中的對(duì)象數(shù)量(同一cache中slab大小相同) |
unsigned int | free_limit | 整個(gè)slab cache中自由空閑對(duì)象的上限 |
spinlock_t | spinlock | 保護(hù)cache的自旋鎖 |
unsigned int | gfporder | 單個(gè)slab中連續(xù)頁(yè)幀數(shù)量的對(duì)數(shù) |
unsigned int | gfpflags | 申請(qǐng)分配頁(yè)幀時(shí)傳遞給伙伴關(guān)系函數(shù)的標(biāo)志組合 |
size_t | colour | slab顏色數(shù)量 |
unsigned int | colour_off | slab中基本對(duì)齊偏移量 |
unsigned int | colour_next | 用于下一個(gè)slab的顏色 |
kmem_cache_t * | slabp_cache |
指向通用slab cache的指針,該cache包含slab描述符 (如果其內(nèi)部的slab描述符已經(jīng)被使用,則為NULL) |
unsigned int | slab_size | 單個(gè)slab的大小 |
unsigned int | dflags | 描述cache的動(dòng)態(tài)屬性的標(biāo)志集 |
void * | ctor | 指向與cache相關(guān)聯(lián)的構(gòu)造函數(shù)方法的指針 |
void * | dtor | 指向與cache相關(guān)聯(lián)的析構(gòu)函數(shù)方法的指針 |
const char * | name | 保存cache名稱的字符數(shù)組 |
struct list_head | next | cache描述符的雙向鏈表的指針 |
lists字段參見(jiàn)下表。
表8-9kmem_list3結(jié)構(gòu)體的各個(gè)字段
類型 | 名稱 | 描述 |
---|---|---|
struct list_head | slabs_partial | slab描述符(包含自由和非自由對(duì)象)的雙向循環(huán)鏈表 |
struct list_head | slabs_full | slab描述符(包含非自由對(duì)象)的雙向循環(huán)鏈表 |
struct list_head | slabs_free | slab描述符(包含自由對(duì)象)的雙向循環(huán)鏈表 |
unsigned long | free_objects | cache中自由對(duì)象的數(shù)量 |
int | free_touched | slab分配器的頁(yè)回收算法使用 |
unsigned long | next_reap | slab分配器的頁(yè)回收算法使用 |
struct array_cache * | shared | 指向所有CPU共享的本地cache |
3 slab描述符
cache中的每一個(gè)slab都有自己的描述符,其各字段描述,如下表所示:
類型 | 名稱 | 描述 |
---|---|---|
struct list_head | list |
指向三個(gè)slab描述符的雙向鏈表之一。 也就是cache描述符的kmem_list3中的列表 (slabs_full、slabs_partial、slabs_free) |
unsigned long | colouroff | 該slab中第一個(gè)對(duì)象的偏移量(跟染色有關(guān)) |
void * | s_mem | 該slab中第一個(gè)對(duì)象的地址 |
unsigned int | inuse | 該slab中當(dāng)前使用的對(duì)象數(shù)量(非自由) |
unsigned int | free |
該slab中下一個(gè)自由對(duì)象的索引, 如果沒(méi)有自由對(duì)象則等于BUFCTL_END |
slab描述符有兩個(gè)存儲(chǔ)的地方:
外部slab描述符
存儲(chǔ)在該slab之外,通用緩存之一中(由cache_sizes指向)。
內(nèi)部slab描述符
存儲(chǔ)在該slab之內(nèi)內(nèi),也就是分配給slab的第一個(gè)頁(yè)幀的開(kāi)頭處。
當(dāng)對(duì)象的大小小于512MB時(shí),或者當(dāng)內(nèi)部碎片在slab內(nèi)為slab描述符和對(duì)象描述符留出足夠的空間時(shí),slab分配器選擇第二種解決方案。如果slab描述符存儲(chǔ)在slab之外,則cache描述符的flags字段中的CFLGS_OFF_SLAB標(biāo)志被設(shè)置為1;否則它將被設(shè)置為0。
下圖展示了cache和slab描述符的主要關(guān)系。已經(jīng)使用的slab,部分使用的slab和未使用的slab,它們使用不同鏈表串聯(lián)起來(lái)。
4 通用和特殊cache
cache可以分為兩類:通用和特殊。通用cache僅由slab分配器使用,而特殊cache由內(nèi)核其它部分使用。
通用cache包含:
第一個(gè)cache稱為kmem_cache,它的對(duì)象都是內(nèi)核中其余cache的描述符。cache_cache變量保存著這個(gè)特殊cache的描述符。
一些包含通用memory area的cache。這些內(nèi)存區(qū)域范圍是13個(gè)呈幾何分布的內(nèi)存大小。內(nèi)核中有一個(gè)表malloc_sizes(一個(gè)數(shù)組,數(shù)據(jù)類型是cache_sizes),它指向26個(gè)通用cache描述符,這些cache的大小是32、64、128、256、512、1024、2048、4096、8192、16384、32768、65536、131072字節(jié)。對(duì)于每種大小,都有兩種cache:一種適用于ISA DMA分配,另一種適用于普通內(nèi)存分配。
系統(tǒng)初始化的時(shí)候,調(diào)用函數(shù)kmem_cache_init()建立通用cache。
特殊cache都是調(diào)用kmem_cache_create()函數(shù)創(chuàng)建的。依據(jù)傳參,該函數(shù)首先檢查處理新cache的最佳方式(例如,slab描述符位于slab內(nèi)部還是外部)。然后從cache_cache通用緩存中分配新的cache描述符,并將其插入到一個(gè)cache描述符的鏈表cache_chain中(插入操作使用cache_chain_sem信號(hào)量進(jìn)行保護(hù),避免競(jìng)態(tài)條件發(fā)生)。
從cache_chain鏈表中銷毀和移除一個(gè)cache,可以調(diào)用kmem_cache_destroy()。此函數(shù)對(duì)于那些在加載時(shí)創(chuàng)建cache,卸載時(shí)銷毀cache的模塊非常有用。為了避免浪費(fèi)內(nèi)存空間,內(nèi)核必須在銷毀cache本身之前,需要銷毀所有的slab。而kmem_cache_shrink()函數(shù)正好可以通過(guò)調(diào)用slab_destroy()迭代銷毀所有slab。
不管是通用還是特殊cache,都可以讀取/proc/slabinfo獲取其名稱;該文件還指定了每個(gè)cache中自由對(duì)象、已分配對(duì)象的數(shù)量。
# name: tunables # : slabdata isofs_inode_cache 72 72 656 24 4 : tunables 0 0 0 : slabdata 3 3 0 nf_conntrack 175 175 320 25 2 : tunables 0 0 0 : slabdata 7 7 0 au_finfo 0 0 192 21 1 : tunables 0 0 0 : slabdata 0 0 0 au_icntnr 0 0 832 39 8 : tunables 0 0 0 : slabdata 0 0 0 au_dinfo 0 0 192 21 1 : tunables 0 0 0 : slabdata 0 0 0 ovl_inode 69 69 688 23 4 : tunables 0 0 0 : slabdata 3 3 0 kvm_async_pf 0 0 136 30 1 : tunables 0 0 0 : slabdata 0 0 0 kvm_vcpu 0 0 17152 1 8 : tunables 0 0 0 : slabdata 0 0 0 ...省略(內(nèi)核數(shù)據(jù)對(duì)象使用) pool_workqueue 1393 1568 256 32 2 : tunables 0 0 0 : slabdata 49 49 0 radix_tree_node 13253 14896 584 28 4 : tunables 0 0 0 : slabdata 532 532 0 task_group 275 275 640 25 4 : tunables 0 0 0 : slabdata 11 11 0 vmap_area 3584 3584 64 64 1 : tunables 0 0 0 : slabdata 56 56 0 dma-kmalloc-8k 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0 ...省略 dma-kmalloc-8 0 0 8 512 1 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-192 0 0 192 21 1 : tunables 0 0 0 : slabdata 0 0 0 dma-kmalloc-96 0 0 96 42 1 : tunables 0 0 0 : slabdata 0 0 0 ...省略 kmalloc-rcl-16 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-rcl-8 0 0 8 512 1 : tunables 0 0 0 : slabdata 0 0 0 kmalloc-8k 168 168 8192 4 8 : tunables 0 0 0 : slabdata 42 42 0 kmalloc-4k 3832 3856 4096 8 8 : tunables 0 0 0 : slabdata 482 482 0 ...省略 kmalloc-16 9472 9472 16 256 1 : tunables 0 0 0 : slabdata 37 37 0 kmalloc-8 12288 12288 8 512 1 : tunables 0 0 0 : slabdata 24 24 0 kmem_cache_node 2432 2432 64 64 1 : tunables 0 0 0 : slabdata 38 38 0 kmem_cache 2239 2340 448 36 4 : tunables 0 0 0 : slabdata 65 65 0
5 slab分配器和zone分配器的關(guān)系
前面我們知道,cache的頁(yè)幀分配是在初始化時(shí)就完成的。而slab分配器創(chuàng)建新slab時(shí),則需要zone分配器獲取一組空閑且連續(xù)的物理內(nèi)存(不會(huì)分配高端內(nèi)存)。因此,需要調(diào)用kmem_getpages()函數(shù),在UMA系統(tǒng)上,實(shí)現(xiàn)大概如下所示:
void * kmem_getpages(kmem_cache_t *cachep, int flags) { struct page *page; int i; flags |= cachep->gfpflags; page = alloc_pages(flags, cachep->gfporder); if (!page) return NULL; i = (1 << cache->gfporder); if (cachep->flags & SLAB_RECLAIM_ACCOUNT) atomic_add(i, &slab_reclaim_pages); while (i--) SetPageSlab(page++); return page_address(page); }
參數(shù)說(shuō)明:
cachep
指向需要額外頁(yè)幀的cache描述符的指針(所需頁(yè)幀數(shù)量由cachep->gfporder字段中的階數(shù)決定)。
flags
指定如何請(qǐng)求頁(yè)幀(參見(jiàn)Zone分配器)。這個(gè)標(biāo)志與cache描述符中保存的標(biāo)志組合使用。
內(nèi)存分配請(qǐng)求的大小由緩存描述符的gfporder字段指定,該字段決定了緩存中slab的大小。如果slab cache設(shè)置了SLAB_RECLAIM_ACCOUNT標(biāo)志,當(dāng)內(nèi)核檢查是否有足夠的內(nèi)存來(lái)滿足一些用戶請(qǐng)求時(shí),分配給slab的頁(yè)幀將被視為可回收頁(yè)。該函數(shù)還在分配的頁(yè)幀的頁(yè)描述符中設(shè)置PG_slab標(biāo)志。
釋放slab頁(yè)幀時(shí),調(diào)用kmem_freepages()函數(shù):
void kmem_freepages(kmem_cache_t *cachep, void *addr) { unsigned long i = (1 << cachep->gfporder); struct page *page = virt_to_page(addr); if (current->reclaim_state) current->reclaim_state->reclaimed_slab += i; while (i--) ClearPageSlab(page++); free_pages((unsigned long) addr, cachep->gfporder); if (cachep->flags & SLAB_RECLAIM_ACCOUNT) atomic_sub(1<gfporder, &slab_reclaim_pages); }
該函數(shù)釋放slab頁(yè)幀,從線性地址為addr的頁(yè)幀開(kāi)始。如果當(dāng)前進(jìn)程正在執(zhí)行內(nèi)存回收(current->reclaim_state字段不是NULL),reclaim_state->reclaimed_slab加上要釋放的頁(yè)幀數(shù),由頁(yè)幀回收算法計(jì)算在內(nèi)。此外,如果設(shè)置了SLAB_RECLAIM_ACCOUNT標(biāo)志(見(jiàn)上文),適當(dāng)?shù)臏p小slab_reclaim_pages,該變量用來(lái)記錄在內(nèi)存不足時(shí),分配給slab的頁(yè)幀有多少是空閑的。
6 分配slab給cache
剛創(chuàng)建的cache不包含slab,因此也就沒(méi)有任何空閑的對(duì)象。只有在滿足下面兩個(gè)條件時(shí)才會(huì)將slab分配給cache:
有新對(duì)象的分配請(qǐng)求時(shí)
cache沒(méi)有空閑對(duì)象時(shí)
slab分配器調(diào)用cache_grow()分配新的slab。具體的過(guò)程如下所示:
首先,調(diào)用kmem_getpages()從ZONE頁(yè)幀分配器獲取存儲(chǔ)slab所需的頁(yè)幀;
然后,調(diào)用alloc_slabmgmt()來(lái)獲取新的slab描述符。如果設(shè)置了cache描述符的CFLGS_OFF_SLAB標(biāo)志,則從cache描述符的slabp_cache指向的通用緩存中分配slab描述符;否則,將在slab的第1個(gè)頁(yè)幀中分配slab描述符。
對(duì)于給定的頁(yè)幀,內(nèi)核必須能夠確定它是否被slab分配器使用,如果是,則必須能夠快速導(dǎo)出相應(yīng)cache和slab描述符的地址。因此,cache_grow()掃描分配給新slab的頁(yè)幀的所有頁(yè)描述符,并分別使用cache和slab描述符的地址加載page描述符中l(wèi)ru字段的next和prev字段。這是正確的,因?yàn)閘ru字段僅在頁(yè)幀空閑時(shí)由buddy伙伴系統(tǒng)使用,而由slab分配器處理的頁(yè)幀設(shè)置了PG_slab標(biāo)志,對(duì)buddy伙伴系統(tǒng)而言不是空閑的。相反的問(wèn)題:對(duì)于給定的slab,哪些是實(shí)現(xiàn)它的頁(yè)幀?可以通過(guò)使用slab描述符的s_mem字段(slab第一個(gè)頁(yè)幀的起始地址)和cache描述符的gfporder字段(slab大?。﹣?lái)確定。
接下來(lái),cache_init_objs(),對(duì)slab所有對(duì)象調(diào)用構(gòu)造函數(shù);(也就是初始化所有對(duì)象)
最后,調(diào)用list_add_tail()將獲取的slab描述符(*slabp)添加到cache描述符(*cachep)中的空閑slab列表中,并更新空閑對(duì)象的計(jì)數(shù):
list_add_tail(&slabp->list, &cachep->lists->slabs_free); cachep->lists->free_objects += cachep->num;
7 從cache中釋放slab
銷毀slab分為兩種情況:
slab cache中有太多空閑的對(duì)象;
定時(shí)器函數(shù)周期性地檢查是否有完全未使用的slab需要釋放
銷毀過(guò)程的實(shí)現(xiàn)函數(shù)是slab_destroy(),銷毀slab并將對(duì)應(yīng)的頁(yè)幀釋放回ZONE頁(yè)幀分配器:
void slab_destroy(kmem_cache_t *cachep, slab_t *slabp) { /* 檢查cache是否具有析構(gòu)函數(shù):如果有對(duì)所有對(duì)象調(diào)用析構(gòu)函數(shù) */ if (cachep->dtor) { int i; for (i = 0; i < cachep->num; i++) { // objp指向當(dāng)前正在處理的對(duì)象 void* objp = slabp->s_mem+cachep->objsize*i; (cachep->dtor)(objp, cachep, 0); } } /* 將slab使用的所有頁(yè)幀返回給buddy系統(tǒng) */ kmem_freepages(cachep, slabp->s_mem - slabp->colouroff); /* 如果slab描述符存儲(chǔ)在slab之外,需要從slab描述符的緩存中釋放 */ if (cachep->flags & CFLGS_OFF_SLAB) kmem_cache_free(cachep->slabp_cache, slabp); /* 如果slab cache被設(shè)置了`SLAB_DESTROY_BY_RCU`標(biāo)志, * 意味著使用延遲執(zhí)行的方法釋放`slab`,使用call_rcu()注冊(cè)回調(diào)函數(shù) * 由回調(diào)函數(shù)調(diào)用kmem_freepages()。如果可能,還需要調(diào)用kmem_cache_free */ if (unlikely(cachep->flags & SLAB_DESTROY_BY_RCU)) { struct slab_rcu *slab_rcu; slab_rcu = (struct slab_rcu *) slabp; slab_rcu->cachep = cachep; slab_rcu->addr = addr; call_rcu(&slab_rcu->head, kmem_rcu_free); } else { kmem_freepages(cachep, addr); if (OFF_SLAB(cachep)) kmem_cache_free(cachep->slabp_cache, slabp); } }
8 對(duì)象描述符
每個(gè)對(duì)象也有描述符,類型是kmem_bufctl_t,這是一個(gè)unsigned short類型。對(duì)象描述符數(shù)組就存放在slab描述符的后邊。所以,對(duì)象描述符的存儲(chǔ)位置也分為兩種情況,如下圖所示:
slab外部
存儲(chǔ)在由slabp_cache指向的通用cache中。對(duì)象描述符和對(duì)象所占用的內(nèi)存大小,有存儲(chǔ)在slab中的對(duì)象數(shù)量決定(cache描述符中的num字段)。
slab內(nèi)部
存儲(chǔ)在slab之內(nèi),就在slab描述符后邊。
數(shù)組中的第一個(gè)對(duì)象描述符描述第一個(gè)對(duì)象,依次對(duì)應(yīng)。對(duì)象描述符是一個(gè)unsigned short整數(shù),只有當(dāng)對(duì)象是空閑時(shí)才有意義。它包含指向下一個(gè)空閑對(duì)象的索引,因此形成了一個(gè)空閑對(duì)象的列表。該列表中,最后一個(gè)空閑對(duì)象的索引標(biāo)記為BUFCTL_END(0xffff)。
9 對(duì)象的內(nèi)存對(duì)齊
slab分配器管理的對(duì)象在內(nèi)存中需要對(duì)齊,也就是說(shuō),存儲(chǔ)它們的初始物理地址是給定常數(shù)倍數(shù)的內(nèi)存單元中,通常是2的冪。這個(gè)常數(shù)稱為對(duì)齊因子。
slab分配器允許的最大對(duì)齊因子是4096(頁(yè)幀大?。?。這意味著對(duì)象可以通過(guò)引用它們的物理地址或線性地址來(lái)對(duì)齊。在這兩種情況下,只有地址的低12位可能會(huì)被對(duì)齊改變。
通常,如果物理地址按照word(即計(jì)算機(jī)內(nèi)部存儲(chǔ)器總線的寬度)對(duì)齊,計(jì)算機(jī)訪問(wèn)存儲(chǔ)單元的速度會(huì)更快。因此,默認(rèn)情況下,kmem_cache_create()函數(shù)根據(jù)BYTES_PER_WORD宏指定的字長(zhǎng)來(lái)對(duì)齊對(duì)象。
對(duì)于×86處理器,宏的值為4,字長(zhǎng)為32位。在創(chuàng)建新的slab cache時(shí),可以將對(duì)象在硬件L1-cache中對(duì)齊。為了實(shí)現(xiàn)這一點(diǎn),內(nèi)核設(shè)置了SLAB_HWCACHE_ALIGNcache描述符標(biāo)志。kmem_cache_create()會(huì)按照如下方式處理請(qǐng)求:
如果對(duì)象大于cache line的一半,那么它在內(nèi)存中的對(duì)齊大小就是L1_CACHE_BYTES;換句話說(shuō),總是位于cache line的起始處。
否則,對(duì)象大小按照obj_size * n = L1_CACHE_BYTES計(jì)算出的合理值進(jìn)行對(duì)齊,總之要保證一個(gè)對(duì)象不能跨越2個(gè)cache line。
很顯然,slab分配器在這兒就是采用以空間換取時(shí)間的思想;通過(guò)人為的增加對(duì)象的大小獲得更好地緩存性能,但也會(huì)產(chǎn)生內(nèi)部碎片。
10 slab染色
我們知道,同一條cache line可以映射許多不同的內(nèi)存塊。在本章中,我們還看到了相同大小的對(duì)象最終被存儲(chǔ)在硬件cache中相同的偏移位置。在不同slab中具有相同偏移量的對(duì)象將以相對(duì)較高的概率最終映射到相同的cache line中。因此,頻繁訪問(wèn)映射到同一cache line的不同內(nèi)存位置時(shí),需要來(lái)回在硬件cache和內(nèi)存之間搬運(yùn)數(shù)據(jù),造成訪存性能降低。slab分配器通過(guò)一種稱為slab染色的策略避免這種行為:將不同的值(稱為colors)賦給不同的slab。
在分析slab著色之前,我們必須先看一下cache中對(duì)象的布局。因?yàn)閏ache在內(nèi)存中是對(duì)齊的,這意味著對(duì)象地址必須是給定值(比如aln)的倍數(shù)。但即使考慮到對(duì)齊約束,也有許多可能的方法將對(duì)象存放到slab中。如何選擇取決于對(duì)以下變量所做的決定:
num
可以存儲(chǔ)到slab中的對(duì)象數(shù)量。
osize
對(duì)象大小,包含對(duì)齊字節(jié)。
dsize
描述符大小(包括slab和所有對(duì)象描述符的大?。?,按照cache line對(duì)齊。如果slab和對(duì)象描述符存儲(chǔ)在slab之外,它的值等于0。
free
slab中未使用的字節(jié)(那些沒(méi)有分配給任何對(duì)象的字節(jié))。
所以,slab總長(zhǎng)可以用下面的公式計(jì)算:
slab length = (num × osize) + dsize + free
free總是小于osize,否則就可以在slab中添加一個(gè)對(duì)象了。但是,free可能大于aln。
slab分配器利用free未使用字節(jié)為slab染色。術(shù)語(yǔ)color簡(jiǎn)單地對(duì)slab進(jìn)行劃分,從而允許內(nèi)存分配器將對(duì)象分散到不同的線性地址中。通過(guò)這種方式,內(nèi)核可以從處理器的硬件cache中獲得最佳性能。
將slab染色可以將slab的第一個(gè)對(duì)象存儲(chǔ)到不同的內(nèi)存位置,同時(shí)滿足對(duì)齊約束??捎玫腸olor數(shù)量是free?aln(該值存儲(chǔ)在cache描述符的colour字段中)。因此,第1個(gè)顏色值是0,最后一個(gè)為(free?aln)?1。(一種特殊情況是,free < aln,colour設(shè)為0,所有的slab使用顏色值0,顏色的數(shù)量是一個(gè)。)
如果slab被使用顏色值col染色,第一個(gè)對(duì)象的偏移量(相對(duì)于slab初始地址)等于col × aln + dsize個(gè)字節(jié)。如下圖所示,圖中闡釋了slab內(nèi)對(duì)象的位置如何依賴slab顏色值。本質(zhì)上,染色就是將slab中未使用的部分字節(jié)從結(jié)尾處移動(dòng)到起始處。
染色只有在free足夠大時(shí)才起作用。很明顯,如果對(duì)象沒(méi)有要求對(duì)齊,或者如果slab中未使用的字節(jié)數(shù)小于對(duì)齊因子(free < aln),唯一可能的slab染色就一個(gè),顏色值為0,即第一個(gè)對(duì)象的偏移量賦值為零。
通過(guò)將當(dāng)前顏色存儲(chǔ)在cache描述符中的color_next字段中,cache_grow()函數(shù)將color_next指定的顏色分配給新的slab,然后增加該字段的值。到達(dá)colour后,它再次繞到“0”。通過(guò)這種方式,每個(gè)slab都使用與前一個(gè)不同的顏色創(chuàng)建,直到最大可用顏色。此外,cache_grow()函數(shù)從cache描述符的color_off字段獲取值aln,根據(jù)slab內(nèi)部對(duì)象的數(shù)量計(jì)算dsize,最后將值col × aln + dsize存儲(chǔ)在slab描述符的coloroff字段中。
11 空閑slab對(duì)象的本地緩存
在多核處理器系統(tǒng)中,Linux v2.6版本實(shí)現(xiàn)的slab分配器,與最初的Solaris 2.4實(shí)現(xiàn)不同。為了減少處理器之間的自旋鎖競(jìng)爭(zhēng)并更好地利用硬件緩存,slab分配器的每個(gè)緩存都包含一個(gè)CPU核的本地?cái)?shù)據(jù)結(jié)構(gòu),該數(shù)據(jù)結(jié)構(gòu)由指向被釋放對(duì)象指針組成的數(shù)組,稱為“slab本地緩存”。大多數(shù)slab對(duì)象的分配和釋放只影響本地緩存;只有當(dāng)本地緩存下溢或溢出時(shí),才會(huì)涉及到slab數(shù)據(jù)結(jié)構(gòu)。這種技術(shù)與前面的“CPU本地頁(yè)幀緩存”一節(jié)中介紹的技術(shù)非常相似。
緩存描述符的array字段是指向該指針數(shù)組,而指針指向array_cache數(shù)據(jù)結(jié)構(gòu),系統(tǒng)中的每個(gè)CPU都有一個(gè)這樣的元素。每個(gè)array_cache數(shù)據(jù)結(jié)構(gòu)都是空閑對(duì)象的本地緩存的描述符,其字段如表8-11所示。
表8-11array_cache結(jié)構(gòu)的字段
類型 | 名稱 | 描述 |
---|---|---|
unsigned int | avail | 指向本地緩存中可用對(duì)象的指針數(shù)。該字段可以作為緩存中的第一個(gè)空閑slot。 |
unsigned int | limit | 本地緩存的大小。也就是說(shuō),本地緩存中指針的最大數(shù)量。 |
unsigned int | batchcount | 本地緩存重填或清空的塊大小。 |
unsigned int | touched | 如果本地緩存最近被使用,則標(biāo)志設(shè)置為1。 |
注意,本地緩存描述符不包括本地緩存本身的地址;實(shí)際上,本地緩存就放在描述符后面,代碼如下所示。當(dāng)然,本地緩存存儲(chǔ)的是指向被釋放對(duì)象的指針,而不是對(duì)象本身,對(duì)象總是放在緩存的slab中。
struct arraycache_init { struct array_cache cache; void * entries[BOOT_CPUCACHE_ENTRIES]; };
當(dāng)創(chuàng)建新的slab緩存時(shí),kmem_cache_create()函數(shù)確定本地緩存的大小(將此值存儲(chǔ)在緩存描述符的limit字段中),分配它們,并將它們的指針存儲(chǔ)在緩存描述符的array字段中。大小取決于存儲(chǔ)在slab緩存中的對(duì)象的大小,范圍從1表示非常大的對(duì)象到120表示較小的對(duì)象。此外,“batchcount”字段的初始值,即在塊中從本地緩存中添加或刪除的對(duì)象的數(shù)量,最初設(shè)置為本地緩存大小的一半。
系統(tǒng)管理員可以為每個(gè)cache調(diào)節(jié)本地緩存的大小,方法是寫batchcount字段值,該值保存在/proc/slabinfo文件中。
在多核系統(tǒng)中,存儲(chǔ)小內(nèi)存對(duì)象的slab緩存還支持一個(gè)額外的本地緩存,它的地址存儲(chǔ)在緩存描述符的lists.shared字段中。顧名思義,共享本地緩存是所有CPU共享的,它使空閑對(duì)象在本地緩存之間進(jìn)行遷移變得容易。初始值等于8倍于batchcount的值。
12 分配slab對(duì)象
新對(duì)象可以通過(guò)調(diào)用kmem_cache_alloc()函數(shù)獲得。參數(shù)cachep指向必須從中獲得新的空閑對(duì)象的緩存描述符,而參數(shù)flag表示要傳遞給ZONE頁(yè)幀分配器函數(shù)的標(biāo)志,如果緩存的所有slab都已滿時(shí),使用這些標(biāo)志創(chuàng)建新的slab。
該函數(shù)本質(zhì)上等價(jià)于下面的代碼:
void * kmem_cache_alloc(kmem_cache_t *cachep, int flags) { unsigned long save_flags; void *objp; struct array_cache *ac; local_irq_save(save_flags); ac = cachep->array[smp_processor_id()]; if (ac->avail) { ac->touched = 1; objp = ((void **)(ac+1))[--ac->avail]; } else objp = cache_alloc_refill(cachep, flags); local_irq_restore(save_flags); return objp; }
該函數(shù)首先嘗試從本地緩存中檢索一個(gè)空閑對(duì)象。如果有空閑對(duì)象,avail字段包含本地緩存中指向最后一個(gè)被釋放對(duì)象的索引。因?yàn)楸镜鼐彺鏀?shù)組存儲(chǔ)在ac描述符之后,((void**)(ac+1))[——ac->avail]獲取該空閑對(duì)象的地址并減小ac->avail的值。調(diào)用cache_alloc_refill()函數(shù)來(lái)重新填充本地緩存,并在本地緩存中沒(méi)有空閑對(duì)象時(shí)獲得一個(gè)空閑對(duì)象。
cache_alloc_fill()函數(shù)基本上執(zhí)行以下步驟:
static void* cache_alloc_refill(kmem_cache_t* cachep, int flags) { // ... check_irq_off(); /* 1. 獲取本地緩存描述符 */ ac = ac_data(cachep); retry: batchcount = ac->batchcount; if (!ac->touched && batchcount > BATCHREFILL_LIMIT) { /* 如果最近這個(gè)緩存很少有活動(dòng),執(zhí)行部分填充。 * 否則容易產(chǎn)生refill bouncing。 */ batchcount = BATCHREFILL_LIMIT; } l3 = list3_data(cachep); /* 2. 申請(qǐng)自旋鎖 */ spin_lock(&cachep->spinlock); /* 3. 如果slab緩存包含本地共享緩存,且其中包含一些空閑對(duì)象時(shí)。 * 則將這些空閑對(duì)象轉(zhuǎn)移給本地緩存。 */ if (l3->shared) { struct array_cache *shared_array = l3->shared; if (shared_array->avail) { if (batchcount > shared_array->avail) batchcount = shared_array->avail; shared_array->avail -= batchcount; ac->avail = batchcount; memcpy(ac_entry(ac), &ac_entry(shared_array)[shared_array->avail], sizeof(void*)*batchcount); shared_array->touched = 1; goto alloc_done; } } /* 4 使用batchcount個(gè)指針填充本地緩存,這些指針是slab中的空閑對(duì)象的地址 */ while (batchcount > 0) { struct list_head *entry; struct slab *slabp; /* 4.a 查看cache描述符中的slabs_partial和slabs_free列表,尋找合適的空閑對(duì)象: * (1) 如果slabs_partial還有空閑對(duì)象,則從其中申請(qǐng)空閑對(duì)象 * (2) 否則從slabs_free申請(qǐng)空閑對(duì)象 * (3) 如果slabs_free沒(méi)有空閑對(duì)象,則跳轉(zhuǎn)到must_grow,擴(kuò)展slab緩存 */ entry = l3->slabs_partial.next; if (entry == &l3->slabs_partial) { l3->free_touched = 1; entry = l3->slabs_free.next; if (entry == &l3->slabs_free) goto must_grow; } slabp = list_entry(entry, struct slab, list); check_slabp(cachep, slabp); check_spinlock_acquired(cachep); while (slabp->inuse < cachep->num && batchcount--) { kmem_bufctl_t next; STATS_INC_ALLOCED(cachep); STATS_INC_ACTIVE(cachep); STATS_SET_HIGH(cachep); /* 4.b 獲取空閑對(duì)象: * (1)獲取slab中空閑對(duì)象的指針; * (2)slab描述符的inuse字段+1,表明該空閑對(duì)象已經(jīng)被使用 * (3)slab描述符的free字段+1,指向下一個(gè)空閑對(duì)象 */ ac_entry(ac)[ac->avail++] = slabp->s_mem + slabp->free*cachep->objsize; slabp->inuse++; next = slab_bufctl(slabp)[slabp->free]; slabp->free = next; } check_slabp(cachep, slabp); /* 4.c 將已經(jīng)消耗的slab插入到cache描述符的正確列表中 * (1) slab_fulll列表 * (2) slab_partial列表 */ list_del(&slabp->list); if (slabp->free == BUFCTL_END) list_add(&slabp->list, &l3->slabs_full); else list_add(&slabp->list, &l3->slabs_partial); } must_grow: /* 5. 此時(shí),要添加到本地緩存的指針數(shù)量存儲(chǔ)在`ac->avail`變量中。 * 將kmem_list3結(jié)構(gòu)體的字段free_objects減去添加到本地緩存中的指針數(shù)量, * 即是表明這些對(duì)象不再是空閑的了。 */ l3->free_objects -= ac->avail; alloc_done: /* 6. 釋放自旋鎖 */ spin_unlock(&cachep->spinlock); /* 8. 如果沒(méi)有發(fā)生緩存重填,則調(diào)用cache_grow申請(qǐng)一個(gè)新的slab, * 并申請(qǐng)分配新的空閑對(duì)象 */ if (unlikely(!ac->avail)) { int x; x = cache_grow(cachep, flags, -1); /* 9. 沒(méi)有申請(qǐng)到新slab緩存,則返回NULL; * 否則跳轉(zhuǎn)到第一步,重新分配對(duì)象 */ ac = ac_data(cachep); if (!x && ac->avail == 0) // 沒(méi)有可用的空閑對(duì)象,放棄 return NULL; if (!ac->avail) // 對(duì)象重填被中斷? goto retry; } /* 7 如果ac->avail大于0(說(shuō)明某些緩存被重填了),設(shè)置本地緩存被使用, * 并返回插入到本地緩存中的最后一個(gè)空閑對(duì)象的指針 */ ac->touched = 1; return ac_entry(ac)[--ac->avail]; }
13 釋放slab對(duì)象
kmem_cache_free()函數(shù)釋放先前由slab分配器分配給某個(gè)內(nèi)核函數(shù)的對(duì)象。它的參數(shù)是cachep,緩存描述符的地址,objp,要釋放的對(duì)象的地址:
void kmem_cache_free(kmem_cache_t *cachep, void *objp) { unsigned long flags; struct array_cache *ac; local_irq_save(flags); ac = cachep->array[smp_processor_id()]; if (ac->avail == ac->limit) cache_flusharray(cachep, ac); ((void**)(ac+1))[ac->avail++] = objp; local_irq_restore(flags); }
該函數(shù)首先檢查本地緩存是否有空間容納指向空閑對(duì)象的額外指針。如果是,指針被添加到本地緩存中,函數(shù)返回。否則,它首先調(diào)用cache_flusharray()來(lái)耗盡本地緩存,然后將指針添加到本地緩存。
cache_flusharray()函數(shù)的主要功能如下:
static void cache_flusharray (kmem_cache_t* cachep, struct array_cache *ac) { int batchcount; batchcount = ac->batchcount; check_irq_off(); /* 1. 申請(qǐng)自旋鎖 */ spin_lock(&cachep->spinlock); /* 2. 判斷貢獻(xiàn)本地緩存是否還有空間,如果有, * 則從CPU本地緩存中拷貝batchcount個(gè)指針到共享緩存中; * 然后跳轉(zhuǎn)到第4步。 */ if (cachep->lists.shared) { struct array_cache *shared_array = cachep->lists.shared; int max = shared_array->limit-shared_array->avail; if (max) { if (batchcount > max) batchcount = max; memcpy(&ac_entry(shared_array)[shared_array->avail], &ac_entry(ac)[0], sizeof(void*)*batchcount); shared_array->avail += batchcount; goto free_done; } } /* 3 將當(dāng)前本地緩存中的batchcount對(duì)象返還給`slab`分配器 */ free_block(cachep, &ac_entry(ac)[0], batchcount); free_done: /* 4. 釋放自旋鎖 */ spin_unlock(&cachep->spinlock); /* 5. 將移動(dòng)到共享本地緩存或slab分配器中的對(duì)象個(gè)數(shù)從本地緩存描述符中減去 */ ac->avail -= batchcount; /* 6. 將本地緩存中所有合法的指針移動(dòng)到本地緩存數(shù)組的起始處。 * 因?yàn)樵鹗继幍膶?duì)象指針已經(jīng)被我們移走了。 */ memmove(&ac_entry(ac)[0], &ac_entry(ac)[batchcount], sizeof(void*)*ac->avail); }
free_block()函數(shù)的主要功能如下:
static void free_block(kmem_cache_t *cachep, void **objpp, int nr_objects) { int i; check_spinlock_acquired(cachep); /* NUMA: move add into loop * 1. 增加cache描述符的空閑對(duì)象計(jì)數(shù) */ cachep->lists.free_objects += nr_objects; for (i = 0; i < nr_objects; i++) { void *objp = objpp[i]; struct slab *slabp; unsigned int objnr; /* 2. 獲取對(duì)象所在slab的描述符地址: * (slab所在頁(yè)的描述符的lru字段指向相應(yīng)的slab描述符) */ slabp = GET_PAGE_SLAB(virt_to_page(objp)); // 3. 從slab cache列表中移除slab描述符 // (cachep->lists.slabs_partial或cachep->lists.slabs_full) list_del(&slabp->list); // 4. 計(jì)算對(duì)象在slab中的索引 objnr = (objp - slabp->s_mem) / cachep->objsize; check_slabp(cachep, slabp); // 5. 將slab中下一個(gè)空閑對(duì)象的索引存入對(duì)象描述符中 // 下一個(gè)空閑對(duì)象是`objnr`。 // (最后一個(gè)釋放的對(duì)象,將是第一個(gè)待分配的對(duì)象) slab_bufctl(slabp)[objnr] = slabp->free; slabp->free = objnr; STATS_DEC_ACTIVE(cachep); // 6. 對(duì)象已經(jīng)恢復(fù)空閑 slabp->inuse--; check_slabp(cachep, slabp); /* */ if (slabp->inuse == 0) { /* 7 如果slab中所有對(duì)象都是空閑的(inuse=0),且 * 整個(gè)slab緩存中空閑對(duì)象的數(shù)量大于cache上限,則 * 將多余的空閑對(duì)象占用的slab頁(yè)幀釋放回`ZONE`頁(yè)幀分配器> * * cachep->free_limit == cachep->num+ (1+N) × cachep->batchcount * 此處的N是系統(tǒng)中CPU核的數(shù)量。 */ if (cachep->lists.free_objects > cachep->free_limit) { cachep->lists.free_objects -= cachep->num; slab_destroy(cachep, slabp); } else { /* 8. 如果slab中所有對(duì)象都是空閑的(inuse=0),但整個(gè)slab緩存 * 中的空閑對(duì)線小于等于cachep->free_limit,則將slab描述符 * 插入到cachep->lists.slabs_free列表中 */ list_add(&slabp->list, &list3_data_ptr(cachep, objp)->slabs_free); } } else { /* 9. 如果inuse大于0,說(shuō)明slab部分被用,所以將slab描述符插入到 * cachep->lists.slabs_partial列表中。 * 無(wú)條件的將一個(gè)slab移動(dòng)到slabs_partial列表的末尾, * 這也是釋放其它的最大時(shí)間。 */ list_add_tail(&slabp->list, &list3_data_ptr(cachep, objp)->slabs_partial); } } }
14 通用對(duì)象
在前面通用和特殊緩存一節(jié)中,對(duì)內(nèi)存不頻繁的請(qǐng)求是通過(guò)一組通用緩存實(shí)現(xiàn)的,這些通用緩存對(duì)象的大小是按照幾何大小均勻分布的,從32→131072個(gè)字節(jié)。
這類對(duì)象是通過(guò)kmalloc()實(shí)現(xiàn)的,約等于下面的代碼:
void * kmalloc(size_t size, int flags) { struct cache_sizes *csizep = malloc_sizes; kmem_cache_t *cachep; for (; csizep->cs_size; csizep++) { if (size > csizep->cs_size) continue; if (flags & __GFP_DMA) cachep = csizep->cs_dmacachep; else cachep = csizep->cs_cachep; return kmem_cache_alloc(cachep, flags); } return NULL; }
該函數(shù)借助malloc_sizes表鎖定最接近請(qǐng)求內(nèi)存大小的2的冪次方對(duì)應(yīng)的cache描述符表。然后調(diào)用kmem_cache_alloc()分配對(duì)象,可以傳遞給DMA內(nèi)存的緩存描述符,也可以是普通內(nèi)存的緩存描述符,取決于調(diào)用者是否傳遞了__GFP_DMA標(biāo)志。
對(duì)應(yīng)的釋放通用緩存對(duì)象的函數(shù)是kfree():
void kfree(const void *objp) { kmem_cache_t *c; unsigned long flags; if (!objp) return; local_irq_save(flags); c = (kmem_cache_t *)(virt_to_page(objp)->lru.next); kmem_cache_free(c, (void *)objp); local_irq_restore(flags); }
正確的緩存描述符是通過(guò)讀取包含內(nèi)存區(qū)域的第1頁(yè)幀的描述符的lru.next字段來(lái)確定的。通過(guò)調(diào)用kmem_cache_free()釋放內(nèi)存區(qū)域。
15 內(nèi)存池
內(nèi)存池是Linux v2.6內(nèi)核的一個(gè)新特性?;旧希瑑?nèi)存池允許內(nèi)核子系統(tǒng)(比如塊設(shè)備子系統(tǒng))分配一些動(dòng)態(tài)內(nèi)存,僅在內(nèi)存不足的緊急情況下使用。
首先我們不應(yīng)該將內(nèi)存池(memory pool)和預(yù)留頁(yè)幀池混淆。
實(shí)際上,這些預(yù)留頁(yè)幀只能用于滿足中斷處理程序或關(guān)鍵代碼區(qū)發(fā)出的原子內(nèi)存分配請(qǐng)求。相反,內(nèi)存池是動(dòng)態(tài)內(nèi)存的儲(chǔ)備,只能由特定的內(nèi)核組件(即內(nèi)存池的“所有者”)使用。內(nèi)核組件通常不使用這些預(yù)留的動(dòng)態(tài)內(nèi)存池;但是,如果動(dòng)態(tài)內(nèi)存變得稀缺時(shí),以至于所有正常的內(nèi)存分配請(qǐng)求都注定要失敗,內(nèi)核組件可以調(diào)用特殊的內(nèi)存池函數(shù),作為最后的手段,可以從內(nèi)存池預(yù)留獲得所需的內(nèi)存。
通常,內(nèi)存池也是基于slab分配器申請(qǐng)的,也就是說(shuō),內(nèi)存池就是預(yù)留了一些slab對(duì)象。但是,內(nèi)存池可以分配任何類型的動(dòng)態(tài)內(nèi)存,從整個(gè)的頁(yè)幀到非常小的內(nèi)存塊都可以。因此,我們一般將內(nèi)存池處理的內(nèi)存單元稱為內(nèi)存元素。
內(nèi)存池用mempool_t數(shù)據(jù)結(jié)構(gòu)表示,各個(gè)字段如下表所示:
類型 | 名稱 | 描述 |
---|---|---|
spinlock_t | lock | 保護(hù)對(duì)象的自旋鎖 |
int | min_nr | 內(nèi)存池的最大數(shù)量 |
int | curr_nr | 內(nèi)存池的當(dāng)前數(shù)量 |
void ** | elements | 指向內(nèi)存池中內(nèi)存元素指針的數(shù)組的指針 |
void * | pool_data | 內(nèi)存池所有者的私有數(shù)據(jù) |
mempool_alloc_t * | alloc | 分配一個(gè)內(nèi)存池元素的方法 |
mempool_free_t * | free | 釋放一個(gè)內(nèi)存池元素的方法 |
wait_queue_head_t | wait | 內(nèi)存池為空時(shí)使用的等待隊(duì)列 |
min_nr存儲(chǔ)內(nèi)存池中元素的初始數(shù)量。換句話說(shuō),存儲(chǔ)在此字段中的值表示內(nèi)存池的所有者肯定會(huì)從內(nèi)存分配器獲得的內(nèi)存元素的數(shù)量。curr_nr總是小于或等于min_nr,它存儲(chǔ)當(dāng)前包含在內(nèi)存池中的內(nèi)存元素的數(shù)量。內(nèi)存元素本身由一個(gè)指針數(shù)組引用,其地址存儲(chǔ)在“elements”字段中。
alloc和free方法分別與底層內(nèi)存分配器交互以獲取和釋放內(nèi)存元素。這兩種方法都可以是由擁有內(nèi)存池的內(nèi)核組件提供的自定義函數(shù)。
當(dāng)內(nèi)存元素是slab對(duì)象時(shí),alloc和free方法通常由mempool_alloc_slab()和mempool_free_slab()函數(shù)實(shí)現(xiàn),它們分別調(diào)用kmem_cache_alloc()和kmem_cache_free()函數(shù)。在這種情況下,mempool_t對(duì)象的pool_data字段存儲(chǔ)slab緩存描述符的地址。
當(dāng)內(nèi)存元素是自定義數(shù)據(jù)對(duì)象時(shí),alloc和free方法通常由kmalloc和kfree函數(shù)實(shí)現(xiàn)。分配的通用緩存對(duì)象。如下所示:
static void *pkt_rb_alloc(int gfp_mask, void *data) { return kmalloc(sizeof(struct pkt_rb_node), gfp_mask); }
mempool_create()創(chuàng)建一個(gè)新的內(nèi)存池:它接收的參數(shù)是min_nr,alloc和free函數(shù)的地址,及pool_data。該函數(shù)為mempool_t對(duì)象和指向內(nèi)存元素的指針數(shù)組分配內(nèi)存,然后重復(fù)調(diào)用alloc方法來(lái)獲取min_nr內(nèi)存元素。相反,mempool_destroy()函數(shù)釋放池中的所有內(nèi)存元素,然后釋放元素?cái)?shù)組和mempool_t對(duì)象本身。
為了從內(nèi)存池中分配一個(gè)元素,內(nèi)核調(diào)用mempool_alloc()函數(shù),傳遞給它mempool_t對(duì)象的地址和內(nèi)存分配標(biāo)志。本質(zhì)上,該函數(shù)根據(jù)指定的內(nèi)存分配標(biāo)志,通過(guò)調(diào)用alloc方法,嘗試從底層內(nèi)存分配器分配內(nèi)存元素。如果分配成功,該函數(shù)返回獲得的內(nèi)存元素,而不觸及內(nèi)存池。否則,如果分配失敗,則從內(nèi)存池中取一個(gè)內(nèi)存元素。當(dāng)然,在內(nèi)存不足的情況下,太多的內(nèi)存分配請(qǐng)求會(huì)耗盡內(nèi)存池;在這種情況下,如果沒(méi)有設(shè)置__GFP_WAIT標(biāo)志,mempool_alloc()會(huì)阻塞當(dāng)前進(jìn)程,直到內(nèi)存元素被釋放到內(nèi)存池中。
相反,要將元素釋放到內(nèi)存池中,內(nèi)核調(diào)用mempool_free()函數(shù)。如果內(nèi)存池未滿(curr_min小于min_nr),則該函數(shù)將元素添加到內(nèi)存池中。否則,mempool_free()調(diào)用free方法將元素釋放到底層內(nèi)存分配器。
審核編輯:湯梓紅
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1360瀏覽量
40185 -
Linux
+關(guān)注
關(guān)注
87文章
11207瀏覽量
208717 -
分配器
+關(guān)注
關(guān)注
0文章
193瀏覽量
25694 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4277瀏覽量
62323
原文標(biāo)題:Linux內(nèi)核8.7-內(nèi)存管理之slab分配器
文章出處:【微信號(hào):嵌入式ARM和Linux,微信公眾號(hào):嵌入式ARM和Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論