Linux的內(nèi)存管理
Linux的內(nèi)存管理是一個(gè)非常復(fù)雜的過(guò)程,主要分成兩個(gè)大的部分:內(nèi)核的內(nèi)存管理和進(jìn)程虛擬內(nèi)存。內(nèi)核的內(nèi)存管理是Linux內(nèi)存管理的核心,所以我們先對(duì)內(nèi)核的內(nèi)存管理進(jìn)行簡(jiǎn)介。
一、物理內(nèi)存模型

?
物理內(nèi)存模型主要分為兩種:UMA(Uniform Memory Access)和NUMA(Non-Uniform Memory Access)。
UMA模型是指物理內(nèi)存是連續(xù)的,SMP系統(tǒng)中的每個(gè)處理器訪問(wèn)各個(gè)內(nèi)存區(qū)都是同樣快的;而NUMA模型則是指SMP中的每個(gè)CPU都有自己的物理內(nèi)存區(qū),雖然CPU可以訪問(wèn)其他CPU的內(nèi)存區(qū),但是要比方位自己的內(nèi)存區(qū)慢得多。我們一般使用的物理模型都是UMA模型。為了NUMA模型,Linux提供了三種可能的內(nèi)存布局配置:Flat Memory, Sparse Memory, Discontiguous Memory。

?
Flat Memory就是簡(jiǎn)單的線性組織物理內(nèi)存,一般沒(méi)有內(nèi)存空洞的UMA架構(gòu)都采用這種配置。對(duì)于NUMA模型,一般只能采用后兩者,而后兩者的區(qū)別主要在于:Sparse Memory配置一般認(rèn)為是試驗(yàn)性的,不是那么穩(wěn)定,但是有一些新的功能和性能優(yōu)化,而Discontiguous Memory配置一般認(rèn)為是穩(wěn)定的,但不具有內(nèi)存熱插拔之類(lèi)的新特性。
二、物理內(nèi)存組織
- 物理內(nèi)存的組織主要分為兩個(gè)部分:節(jié)點(diǎn)(node)和內(nèi)存與內(nèi)存域(zone)。
- node主要針對(duì)NUMA設(shè)計(jì),在NUMA的SMP系統(tǒng)中,每個(gè)處理器都有一個(gè)自己的node,而在UMA模型中則只有一個(gè)node。對(duì)于每個(gè)node中的內(nèi)存,Linux分成了若干內(nèi)存域,定義在mmzone.h的zone_type中,常用的有ZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_HIGHMEM和ZONE_MOVABLE。其中ZONE_NORMAL是最為常用的,表示內(nèi)核能夠直接映射的一般內(nèi)存區(qū)域;ZONE_DMA表示DMA內(nèi)存區(qū);ZONE_DMA32表示64位系統(tǒng)中對(duì)于32位DMA設(shè)備使用的內(nèi)存;ZONE_HIGHMEM表示在32位系統(tǒng)中,高地址內(nèi)存的區(qū)域;ZONE_MOVABLE與伙伴系統(tǒng)的內(nèi)存碎片消除有關(guān)。后文會(huì)詳細(xì)介紹相關(guān)部分。
- 在物理內(nèi)存管理過(guò)程中有一些名詞:
- Page Frame(頁(yè)幀,或稱(chēng)頁(yè)框):是系統(tǒng)內(nèi)存管理的最小單位,系統(tǒng)中每個(gè)頁(yè)框都是struct page的一個(gè)實(shí)例。IA-32系統(tǒng)的頁(yè)框大小是4KB。
- Hot-n-Code Pages(冷熱頁(yè)):是指內(nèi)存管理中對(duì)頁(yè)框的分類(lèi),訪問(wèn)較多的或者近期訪問(wèn)的為熱頁(yè),否則為冷頁(yè)。該標(biāo)記主要與內(nèi)存換出(memory swap)相關(guān)。
- Page Table(頁(yè)表):是內(nèi)存尋址過(guò)程中的輔助數(shù)據(jù)結(jié)構(gòu)。層次化的頁(yè)表對(duì)于大地之空間的快速、高效管理很有意義。Linux一般支持四級(jí)頁(yè)表:PGD(Page Global Directory)、PUD(Page Upper Directory)、PMD(Page Middle Directory)和PTE(Page Table Entry)。IA-32體系中默認(rèn)只是用了兩級(jí)分頁(yè)系統(tǒng),即只有PGD和PTE。
三、X86架構(gòu)下的內(nèi)存布局
內(nèi)核在內(nèi)存中的布局
Linux的內(nèi)核在初始化的時(shí)候會(huì)被加載到內(nèi)存區(qū)的固定位置(在此我們不討論可重定位內(nèi)核的情況),而內(nèi)核所占用的內(nèi)存區(qū)域的布局是固定的,如圖:

