您現在的位置是:首頁 > 舞蹈首頁舞蹈

記憶體是怎麼對映到物理地址空間的?記憶體是連續分佈的嗎?

由 老狼zhihu 發表于 舞蹈2021-07-07
簡介舉個例子,如果我們把PCIe BAR(BEGREG)設為0x80000000,那麼儘管插了8G DIMM,4G以下也不會超過2G的記憶體可以使用,而2到8G的真實記憶體都被對映到在4G地址空間以上了,而這些是32位Windows使用不了的

開對映一定連續嗎

記憶體是怎麼對映到物理地址空間的?記憶體是連續分佈的嗎?

如果我們將兩個4G記憶體插入記憶體插槽,得到的記憶體地址空間是0到8G嗎?是不是0到4G是第一根記憶體,4到8G是第二根記憶體呢?實際情況相差甚遠,記憶體在物理地址空間的對映是分散的。一部分原因是4G以下有Memory map IO(mmio)空間和PCIe的配置空間,另一個原因是Interleaving會打撒記憶體地址到各個Channel、DIMM甚至是Rank和bank上。今天我們就一起來了解一下x86系統的地址空間分佈。

物理地址空間

一個典型的物理地址空間是這樣的:

記憶體是怎麼對映到物理地址空間的?記憶體是連續分佈的嗎?

其中只有灰色部分是真正的記憶體,其餘都是MMIO。而記憶體被分為High DRAM和Low DRAM,如圖:

記憶體是怎麼對映到物理地址空間的?記憶體是連續分佈的嗎?

為什麼要把記憶體強行分割成兩塊呢?因為歷史的包袱。最早記憶體都很小,32位的地址(4G)空間看起來永遠也用不完,低地址被分配給記憶體用,高地址就自然而然被分配用來給Memory map IO。既然已經分給它們了,為了相容以前的驅動,這一塊就被固定下來。再有記憶體就只能從4G以上分配了。

Low MMIO和High MMIO

Low MMIO結構如下圖:

記憶體是怎麼對映到物理地址空間的?記憶體是連續分佈的嗎?

其中有幾塊要特別說明一下:

1。Boot Vector的空間是BIOS內容對映的地址,它的大小是可以調節的,為了滿足不同大小的BIOS。

2。Local APIC是APIC中斷模式各個核心local APIC暫存器的對映地址。

3。PCI ECAM也有叫做PCIBAR,是PCIe配置地址空間的對映地址。它的起始地址可調,桌上型電腦BIOS一般會把它設定得很高,這樣4G以下記憶體會比較大,方便32位Windows使用。舉個例子,如果我們把PCIe BAR(BEGREG)設為0x80000000,那麼儘管插了8G DIMM,4G以下也不會超過2G的記憶體可以使用,而2到8G的真實記憶體都被對映到在4G地址空間以上了,而這些是32位Windows使用不了的。所以有的主機板執行32位作業系統發現可用記憶體小了一大塊就是這個原因。它的大小可以修改,一般可以設為64MB和128MB。

High MMIO被BIOS保留作為64位mmio分配之用,例如PCIe的64位BAR等。

Low DRAM和High DRAM

4G以下記憶體最高地址叫做BMBOUND,也有叫做Top of Low Usable DRAM (TOLUD) 。BIOS也並不是把這些都報告給作業系統,而是要在裡面劃分出一部分給核顯、ME和SMM等功能:

記憶體是怎麼對映到物理地址空間的?記憶體是連續分佈的嗎?

紅框中是在low DRAM被“偷”的部分

4G以上的記憶體最高階叫做Top of Up Usable DRAM (TOUUD) ,再上面就是High MMIO了。

1MB以下比較特殊,裡面全部都是已經被淘汰的傳統BIOS和DOS關心的內容,我們叫它DOS Space或者Legacy Region:

記憶體是怎麼對映到物理地址空間的?記憶體是連續分佈的嗎?

在那裡,我們習慣用傳統的真實模式地址來劃分它們的具體內容:

1。0~640KB,傳統DOS空間。

2。A段和B段,傳統SMM空間。VGA的MMIO也被對映到這裡,可以透過暫存器切換。

