內(nèi)存管理是Linux內(nèi)核最復(fù)雜的組件。內(nèi)存管理包括虛擬內(nèi)存機(jī)制和物理內(nèi)存管理。這篇說說物理內(nèi)存管理的一些要點(diǎn)。
物理內(nèi)存地址空間和虛擬內(nèi)存地址空間
說到虛擬內(nèi)存的時(shí)候我們知道虛擬內(nèi)存地址空間分為兩部分:內(nèi)核地址空間和用戶進(jìn)程地址空間。這兩個(gè)地址空間都使用虛擬地址,也就是說程序使用的都是虛擬地址。從虛擬地址映射到實(shí)際物理地址時(shí)有所區(qū)別:
1. 內(nèi)核使用物理內(nèi)存時(shí)可以直接通過虛擬地址和內(nèi)核地址空間的起始值的偏移量來計(jì)算得到實(shí)際物理內(nèi)存的地址
2. 進(jìn)程使用物理內(nèi)存必須通過頁表結(jié)構(gòu)進(jìn)行轉(zhuǎn)換
對(duì)應(yīng)內(nèi)核地址空間來說,
1. 如果內(nèi)核虛擬地址空間 > 物理內(nèi)存地址空間時(shí), 那么物理內(nèi)存地址空間可以全部映射到內(nèi)核虛擬地址空間。在目前64位機(jī)器的情況下基本都是這種情況,由于硬件的限制,可用的物理內(nèi)存遠(yuǎn)小于可用的內(nèi)核虛擬地址空間
2. 如果內(nèi)核虛擬地址空間 < 物理內(nèi)存地址空間時(shí),物理內(nèi)存地址空間的一部分映射到內(nèi)核虛擬地址空間,剩余的物理內(nèi)存地址空間被稱為高端內(nèi)存(high memory),內(nèi)核會(huì)采取額外的映射機(jī)制來訪問這些高端內(nèi)存。這種情況在之前的32位機(jī)器上是常態(tài),在32位機(jī)器下,內(nèi)核地址空間和用戶進(jìn)程地址空間的比例默認(rèn)為1:3,也就是說4G的虛擬地址空間,內(nèi)核地址空間占1G,而可用的物理內(nèi)存可達(dá)到4G,內(nèi)核地址空間還必須預(yù)留一部分作為內(nèi)核運(yùn)行使用,所以可用的內(nèi)核地址空間對(duì)物理內(nèi)存地址空間的映射為896MB,剩余的物理內(nèi)存地址空間都是高端內(nèi)存
對(duì)于64位機(jī)器來說,虛擬內(nèi)存地址空間遠(yuǎn)大于可用的物理內(nèi)存地址空間,所以虛擬內(nèi)存地址空間劃分成內(nèi)核地址空間和用戶進(jìn)程地址空間時(shí)就比32位機(jī)器的地址空間富裕很多,整個(gè)64位虛擬地址空間分為上下半部和中間禁用區(qū)三部分,可以看到虛擬地址的第0到46位都是任意設(shè)置的,而47位到63位對(duì)于內(nèi)核地址空間來說都是0,對(duì)于用戶進(jìn)程地址空間都是1。這種虛擬地址稱為規(guī)范的地址,其他的都是不規(guī)范的地址。所以在64位機(jī)器來說內(nèi)核虛擬地址空間和用戶進(jìn)程虛擬地址空間都是2的47次。
下圖是更細(xì)節(jié)的描述,內(nèi)核的虛擬內(nèi)存地址空間的起始部分是對(duì)物理內(nèi)次地址空間的一致性映射,然后是vmalloc區(qū)域,然后是給內(nèi)核使用的其他內(nèi)存區(qū)域
之前說虛擬內(nèi)存的時(shí)候已經(jīng)說了頁表的結(jié)構(gòu),再來看一下頁表和一個(gè)虛擬地址的映射關(guān)系
1. 對(duì)于64位機(jī)器,4KB的頁大小來說,Offset表示該地址在頁內(nèi)的偏移量,所以O(shè)ffset 的長度=12, 2的12次方正好是4KB
2. PGD,PUD,PMD,PTE各位的長度都是9,也就是每級(jí)頁表的頁表項(xiàng)都是512個(gè)
3. 這樣總共64位的虛擬地址長度使用了48位,已經(jīng)遠(yuǎn)大于目前可用的物理內(nèi)存地址空間
而頁表結(jié)構(gòu)如下,每級(jí)頁表實(shí)際就是一個(gè)一個(gè)的數(shù)組,數(shù)組項(xiàng)的長度是64位的,上級(jí)頁表的頁表項(xiàng)存放著下級(jí)頁表的物理內(nèi)存地址,而最后的PTE頁表項(xiàng)存放的就是實(shí)際物理內(nèi)存頁的頁號(hào)。有了物理內(nèi)存頁的頁號(hào)我們就可以找到對(duì)應(yīng)物理內(nèi)存頁。物理內(nèi)存地址空間就是按照物理頁長度劃分的一個(gè)數(shù)組。PTE頁表項(xiàng)既然存放的是物理內(nèi)存頁號(hào),那么肯定不需要64位長度,剩下的位可以用作標(biāo)志位來提供額外的功能,比如讀寫執(zhí)行等權(quán)限控制,是否是臟頁,是否被交換到了交換區(qū)等等。
物理內(nèi)存的頁組織成了唯一一個(gè)數(shù)組,就是我們上面說的mem_map結(jié)構(gòu),而虛擬內(nèi)存的頁卻采用了頁表結(jié)構(gòu),組織成了多級(jí)數(shù)組結(jié)構(gòu),這樣的目的主要就是減少頁表的長度。虛擬內(nèi)存當(dāng)然也可以和物理內(nèi)存一樣只使用一個(gè)唯一的數(shù)組,但是虛擬內(nèi)存地址空間有2的64次方這么大,如果使用固定長度的數(shù)組來表示,那么有大量的數(shù)組項(xiàng)是空的,因?yàn)槲锢韮?nèi)存沒這么大。每個(gè)進(jìn)程要維護(hù)自己的頁表,這樣虛擬內(nèi)存也使用一個(gè)數(shù)組來表示,就造成了極大的內(nèi)存浪費(fèi),因?yàn)榇鎯?chǔ)頁表是需要物理內(nèi)存的。
采用了頁表這樣的多級(jí)數(shù)組結(jié)構(gòu)后,可以按需分配數(shù)組,使用多少物理內(nèi)存就分配多少虛擬地址空間的頁表項(xiàng),這樣極大地壓縮了頁表長度,節(jié)省了物理內(nèi)存。
關(guān)于頁表,需要記住的是用戶進(jìn)程的虛擬地址是和這個(gè)地址對(duì)應(yīng)的頁表數(shù)組索引可以互相轉(zhuǎn)化計(jì)算。
物理內(nèi)存數(shù)據(jù)結(jié)構(gòu)
Linux要支持NUMA架構(gòu)和非NUMA架構(gòu)等多種硬件體系架構(gòu),在NUMA架構(gòu)下,每個(gè)CPU獨(dú)享一個(gè)本地內(nèi)存,在非NUMA架構(gòu)比如SMP架構(gòu)下,多個(gè)CPU共享一個(gè)物理內(nèi)存,Linux對(duì)物理內(nèi)存的管理必須要適用這多種架構(gòu)。
所以Linux對(duì)物理內(nèi)存的管理分為幾個(gè)層次
1. 最頂層是結(jié)點(diǎn),在內(nèi)核中是pg_data_t結(jié)構(gòu)的實(shí)例
2. 每個(gè)結(jié)點(diǎn)又最多分為3個(gè)內(nèi)存域,比如上面說的不可以直接映射的高端內(nèi)存,可以直接映射的普通內(nèi)存,已經(jīng)給DMA用的內(nèi)存域
3. 每個(gè)內(nèi)存域都關(guān)聯(lián)了一個(gè)mem_map結(jié)構(gòu)的數(shù)組,數(shù)組項(xiàng)都是page實(shí)例。page結(jié)構(gòu)表示物理內(nèi)存頁幀,是最重要的物理內(nèi)存的表示結(jié)構(gòu)。相當(dāng)于把整個(gè)內(nèi)存域的物理內(nèi)存地址空間按頁大小(4KB-2MB)劃分成一個(gè)數(shù)組,這個(gè)數(shù)組就是mem_map,數(shù)組項(xiàng)就是page。得到了page結(jié)構(gòu),就得到了物理內(nèi)存頁幀的位置,物理內(nèi)存頁幀的狀態(tài)。
page的結(jié)構(gòu)如下,在計(jì)算機(jī)底層知識(shí)拾遺(六)理解頁緩存page cache和地址空間address_space 這篇中說到地址空間address_space時(shí)也說到了page的結(jié)構(gòu)
1. flags表示該物理內(nèi)存頁幀的狀態(tài)
2. _count表示該頁被引用的計(jì)數(shù)器
3. private指針當(dāng)在PagePrivate標(biāo)志下,用來指向該頁緩存對(duì)應(yīng)的底層buffer cache的buffer_head鏈表指針,當(dāng)設(shè)置了PageSwapCache的時(shí)候,表示這個(gè)頁對(duì)應(yīng)的頁交換區(qū)的位置信息
4. mapping指針當(dāng)作為文件的頁緩存時(shí),指向該文件inode對(duì)應(yīng)的address_space地址空間。如果頁是匿名內(nèi)存,即沒有后備文件,那么指向anon_vma匿名區(qū)域?qū)ο?/p>
5. virtual 指向這個(gè)物理內(nèi)存地址對(duì)應(yīng)的虛擬內(nèi)存地址
物理內(nèi)存分配
Linux內(nèi)核在機(jī)器啟動(dòng)時(shí)被加載到物理內(nèi)存,內(nèi)核鏡像在物理內(nèi)存的存儲(chǔ)結(jié)構(gòu)如下所示
1. 第一個(gè)物理頁幀4KB的空間是預(yù)留給BIOS使用的
2. 接下來的640KB區(qū)域預(yù)留未使用,原因是緊鄰該區(qū)域的后面一塊空間給加載ROM使用,ROM是不可寫的,所以如果這640KB給內(nèi)核使用,必須保證內(nèi)核小于640KB
3. Linux內(nèi)核實(shí)際從1MB之后的連續(xù)內(nèi)存開始,依次是內(nèi)核代碼,內(nèi)核數(shù)據(jù)等
可以通過查看/proc/iomem來查看物理內(nèi)存實(shí)際的分配情況,可以看到,內(nèi)核代碼是用第1MB物理地址開始的
當(dāng)內(nèi)核初始化完成之后,對(duì)物理內(nèi)存的管理由伙伴系統(tǒng)承擔(dān),下面看看伙伴系統(tǒng)的基本原理。
上面說了每個(gè)結(jié)點(diǎn)的內(nèi)存分為3個(gè)域,域采用zone數(shù)據(jù)結(jié)構(gòu),包含一個(gè)free_area數(shù)組。數(shù)組的下標(biāo)是階order。階是伙伴系統(tǒng)的一個(gè)重要術(shù)語,描述了內(nèi)存分配的數(shù)量單元。內(nèi)存塊的長度是2^order,階的范圍是0-MAX_ORDER。MAX_ORDER默認(rèn)是11,也就是說一次分配可以請(qǐng)求的最大頁數(shù)是2^11 = 2048個(gè)頁
free_area結(jié)構(gòu)包含了一個(gè)free_list鏈表。free_list是用于連接空閑頁的頁鏈表,頁鏈表包含大小相同的連續(xù)內(nèi)存區(qū)。比如free_area[0]表示0階的空閑區(qū)域,它的頁鏈表包含的都是連續(xù)的空閑單頁。free_area[1]表示1階的空閑區(qū)域,它的頁鏈表包含的是連續(xù)的空閑雙頁
free_area數(shù)組和free_list鏈表的組成如下圖所示。 free_list里面大小相同的單元叫做伙伴,伙伴之間不需要連續(xù),采用鏈表相聯(lián)。
系統(tǒng)當(dāng)前伙伴系統(tǒng)信息可以通過cat /proc/buddyinfo查看
伙伴系統(tǒng)的特點(diǎn)是簡單高效,只使用了雙鏈表結(jié)構(gòu),它可以高效的分配連續(xù)的內(nèi)存區(qū)域。對(duì)于用戶進(jìn)程來說,物理內(nèi)存碎片的問題影響還不大,因?yàn)橛脩暨M(jìn)程利用頁表來訪問物理內(nèi)存,只要虛擬地址是連續(xù)的即可,vmalloc可以支持非連續(xù)的物理內(nèi)存。但是對(duì)于內(nèi)核來說,它直接映射了物理內(nèi)存地址空間,如果物理內(nèi)存碎片多,那么影響內(nèi)核的內(nèi)存分配。比如下面這個(gè)結(jié)構(gòu),雖然只有4頁被分配了,但是對(duì)伙伴系統(tǒng)來說,它能分配的最大連續(xù)頁只能是8頁,因?yàn)榉峙涞捻摂?shù)都是2的冪,雖然有連續(xù)的15個(gè)頁,但是伙伴系統(tǒng)最大只能分配8頁
為了解決內(nèi)存碎片的問題,內(nèi)核采用了反碎片的設(shè)計(jì),試圖從最初開始盡可能防止碎片。
內(nèi)核將已分配的頁分為三種類型
1. 不可移動(dòng)頁 Unmovable。 內(nèi)核中的已分配頁基本屬于這個(gè)類型,不能移動(dòng)位置
2. 可回收頁 Reclaimable。 不能直接移動(dòng),但是可以刪除,其內(nèi)容可以重新再生,比如映射到文件的內(nèi)存,可以重新讀取文件內(nèi)容來加載頁
3. 可移動(dòng)頁 Movable,用戶空間的以分配頁基本屬于這個(gè)類型,因?yàn)橛脩艨臻g利用頁表訪問物理內(nèi)存,可以通過復(fù)制頁到新的位置,然后更新頁表項(xiàng)來實(shí)現(xiàn)
反碎片的設(shè)計(jì)就是將相同移動(dòng)性的頁分到一組。我們可以看到free_area里面的free_list鏈表實(shí)際是按照移動(dòng)性MIGRATE_TYPES分組的。
需要注意的是伙伴系統(tǒng)是內(nèi)核用來分配大塊內(nèi)存的,它只能分配2的冪的頁。slab分配器是內(nèi)核用來分配細(xì)粒度的內(nèi)存分配器。它們于C語言庫的malloc這種可以按照字節(jié)大小來分配內(nèi)存的分配器不同。伙伴系統(tǒng)分配器和slab分配器是最底層的內(nèi)核級(jí)內(nèi)存分配器,其他語言機(jī)的內(nèi)存分配器都是基于它們來工作的
伙伴系統(tǒng)分配器的幾個(gè)API:
alloc_pages(mask, orders)分配2^order頁的物理內(nèi)存并返回一個(gè)頁page結(jié)構(gòu),作為分配的內(nèi)存的起始頁
get_zeroed_page(mask)分配一頁并返回page實(shí)例,頁對(duì)應(yīng)的內(nèi)存全部填充為0
__get_free_page(mask, order)返回分配內(nèi)存塊的虛擬地址,而不是頁page實(shí)例。
free_page(struct page*)用于將page實(shí)例對(duì)應(yīng)的物理內(nèi)存頁返回給內(nèi)存管理子系統(tǒng),參數(shù)是page實(shí)例
__free_page(address)也是釋放內(nèi)存給內(nèi)存管理子系統(tǒng),區(qū)別是傳遞參數(shù)是虛擬地址
關(guān)于內(nèi)存分配系統(tǒng),需要注意的是 所有的內(nèi)存分配系統(tǒng)都是在初始化時(shí)預(yù)先獲得了一塊物理內(nèi)存,在這個(gè)物理內(nèi)存區(qū)域內(nèi)再進(jìn)行分配,釋放內(nèi)存實(shí)際上就是把分配的內(nèi)存返還給內(nèi)存分配器。
1. 比如內(nèi)核的伙伴系統(tǒng)分配器,內(nèi)核在初始化時(shí)會(huì)語言確定各個(gè)內(nèi)存域的上下邊界,然后初始化各個(gè)內(nèi)存域的各種數(shù)據(jù)結(jié)構(gòu),比如page實(shí)例,然后把這個(gè)內(nèi)存域交給伙伴系統(tǒng)分配器。
2. 比如Java的內(nèi)存管理,也是在Java進(jìn)程啟動(dòng)的時(shí)候指定了各個(gè)內(nèi)存區(qū)域的大小,確定邊界,然后在Java進(jìn)程啟動(dòng)時(shí)就預(yù)先向內(nèi)核申請(qǐng)了Java進(jìn)程管理的大部分內(nèi)存。Java的內(nèi)存分配和垃圾回收都是在這塊預(yù)先分配的內(nèi)存區(qū)域進(jìn)行的。
vmalloc
內(nèi)核使用vmalloc在虛擬內(nèi)存地址空間分配連續(xù)的虛擬內(nèi)存地址,這些連續(xù)的虛擬內(nèi)存地址可以映射到不連續(xù)的物理內(nèi)存頁,從而利用內(nèi)存碎片。內(nèi)核的虛擬內(nèi)存地址空間有一塊專門的空間是給vmalloc使用的,每個(gè)vmalloc區(qū)域之后用空洞隔開,防止錯(cuò)誤的虛擬地址引用。
每個(gè)用vmalloc函數(shù)創(chuàng)建的vmalloc區(qū)域?qū)ο蠖紝?duì)應(yīng)內(nèi)核的一個(gè)vm_struct結(jié)構(gòu)體
1. addr表示這個(gè)vmalloc區(qū)域的起始虛擬地址,size表示這個(gè)vmalloc區(qū)域的長度,這兩個(gè)參數(shù)就確定了一個(gè)vmalloc區(qū)域的邊界
2. flags標(biāo)志位表示了一組vmalloc區(qū)域的標(biāo)志
3. pages是一個(gè)page數(shù)組,表示這個(gè)vmalloc區(qū)域?qū)?yīng)的實(shí)際物理內(nèi)存地址,可以是不連續(xù)的。nr_pages表示這個(gè)pages數(shù)組的長度
4. next指針指向下一個(gè)vmalloc區(qū)域?qū)ο?,所有的vmalloc區(qū)域?qū)ο蠼M成一個(gè)單鏈表結(jié)構(gòu)
vmalloc區(qū)域?qū)ο蠛臀锢韮?nèi)存的映射關(guān)系如下
slab分配器
伙伴系統(tǒng)分配器用來分配以頁為大小的大內(nèi)存空間,內(nèi)核同樣需要按字節(jié)大小等細(xì)粒度分配內(nèi)存空間的分配器,slab分配器就是這樣一個(gè)分配器
1. 分配細(xì)粒度的內(nèi)存空間,是內(nèi)核的kmallocAPI的底層實(shí)現(xiàn)。slab分配器對(duì)計(jì)算機(jī)高速緩存影響小,原因是不會(huì)每次都分配頁,減少頁表的操作。而伙伴系統(tǒng)分配器每次都要更新頁表,而更新頁表會(huì)影響高速緩存和TLB單元
2. 用作內(nèi)核的緩存,緩存內(nèi)核創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)的對(duì)象實(shí)例
slab分配器和伙伴系統(tǒng)的關(guān)系
聯(lián)系客服