?
內(nèi)存第一個(gè)頁(yè)框不實(shí)用,主要被BIOS用來(lái)初始化;之后的連續(xù)640KB內(nèi)存也不被內(nèi)核使用,主要用來(lái)映射各種ROM(通常是BIOS和顯卡ROM);再之后的空間是閑置的,原因是內(nèi)核要被放在連續(xù)的內(nèi)存空間。在0x100000開(kāi)始為內(nèi)核部分,分別是代碼段、數(shù)據(jù)段和附加段。
IA-32架構(gòu)的布局
IA-32架構(gòu)可以訪問(wèn)4GB的地址空間(不考慮PAE),常規(guī)情況下會(huì)將4GB線性空間劃分成3:1的兩部分:低地址的3/4部分為用戶(hù)空間,而高地址的1GB是內(nèi)核空間,即內(nèi)核地址空間從偏移量0xC0000000開(kāi)始,每個(gè)虛擬地址x都對(duì)應(yīng)于物理地址x-0xC0000000。這樣的設(shè)計(jì)加快了內(nèi)核空間尋址的速度(簡(jiǎn)單的減法操作)。在進(jìn)程切換的過(guò)程中,只有用戶(hù)空間的低3GB內(nèi)存對(duì)應(yīng)的頁(yè)表會(huì)被切換,高地址空間會(huì)公用內(nèi)核頁(yè)表。
IA-32架構(gòu)的這種設(shè)計(jì)存在著一個(gè)問(wèn)題:既然內(nèi)核只能處理1GB的空間(事實(shí)上,內(nèi)核處理的空間還不足1GB,后面會(huì)詳細(xì)說(shuō)明),那么如果物理內(nèi)存大于1GB,剩下的內(nèi)存將如何處理?這種情況下,內(nèi)核將無(wú)法直接映射全部物理內(nèi)存,這樣就用到了上面所說(shuō)的高地址內(nèi)存域(ZONE_HIGHMEM)。具體的內(nèi)存分配如下圖:

?
能夠看到內(nèi)核區(qū)域的映射從__PAGE_OFFSET(0xC00000)開(kāi)始,即3GiB位置開(kāi)始映射到4GiB,開(kāi)始的一段用來(lái)直接映射,而后面有128MB的VMALLOC空間(這部分空間的使用后文將講到),再之后有永久映射和固定映射的空間(從PKMAP_BASE開(kāi)始)。所以事實(shí)上物理內(nèi)存能夠直接映射的空間為1GB-VMALLOC-固定映射-永久映射,所以真正大約只有850MB多一點(diǎn),也就是說(shuō),物理內(nèi)存中只有前850多MB是可以直接映射到內(nèi)核空間的,對(duì)于超過(guò)的部分來(lái)說(shuō),將作為高地址空間(HIGHMEM)。高地址空間可以在VMALLOC、永久映射和固定映射部分使用到。
到這里可能會(huì)有這樣一個(gè)疑問(wèn):如果內(nèi)核只能處理896MB的空間,那么如果內(nèi)存很大(比如3GB),剩下的空間的利用率和利用效率豈不是很低?對(duì)于這個(gè)問(wèn)題我們需要注意:這里我們所講述的:1、這里的內(nèi)存都是內(nèi)核在內(nèi)核區(qū)的1GB空間里對(duì)物理內(nèi)存的訪問(wèn),用戶(hù)對(duì)物理內(nèi)存的訪問(wèn)不是通過(guò)直接映射來(lái)訪問(wèn)的,還有另外一套機(jī)制;2、這里的內(nèi)存僅僅是通過(guò)直接映射得到的內(nèi)存,內(nèi)核還可以通過(guò)其他的方式訪問(wèn)到較高地址的內(nèi)存。
還有一個(gè)普遍的疑問(wèn)就是:內(nèi)核直接映射占用了800多MB的空間,那么如果我們又3GB的物理內(nèi)存,是不是只有2GB多一點(diǎn)的實(shí)際可用內(nèi)存呢?這個(gè)說(shuō)法是錯(cuò)誤的,上圖所描述的只是內(nèi)核在線性地址空間的分布情況,其中的任何區(qū)域如果沒(méi)有真正的物理內(nèi)存與之映射的話是不會(huì)真正占用物理內(nèi)存的,而物理內(nèi)存在分配的過(guò)程中(用戶(hù)申請(qǐng)內(nèi)存、VMALLOC部分等),更傾向于先分配高地址內(nèi)存,在高地址內(nèi)存耗盡的情況下才會(huì)使用低850MB內(nèi)存。
AMD64架構(gòu)的布局
AMD64架構(gòu)采用了與IA-32完全不同的布局模式。由于64位的尋址空間的64位長(zhǎng),而在真正的實(shí)現(xiàn)過(guò)程中64位長(zhǎng)尋址會(huì)造成較大的開(kāi)銷(xiāo),所以Linux目前僅適用了48位長(zhǎng)的地址空間,但是為了向后兼容仍然適用64位地址空間表示。在布局方面考慮,如果單純采用48位類(lèi)似IA-32的布局方式的話,則很難保證向后兼容性。所以AMD64架構(gòu)下的內(nèi)存布局Linux采用了一種特殊的方式,如圖:

?
Linux將內(nèi)存分成了高地址部分和低地址部分兩部分,即下半部空間0~0x0000 7FFF FFFF FFFF和上半部空間0xFFFF 8000 0000 0000~0xFFFF FFFF FFFF FFFF。可以看到虛擬地址的低47位,即[0,46]為有效位,[47,63]的值總是相同的:或者全為0或者全為1。除此之外的值都是無(wú)效的。這樣在虛擬內(nèi)存空間中就將內(nèi)存分成了兩個(gè)部分:內(nèi)存空間的下半部和上半部。下半部為用戶(hù)空間,上半部為內(nèi)核空間。我們考慮內(nèi)核空間部分,下半部的前MAXMEM大小(64TB)為直接映射地址,之后有一個(gè)空洞,主要目的是處理內(nèi)存訪問(wèn)越界;再之后是大小為32TB的VMALLOC空間,在之后是VMMEMMAP空間、KERNEL TEXT段空間以及Modules空間。
在這里我們不仔細(xì)講述AMD64架構(gòu)的布局,以后的部分則主要關(guān)注于IA-32架構(gòu)。
四、啟動(dòng)過(guò)程期間的內(nèi)存管理
在啟動(dòng)過(guò)程中,盡管內(nèi)存管理尚未初始化,但內(nèi)核仍然需要分配內(nèi)存以創(chuàng)建各種數(shù)據(jù)結(jié)構(gòu)。bootmem分配器用于在啟動(dòng)階段早期分配內(nèi)存。由于對(duì)這部分內(nèi)存分配集中于簡(jiǎn)單性方面而不是性能和通用性,因此使用的是最先適配(first-fit)分配器。該分配器使用一個(gè)位圖來(lái)管理頁(yè),位圖中的1表示頁(yè)已使用,0表示未使用。在需要分配內(nèi)存時(shí),分配器掃描位圖,直到找到一個(gè)能夠提供足夠連續(xù)頁(yè)的為之,即最先最佳(first-best)或最先適配位置。
在這個(gè)分配過(guò)程中,需要處理一些不可分配的頁(yè)面,如IA-32系統(tǒng)中的0頁(yè)。另外對(duì)于IA-32系統(tǒng),bootmem僅僅使用了低地址部分,對(duì)于高地址部分的操作過(guò)于麻煩,所以在這里被放棄了。
在這個(gè)部分有一個(gè)很有意思的事情。我們?cè)诰帉?xiě)內(nèi)核模塊的時(shí)候,對(duì)于模塊的初始化函數(shù)會(huì)使用__init標(biāo)記或者_(dá)_init_data標(biāo)記。對(duì)于被這兩個(gè)關(guān)鍵字標(biāo)記的函數(shù)和數(shù)據(jù),是只有在初始化階段才用到的,在bootmem退出的時(shí)候會(huì)全部被回收。而這部分代碼和數(shù)據(jù)再內(nèi)核鏈接的過(guò)程中將會(huì)被放在.init.text段和.init.data段,并統(tǒng)一放在內(nèi)核的尾部,在啟動(dòng)結(jié)束后便于回收。
五、物理內(nèi)存的管理
1、伙伴系統(tǒng)
物理內(nèi)存管理中伙伴系統(tǒng)是最為重要的一個(gè)系統(tǒng),伙伴系統(tǒng)也基于一種相對(duì)簡(jiǎn)單然而令人吃驚的強(qiáng)大算法,到目前已經(jīng)使用了幾乎40年?;锇橄到y(tǒng)在這里不再贅述,簡(jiǎn)單谷歌一下就可以查到該算法的描述(實(shí)在是很簡(jiǎn)單)。在這里主要講一下Linux Kernel的伙伴系統(tǒng)以及在2.6.24之后版本的系統(tǒng)中對(duì)伙伴系統(tǒng)的改良。
在上文中已經(jīng)說(shuō)到,物理內(nèi)存的慣例分為若干個(gè)node,每個(gè)node中又有若干個(gè)zone。對(duì)于每個(gè)zone,都會(huì)有對(duì)應(yīng)的伙伴系統(tǒng),如下圖所示:

?
上圖中的Fallback list指的是:在多個(gè)node的系統(tǒng)中,如果某個(gè)node的內(nèi)存空間不夠,則會(huì)在Fallback List中指定的node中分配內(nèi)存。
我們可以執(zhí)行cat /proc/buddyinfo,能夠看到大約如下所示的信息:
/proc/buddyinfo:
wolfgang@meitner> cat /proc/buddyinfo
Node 0, zone DMA 3 5 7 4 6 3 3 3 1 1 1
Node 0, zone DMA32 130 546 695 271 107 38 2 2 1 4 479
Node 0, zone Normal 23 6 6 8 1 4 3 0 0 0 0
顯示的三個(gè)域則是我們使用到的內(nèi)存域。
伙伴系統(tǒng)會(huì)出現(xiàn)一個(gè)很常見(jiàn)的問(wèn)題:在系統(tǒng)使用較長(zhǎng)時(shí)間之后,內(nèi)存中經(jīng)常出現(xiàn)較多碎片。對(duì)于這種情況,內(nèi)核將內(nèi)存頁(yè)面分成五種類(lèi)型:
MIGRATE_UNMOVABLE
MIGRATE_RECLAIMABLE
MIGRATE_RESERVE
MIGRATE_MOVABLE
MIGRATE_ISOLATE
其中MIGRATE_RESERVE所表示的內(nèi)存是被系統(tǒng)保留以備急用的;MIGRATE_UNMOVABLE是不可移動(dòng)的,如BIOS信息頁(yè);MIGRATE_RECLAIMABLE在swap系統(tǒng)中使用;MIGRATE_ISOLATE表示不能從這里分配的內(nèi)存;MIGRATE_MOVABLE表示可以移動(dòng)的內(nèi)存。對(duì)于內(nèi)核來(lái)說(shuō),MIGRATE_MOVABLE部分的內(nèi)存可以采用某種算法來(lái)進(jìn)行移動(dòng),使得內(nèi)存中的碎片減少。另外內(nèi)核還維護(hù)了一個(gè)fallback list,來(lái)表示如果在某個(gè)類(lèi)型中分配頁(yè)面未成功,會(huì)在哪些類(lèi)型的頁(yè)面中來(lái)分配。
具體的信息可以在/proc/pagetypeinfo中看到
2、伙伴系統(tǒng)的內(nèi)存分配API
基本上從如下兩張圖就能夠看出來(lái):

?