3。C段和D段,legacy opROM對映空間和EBDA空間。

4。E段和F段,BIOS空間的Lower和Upper對映地址。BIOS的rom內容也會被對映到這裡,方便Legacy BIOS真實模式跳轉到保護模式。

記憶體的Interleave

從前面可以看出記憶體在地址空間上被拆分成兩塊:Low DRAM和High DRAM。那麼在每塊地址空間上分配連續嗎?現代記憶體系統在引入多通道後,為了規避資料的區域性性(這也是Cache為什麼起作用的原因)對多通道效能的影響,BIOS基本預設全部開啟了Interleaving,過去美好的DIMM 0和DIMM 1挨個連續分配的日子一去不復返了。

什麼是Interleaving?簡單來說,就是讓記憶體交錯起來,如下面的動圖:

記憶體是怎麼對映到物理地址空間的?記憶體是連續分佈的嗎?

來自wikipedia, 參考資料1

這是一個bank層級的模4的interleaving。在桌面電腦上,常見的還有Channel級的、DIMM級的和Rank級的。

伺服器上Interleaving更是不可或缺,它的粒度更細,可以達到數十bytes層級的interleave,它和記憶體的其他特性,如類似磁碟陣列RAID的記憶體spare, mirror特性,構成了複雜異常的記憶體對映系統。在BIOS裡面,桌上型電腦/筆記本記憶體對映相對簡單,只有一個大表和數十個暫存器;而在伺服器BIOS中,有數個相互關聯的大表和暫存器陣列來解碼(decode)記憶體的請求,程式碼的硬體邏輯也是相當複雜。關於它,我會有一篇專欄文章討論地址譯碼和地址反向解碼,詳細內容那裡再說,這裡只需要知道,物理記憶體分佈在各個DIMM上就夠了。

物理地址到記憶體單元的反推

BIOS實際上一手導演的記憶體的分配,它當然可以從任何物理地址反推回記憶體的單元地址。我們可以用下面一組資料來唯一確定某個記憶體單元:

Channel #;DIMM #; Rank #;Bank #;Row #;Column #

在記憶體分配表缺失的情況下,BIOS甚至可以透過它填過的暫存器重建這個對映表。但實際上BIOS並不希望一般使用者知道這些資訊,因為有安全性問題。

暴露記憶體資訊容易招來記憶體側通道攻擊(Side Channel),比較有名的有Row hammer攻擊。簡單的來說它是透過反覆寫某個記憶體單元,藉助記憶體的特性,希望影響相鄰Row/Column的內容。詳細內容可以參考這裡:

記憶體不重新整理會怎樣?有趣的記憶體物理攻擊和旁路攻擊

有些情況確實需要知道這些資訊,就是記憶體出錯的時候。和大家想象的不同,記憶體是會出錯的。尤其雲伺服器中記憶體的出錯是十分頻繁的。出錯起來也千奇百怪,開始可能是偶爾的隨機錯誤,經過ECC等校正後,就再也不會復現;而有時是某個Bit總是出錯,進而慢慢的整個row、column或者相鄰的cell開始出錯,從可以糾正的錯誤變成不可修正的錯誤,導致伺服器必須停機。這時候就必須知道哪個記憶體壞了,進而換掉它。BIOS的報錯是透過WHEA:

報告給作業系統,但這個資訊裡面只有物理地址,如何才能知道是哪個記憶體單元壞了呢?在Linux上面可以透過edca(參考資料4),有程式設計經驗的同學可以透過edca的程式介面(參考資料3),可以得到更加豐富的資訊。

如何關掉Interleaving

對記憶體有特殊需求的朋友,如果希望記憶體連續,可以在BIOS裡面關閉所有的Interleaving來達成這個目標:

記憶體是怎麼對映到物理地址空間的?記憶體是連續分佈的嗎?

注意是所有的。之後可以透過SMBIOS來看到記憶體分佈資訊(dmidecode)。

結論

BIOS作為記憶體的大管家,也負責記憶體的分配和對映memory map。它會把這些資訊透過E820, GetMemoryMap函式和SMBIOS傳遞給作業系統。作業系統在此基礎上再建立頁表,產生虛擬地址。