如果我們將兩個4G內(nèi)存插入內(nèi)存插槽,得到的內(nèi)存地址空間是0到8G嗎?是不是0到4G是第一根內(nèi)存,4到8G是第二根內(nèi)存呢?實際情況相差甚遠(yuǎn),內(nèi)存在物理地址空間的映射是分散的。一部分原因是4G以下有Memory map IO(mmio)空間和PCIe的配置空間,另一個原因是Interleaving會打撒內(nèi)存地址到各個Channel、DIMM甚至是Rank和bank上。今天我們就一起來了解一下x86系統(tǒng)的地址空間分布。
一個典型的物理地址空間是這樣的:
其中只有灰色部分是真正的內(nèi)存,其余都是MMIO。而內(nèi)存被分為High DRAM和Low DRAM,如圖:
為什么要把內(nèi)存強(qiáng)行分割成兩塊呢?因為歷史的包袱。最早內(nèi)存都很小,32位的地址(4G)空間看起來永遠(yuǎn)也用不完,低地址被分配給內(nèi)存用,高地址就自然而然被分配用來給Memory map IO。既然已經(jīng)分給它們了,為了兼容以前的驅(qū)動,這一塊就被固定下來。再有內(nèi)存就只能從4G以上分配了。
Low MMIO結(jié)構(gòu)如下圖:
其中有幾塊要特別說明一下:
1.Boot Vector的空間是BIOS內(nèi)容映射的地址,它的大小是可以調(diào)節(jié)的,為了滿足不同大小的BIOS。
2.Local APIC是APIC中斷模式各個內(nèi)核local APIC寄存器的映射地址。
3.PCI ECAM也有叫做PCIBAR,是PCIe配置地址空間的映射地址。它的起始地址可調(diào),臺式機(jī)BIOS一般會把它設(shè)置得很高,這樣4G以下內(nèi)存會比較大,方便32位Windows使用。舉個例子,如果我們把PCIe BAR(BEGREG)設(shè)為0x80000000,那么盡管插了8G DIMM,4G以下也不會超過2G的內(nèi)存可以使用,而2到8G的真實內(nèi)存都被映射到在4G地址空間以上了,而這些是32位Windows使用不了的。所以有的主板運行32位操作系統(tǒng)發(fā)現(xiàn)可用內(nèi)存小了一大塊就是這個原因。它的大小可以修改,一般可以設(shè)為64MB和128MB。
High MMIO被BIOS保留作為64位mmio分配之用,例如PCIe的64位BAR等。
4G以下內(nèi)存最高地址叫做BMBOUND,也有叫做Top of Low Usable DRAM (TOLUD) 。BIOS也并不是把這些都報告給操作系統(tǒng),而是要在里面劃分出一部分給核顯、ME和SMM等功能:
紅框中是在low DRAM被“偷”的部分
4G以上的內(nèi)存最高端叫做Top of Up Usable DRAM (TOUUD) ,再上面就是High MMIO了。
1MB以下比較特殊,里面全部都是已經(jīng)被淘汰的傳統(tǒng)BIOS和DOS關(guān)心的內(nèi)容,我們叫它DOS Space或者Legacy Region:
在那里,我們習(xí)慣用傳統(tǒng)的實模式地址來劃分它們的具體內(nèi)容:
1.0~640KB,傳統(tǒng)DOS空間。
2.A段和B段,傳統(tǒng)SMM空間。VGA的MMIO也被映射到這里,可以通過寄存器切換。
3.C段和D段,legacy opROM映射空間和EBDA空間。
4.E段和F段,BIOS空間的Lower和Upper映射地址。BIOS的rom內(nèi)容也會被映射到這里,方便Legacy BIOS實模式跳轉(zhuǎn)到保護(hù)模式。
從前面可以看出內(nèi)存在地址空間上被拆分成兩塊:Low DRAM和High DRAM。那么在每塊地址空間上分配連續(xù)嗎?現(xiàn)代內(nèi)存系統(tǒng)在引入多通道后,為了規(guī)避數(shù)據(jù)的局部性(這也是Cache為什么起作用的原因)對多通道性能的影響,BIOS基本缺省全部開啟了Interleaving,過去美好的DIMM 0和DIMM 1挨個連續(xù)分配的日子一去不復(fù)返了。
什么是Interleaving?簡單來說,就是讓內(nèi)存交錯起來,如下面的動圖:
來自wikipedia, 參考資料1
這是一個bank層級的模4的interleaving。在桌面電腦上,常見的還有Channel級的、DIMM級的和Rank級的。
服務(wù)器上Interleaving更是不可或缺,它的粒度更細(xì),可以達(dá)到數(shù)十bytes層級的interleave,它和內(nèi)存的其他特性,如類似磁盤陣列RAID的內(nèi)存spare, mirror特性,構(gòu)成了復(fù)雜異常的內(nèi)存映射系統(tǒng)。在BIOS里面,臺式機(jī)/筆記本內(nèi)存映射相對簡單,只有一個大表和數(shù)十個寄存器;而在服務(wù)器BIOS中,有數(shù)個相互關(guān)聯(lián)的大表和寄存器陣列來解碼(decode)內(nèi)存的請求,代碼的硬件邏輯也是相當(dāng)復(fù)雜。關(guān)于它,我會有一篇專欄文章討論地址譯碼和地址反向解碼,詳細(xì)內(nèi)容那里再說,這里只需要知道,物理內(nèi)存分布在各個DIMM上就夠了。
BIOS實際上一手導(dǎo)演的內(nèi)存的分配,它當(dāng)然可以從任何物理地址反推回內(nèi)存的單元地址。我們可以用下面一組數(shù)據(jù)來唯一確定某個內(nèi)存單元:
Channel #;DIMM #; Rank #;Bank #;Row #;Column #
在內(nèi)存分配表缺失的情況下,BIOS甚至可以通過它填過的寄存器重建這個映射表。但實際上BIOS并不希望一般用戶知道這些信息,因為有安全性問題。
暴露內(nèi)存信息容易招來內(nèi)存?zhèn)刃诺拦簦⊿ide Channel),比較有名的有Row hammer攻擊。簡單的來說它是通過反復(fù)寫某個內(nèi)存單元,借助內(nèi)存的特性,希望影響相鄰Row/Column的內(nèi)容。詳細(xì)內(nèi)容可以參考這里:
內(nèi)存不刷新會怎樣?有趣的內(nèi)存物理攻擊和旁路攻擊
有些情況確實需要知道這些信息,就是內(nèi)存出錯的時候。和大家想象的不同,內(nèi)存是會出錯的。尤其云服務(wù)器中內(nèi)存的出錯是十分頻繁的。出錯起來也千奇百怪,開始可能是偶爾的隨機(jī)錯誤,經(jīng)過ECC等校正后,就再也不會復(fù)現(xiàn);而有時是某個Bit總是出錯,進(jìn)而慢慢的整個row、column或者相鄰的cell開始出錯,從可以糾正的錯誤變成不可修正的錯誤,導(dǎo)致服務(wù)器必須停機(jī)。這時候就必須知道哪個內(nèi)存壞了,進(jìn)而換掉它。BIOS的報錯是通過WHEA:
報告給操作系統(tǒng),但這個信息里面只有物理地址,如何才能知道是哪個內(nèi)存單元壞了呢?在Linux上面可以通過edca(參考資料4),有編程經(jīng)驗的同學(xué)可以通過edca的程序接口(參考資料3),可以得到更加豐富的信息。
對內(nèi)存有特殊需求的朋友,如果希望內(nèi)存連續(xù),可以在BIOS里面關(guān)閉所有的Interleaving來達(dá)成這個目標(biāo):
注意是所有的。之后可以通過SMBIOS來看到內(nèi)存分布信息(dmidecode)。
BIOS作為內(nèi)存的大管家,也負(fù)責(zé)內(nèi)存的分配和映射memory map。它會把這些信息通過E820, GetMemoryMap函數(shù)和SMBIOS傳遞給操作系統(tǒng)。操作系統(tǒng)在此基礎(chǔ)上再建立頁表,產(chǎn)生虛擬地址。