?
對(duì)于其中的函數(shù)命名基本都是自明的,主要的差別在于:對(duì)于雙下劃線開(kāi)頭的函數(shù)(__get_free_page, __free_page等)返回值或者參數(shù)為struct page *,而其他的函數(shù)返回值為unsigned long,即線性地址地址。
3、內(nèi)核中不連續(xù)頁(yè)的分配
根據(jù)上文的講述,我們知道物理上連續(xù)的映射對(duì)內(nèi)核是最好的,但并不是總能成功的使用。所以?xún)?nèi)核提供了類(lèi)似用戶(hù)空間訪問(wèn)內(nèi)存一樣的機(jī)制(vmalloc)來(lái)進(jìn)行對(duì)內(nèi)核中不連續(xù)頁(yè)的分配。這一部分就是上文中所說(shuō)的vmalloc區(qū)域。這部分主要是一個(gè)vmalloc函數(shù):
void *vmalloc(unsigned long size);
在該函數(shù)的實(shí)現(xiàn)過(guò)程中,需要先申請(qǐng)一部分虛擬內(nèi)存空間vm_area,然后將這部分空間映射到vmalloc區(qū)域中。對(duì)于映射的物理內(nèi)存,內(nèi)核更傾向于使用高地址空間(ZONE_HIGHMEM),來(lái)節(jié)省寶貴的地地址空間。對(duì)于不同vmalloc調(diào)用申請(qǐng)的vm_area之間,會(huì)有一個(gè)hole來(lái)隔離,以避免越界訪問(wèn)。

?
注意vmalloc系統(tǒng)底層也是使用伙伴系統(tǒng)來(lái)分配內(nèi)存,所以申請(qǐng)內(nèi)存的大小只能是整頁(yè)的(頁(yè)大小對(duì)齊)。
在這部分有一個(gè)有意思的事情:vmalloc區(qū)域在IA-32中預(yù)設(shè)的大小是128MB,這部分內(nèi)存一般會(huì)被內(nèi)核模塊使用。vmalloc區(qū)域的大小是可以定制的,在新版內(nèi)核中可以在內(nèi)核啟動(dòng)選項(xiàng)中加入vmalloc=xxxMB的方式來(lái)修改,或者修改內(nèi)核代碼對(duì)應(yīng)的宏:
unsigned int __VMALLOC_RESERVE = 128 << 20;
如果修改了vmalloc區(qū)域的大小,那么內(nèi)核能夠直接映射的區(qū)域?qū)?huì)縮小,即kmalloc能夠使用的內(nèi)存將會(huì)變少(kmalloc使用slab allocator分配,后文將會(huì)介紹),但是內(nèi)核真正使用的物理內(nèi)存和vmalloc區(qū)域的大小沒(méi)有直接關(guān)系。所以在內(nèi)核模塊的編寫(xiě)過(guò)程中,要根據(jù)需求來(lái)使用vmalloc和kmalloc,而了解他們的內(nèi)存分配機(jī)制是很有好處的。
4、內(nèi)核映射
盡管vmalloc函數(shù)族可用于從高端內(nèi)存向內(nèi)核映射頁(yè)框,但這并不是這些函數(shù)的實(shí)際用途。內(nèi)核提供了其他函數(shù)用于將ZONE_HIGHMEM頁(yè)框顯式的映射到內(nèi)核空間。
如果需要長(zhǎng)期的將高端頁(yè)框映射到內(nèi)核地址空間中,即持久映射,需要使用kmap函數(shù),映射的空間指向上文圖中所指Persistent Mapings。內(nèi)核使用kunmap接觸映射。持久映射kmap函數(shù)不能用于處理中斷處理程序,因?yàn)閗map過(guò)程可能進(jìn)入睡眠狀態(tài)。
為了能夠原子的執(zhí)行映射過(guò)程(邏輯上稱(chēng)為kmap_atomic),內(nèi)核提供了臨時(shí)映射機(jī)制,也被稱(chēng)作固定映射,頁(yè)面也會(huì)被映射到Fixmaps區(qū)域。映射的API分別是kmap_atomic和kunmap_atomic。固定映射可以用在中斷處理程序中。
對(duì)于不支持高端內(nèi)存的體系結(jié)構(gòu)(如64位體系結(jié)構(gòu)),則將以上若干映射函數(shù)通過(guò)預(yù)編譯選項(xiàng)指向了對(duì)應(yīng)的兼容函數(shù)。事實(shí)上對(duì)于這些體系結(jié)構(gòu)的映射,都是簡(jiǎn)單的返回對(duì)應(yīng)的內(nèi)存地址即可,因?yàn)閮?nèi)核可以在直接映射區(qū)域簡(jiǎn)單的找到對(duì)應(yīng)的地址。
六、slab分配器
上面所描述的物理內(nèi)存管理機(jī)制中,最小粒度的內(nèi)存管理單元是頁(yè)框,大小一般是4KB,而在內(nèi)存中無(wú)論何時(shí)申請(qǐng)內(nèi)存都分配一個(gè)頁(yè)面是不合適的方式,所以引入了新的管理機(jī)制,即slab分配器。Slab是Sun公司的一個(gè)雇員Jeff Bonwick在Solaris 2.4中設(shè)計(jì)并實(shí)現(xiàn)的。slab分配器將大小相同的內(nèi)核對(duì)象放在一起,當(dāng)對(duì)象被free了之后并不是直接還給伙伴系統(tǒng),而是將這部分對(duì)象的頁(yè)面保存下來(lái),在下一次該類(lèi)對(duì)象的內(nèi)存申請(qǐng)時(shí)分配給新的對(duì)象。這種機(jī)制的優(yōu)勢(shì)在于:1、能夠按照CPU緩存的大小來(lái)組織分配對(duì)象的位置,一般來(lái)說(shuō),都會(huì)將若干個(gè)相同的對(duì)象放在一個(gè)cacheline中,并且對(duì)象占用的內(nèi)存不會(huì)跨越兩個(gè)cacheline。這樣的設(shè)計(jì)能夠保證slab分配器分配的對(duì)象能夠較多時(shí)間的存在于CPU緩存中。2、采用LIFO方式管理對(duì)象。這種做法基于:最近釋放的對(duì)象空間是最有可能存在于cache中的。這也能夠有效的利用cache。
各個(gè)緩存管理的對(duì)象,會(huì)合并為較大的組,覆蓋一個(gè)或者多個(gè)連續(xù)的頁(yè)框。這種組稱(chēng)作slab,每個(gè)緩存由幾個(gè)這種slab組成。這也是slab分配器命名的由來(lái)。
1、slab、slob、slub分配器
Linux內(nèi)核中目前支持三種分配器,其中slab前文已經(jīng)簡(jiǎn)單介紹過(guò)了,另外兩種分配器是備選分配器,可以在內(nèi)核編譯選項(xiàng)中指定。由于對(duì)上層提供的API是固定的,僅僅是底層實(shí)現(xiàn)不同,所以Kernel開(kāi)發(fā)者不必去考慮底層的分配情況。
slab分配器雖然有很大的優(yōu)勢(shì),但是其存在兩個(gè)問(wèn)題:1、在較小的內(nèi)存系統(tǒng)下,slab分配器過(guò)于復(fù)雜。如嵌入式環(huán)境下slab顯得有些過(guò)于龐大。2、在內(nèi)存很大的巨型機(jī)上,slab分配器本身的數(shù)據(jù)結(jié)構(gòu)所占用的內(nèi)存空間過(guò)大,最大的可高達(dá)2GB以上。
對(duì)于前一種情況,設(shè)計(jì)了slob分配器,它圍繞一個(gè)simple linked list of block展開(kāi)(也是slob的由來(lái)),在分配內(nèi)存的時(shí)候,采用了最先適配算法(first-fit)。
對(duì)于后一種情況,設(shè)計(jì)了slub分配器,slub分配器將頁(yè)框打包為組,并通過(guò)struct page中未使用的字段來(lái)管理這些組,試圖最小化內(nèi)存開(kāi)銷(xiāo)。slub分配器事實(shí)上是基于slab分配器的一種優(yōu)化結(jié)構(gòu)。在大型機(jī)上slub分配器上有著更好的性能。
2、slab分配器的原理
內(nèi)核中一般的內(nèi)存分配和釋放的函數(shù)有kmalloc、kzalloc、kcalloc。這三個(gè)函數(shù)的區(qū)別是:kmalloc僅僅申請(qǐng)一片空間,kzalloc在申請(qǐng)一篇空間之后將其置0。kcalloc很少用,即對(duì)數(shù)組進(jìn)行空間分配并置0。
所有活動(dòng)的slab緩存可以通過(guò)cat /proc/slabinfo來(lái)看到。
slab分配器由一個(gè)緊密交織的數(shù)據(jù)和內(nèi)存結(jié)構(gòu)的網(wǎng)絡(luò)組成,主要可以分為如圖的兩部分:保存管理型數(shù)據(jù)的緩存對(duì)象和保存被管理對(duì)象的各個(gè)slab。

?
每個(gè)slab緩存只負(fù)責(zé)一種對(duì)象類(lèi)型,或者提供一般性的緩沖區(qū)。下圖中給出了緩存的精細(xì)結(jié)構(gòu):

?
可以看到對(duì)于每個(gè)slab緩存,都會(huì)保存成一個(gè)struct kmem_cache,每個(gè)結(jié)構(gòu)里面包含一個(gè)穿在一起的鏈表,以及三個(gè)鏈表頭:free、partial、full,分別表示空閑鏈、部分空閑鏈和滿(mǎn)鏈。含義和字面意思相同。對(duì)象在slab中并不是連續(xù)排列的,用戶(hù)可以要求對(duì)象按硬件緩存對(duì)齊,也可以要求按照BYTES_PER_WORD對(duì)齊,該值表示void指針?biāo)璧淖止?jié)的數(shù)目。
創(chuàng)建新的slab緩存需要調(diào)用kmem_cache_create函數(shù),返回struct kmem_cache結(jié)構(gòu)。創(chuàng)建緩存的時(shí)候需要制定緩存的可讀name(會(huì)出現(xiàn)在/proc/slabinfo中),還需要制定被管理對(duì)象以字節(jié)計(jì)的長(zhǎng)度(size),在對(duì)齊數(shù)據(jù)時(shí)使用的偏移量(align),以及flags標(biāo)志。另外還需要制定構(gòu)造/析構(gòu)函數(shù)ctor/dtor。
分配對(duì)象時(shí)調(diào)用kmem_cache_alloc,函數(shù)需要指定創(chuàng)建過(guò)的slab緩存,以及flags。內(nèi)核支持的所有GFP_xxx宏都可以用于指定標(biāo)志。
下圖顯示了分配對(duì)象的過(guò)程:

?
3、通用緩存
如果不涉及對(duì)象緩存,而是傳統(tǒng)意義上的分配/釋放內(nèi)存,則需要調(diào)用kmalloc和kfree函數(shù),而這兩個(gè)函數(shù)的后端依然是使用slab分配器進(jìn)行分配的。kmalloc的基礎(chǔ)是一個(gè)數(shù)組,其中是一些分別用于不同內(nèi)存長(zhǎng)度的slab緩存,數(shù)組項(xiàng)是cache_sizes的實(shí)例。該數(shù)據(jù)結(jié)構(gòu)定義如下:
struct cache_sizes {
size_t cs_size;
kmem_cache_t *cs_cachep;
kmem_cache_t *cs_dmacachep;
#ifdef CONFIG_ZONE_DMA
struct kmem_cache *cs_dmacachep;
#endif
}
cs_size指定了該項(xiàng)負(fù)責(zé)的內(nèi)存區(qū)的長(zhǎng)度。每個(gè)長(zhǎng)度對(duì)應(yīng)兩個(gè)slab緩存,其中之一提供適合DMA訪問(wèn)的內(nèi)存。通過(guò)cat /proc/slabinfo中能夠看到,kmalloc-xxx和dma-kmalloc-xxx就是這一部分提供的。
kmalloc定義在,該函數(shù)首先檢查所需的緩存是否用常數(shù)來(lái)指定,如果是這種情況,所需的緩存可以在編譯時(shí)靜態(tài)確定,這可以提高速度(內(nèi)核的優(yōu)化真是無(wú)所不用其極!?。?。否則,該函數(shù)調(diào)用__kmalloc查找長(zhǎng)度匹配的緩存,后者是__do_kmalloc的前端,提供參數(shù)轉(zhuǎn)換功能。
mm/slab.c
void *__do_kmalloc(size_t size, gfp_t flags)
{
kmem_cache_t *cachep;
cachep = __find_general_cachep(size, flags);
if (unlikely(ZERO_OR_NULL_PTR(cachep)))
return NULL;
return __cache_alloc(cachep, flags);
}
__find_general_cachep在上文提到的緩存中找到適當(dāng)?shù)囊粋€(gè),之后使用__cache_alloc函數(shù)完成最終的分配。
七、處理器高速緩存和TLB控制
在這里簡(jiǎn)單的總結(jié)一些Kernel中和TLB/高速緩存相關(guān)的函數(shù),具體TLB的實(shí)現(xiàn)機(jī)制與體系架構(gòu)相關(guān)性很大,就不詳細(xì)總結(jié)了。
flush_tlb_all和flush_cache_all刷出整個(gè)TLB/高速緩存。此操作只在操縱內(nèi)核頁(yè)表時(shí)需要,因?yàn)榇祟?lèi)修改不僅影響所有進(jìn)程,而且影響系統(tǒng)中的所有處理器。
flush_tlb_mm(struct mm_struct *mm)和flush_cache_mm刷出所有屬于地址空間mm的TLB/高速緩存項(xiàng)。
flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end)和flush_cache_range(vma, start, end)刷出地址范圍vma->vm_mm中虛擬地址start和end之間的所有TLB/高速緩存項(xiàng)。
flush_tlb_page(struct vm_area_struct *vma, unsigned long page) 和flush_cache_page(vma, page)刷出虛擬地址在[page, page + PAGE_SIZE]范圍內(nèi)所有的TLB/高速緩存項(xiàng)。
update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte)在處理頁(yè)失效之后調(diào)用。它在處理器的內(nèi)存管理單元MMU中加入信息,是的虛擬地址address由頁(yè)表項(xiàng)pte描述。僅當(dāng)存在外部MMU時(shí)才需要該函數(shù),通常MMU集成在處理器內(nèi)部。
此外,flush_cache_和flush_tlb_函數(shù)常常成對(duì)出現(xiàn),例如,在使用fork進(jìn)程復(fù)制進(jìn)程的地址空間時(shí),則:1、刷出高速緩存,2、操作內(nèi)存,3、刷出TLB。這個(gè)順序很重要,因?yàn)?/p>
如果順序相反,那么在TLB刷出之后,正確信息提供之前,多處理器系統(tǒng)中的另一個(gè)CPU可能從進(jìn)程的頁(yè)表項(xiàng)取得錯(cuò)誤的信息。
在刷出高速緩存時(shí),某些體系結(jié)構(gòu)需要依賴(lài)TLB中的“虛擬->物理”轉(zhuǎn)換規(guī)則。flush_tlb_mm必須在flush_cache_mm之后執(zhí)行以確保這一點(diǎn)。
小結(jié)
這部分東西實(shí)在是太多,簡(jiǎn)單的總結(jié)一下就已經(jīng)這么多了。在這里對(duì)以上的內(nèi)容進(jìn)行一個(gè)簡(jiǎn)單的概括。
在內(nèi)核進(jìn)入正常運(yùn)行之后,內(nèi)存管理分為兩個(gè)層次:伙伴系統(tǒng)負(fù)責(zé)物理頁(yè)框的管理。在伙伴系統(tǒng)之上,所有的內(nèi)存管理都基于此,主要分為:slab分配器處理小塊內(nèi)存;vmalloc模塊為不連續(xù)物理頁(yè)框提供映射;永久映射區(qū)域和固定映射區(qū)域提供對(duì)高地址物理頁(yè)框的訪問(wèn)。
內(nèi)存管理的初始化很具有挑戰(zhàn)性,內(nèi)核通過(guò)引入一個(gè)非常簡(jiǎn)單的自舉內(nèi)存分配器(bootmem)解決了該問(wèn)題,該分配器在正式的分配機(jī)制(伙伴系統(tǒng))啟用后停用。
審核編輯:符乾江
-
Linux
+關(guān)注
關(guān)注
87文章
11407瀏覽量
212126 -
文件系統(tǒng)
+關(guān)注
關(guān)注
0文章
293瀏覽量
20197 -
內(nèi)存管理
+關(guān)注
關(guān)注
0文章
168瀏覽量
14399 -
虛擬內(nèi)存
+關(guān)注
關(guān)注
0文章
77瀏覽量
8176
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
Linux kernel內(nèi)存管理模塊結(jié)構(gòu)分析

走進(jìn)Linux內(nèi)存系統(tǒng)探尋內(nèi)存管理的機(jī)制和奧秘
關(guān)于Linux內(nèi)存管理的詳細(xì)介紹
Linux內(nèi)核的內(nèi)存管理詳解

Linux內(nèi)核內(nèi)存管理架構(gòu)解析

linux內(nèi)存管理
linux內(nèi)存管理機(jī)制淺析

你知道linux內(nèi)存管理基礎(chǔ)及方法?
適當(dāng)了解Linux內(nèi)存管理等問(wèn)題

評(píng)論