從 MMU 看記憶體管理

程式設計師cxuan發表於2022-02-17

在計算機早期的時候,計算機是無法將大於記憶體大小的應用裝入記憶體的,因為計算機讀寫應用資料是直接通過匯流排來對記憶體進行直接操作的,對於寫操作來說,計算機會直接將地址寫入記憶體;對於讀操作來說,計算機會直接讀取記憶體的資料。

但是隨著軟體的不斷膨脹和移動應用的到來,一切慢慢變了。

我們想要手機既能夠執行微信,同時又能夠執行 QQ 音樂,還希望能夠聊微博、刷知乎以及看股票。如果我們的手機記憶體只有 1G,那麼顯然是無法滿足這些應用的,因為微信的後臺程式都佔用 1G 多記憶體了。那麼就會有人說,把記憶體容量提高不就行了嗎?這句話看似沒錯,但是記憶體的造價成本非常昂貴,而且記憶體的容量是永遠追不上應用程式所佔用的容量的,把所有應用程式直接堆入記憶體也只是飲鴆止渴,因為即使是多核執行緒持續不斷的進行計算,也會存在效能瓶頸。

客戶都是難搞的,我們並不希望正在使用微信,而後臺卻執行一些其他沒有必要的應用,比如說旅遊軟體、郵箱等。所以站在使用者的角度上來說,執行緒切換到這些應用上是完全沒有必要的。

基於上述這些考量,工程師們認為一直擴大記憶體容量並不能解決根本問題。

就像是軟體架構一樣,盲目追求高效能並不一定可取,需要同時兼顧 CAP 的各個原則。

後來,提出了一種虛擬記憶體(virtual memory)思想,虛擬記憶體是作業系統的一種抽象,虛擬記憶體的主要思想是:每個程式都有自己的地址空間,程式間的地址空間彼此不可見,我們的程式首先要先執行在虛擬記憶體中,虛擬記憶體會分為多個塊,每個塊稱為一頁或者頁面(page),每一頁有連續的地址範圍,這些頁會被對映到實體記憶體,當程式引用到一部分在實體記憶體的地址空間時,會由硬體完成對應的對映過程,當程式引用到不在實體記憶體的地址空間時,由作業系統負責將缺失(不在記憶體)的部分裝入實體記憶體並重新執行。

image-20220123100122857

虛擬記憶體系統是一種管理和分配程式虛擬記憶體的程式,在使用虛擬記憶體的情況下,虛擬地址不會直接對映到實體記憶體中,而是會直接輸送到記憶體管理單元(Memory Management Unit,MMU),由 MMU 將虛擬地址對映為實體記憶體地址。執行這個對映過程的技術就叫做分頁(paging)

MMU 是一塊單獨的晶片,它可以獨立存在,不過現代 OS 都把它內建在了 CPU 中。

image-20220123195659698

但是,對映到實體記憶體的哪個位置?對映的規則又是什麼?

實際上,實體記憶體的劃分其實和虛擬記憶體是一樣的,在實體記憶體中,也會被劃分為一個個的頁框(page frame),頁框是記憶體所劃分的儲存單元,與虛擬記憶體中的頁所不同的是,頁框是主存中的物理屬性,而頁面只是一種虛擬的抽象表示。頁面和頁框的儲存大小通常是一樣的,一般是 4KB。

image-20220125142831422

通過 MMU 的幫助,可以有效的將虛擬地址對映到實體記憶體地址,這也就是說,頁面可以和頁框進行合理的對映。但是這並沒有解決虛擬地址空間比實體記憶體空間大的這個問題。當 CPU 進行資料寫入時,MMU 會判斷資料寫入的頁面是否已經對映到頁框中,如果沒有對映的話,CPU 會陷入(trap)到作業系統,這個陷入的過程稱為缺頁中斷或者叫做缺頁錯誤(page fault)

之後,作業系統會找到一個空閒的頁框並將它的內容寫入到頁框中(這個頁框其實是在磁碟中,因為作業系統不會一次性把所有的頁框都載入到記憶體中,而是按需加入),然後修改頁框和頁面的對映關係,再重新啟動 trap 陷入的指令。

上面說到,MMU 會判斷資料寫入的頁面是否已經對映到了頁框中,這個是如何判斷的呢?

實際上,MMU 中有一個叫做頁表(page table)的結構,這個頁表就記錄了頁面和頁框的對映關係,上面所聊到修改頁框和頁面的對映關係,實際上就是修改頁表中的一個在/不在位的資料項,關於頁表的結構,我們後面回說。

下面來看一下頁面和頁框的對映關係圖。

image-20220126162628995

根據頁面中的頁表索引可以找到對應的頁表號,根據頁表中的頁框索引可以找到頁框號,這種對映關係就很類似計算機網路中的路由表(記錄各個資料轉發路徑)。從數學的角度來講,可以把頁表看做是一個函式,它的引數是頁表索引(通常也叫做虛擬頁號),結果是頁框號,通過這個函式可以將虛擬地址中的虛擬頁面轉換成頁框號,然後形成實體地址。

這裡強調一點:如果在/不在位是 0 的話,說明該頁面和頁框沒有存在對映關係,此時就會直接引起作業系統陷入,由作業系統找到對應的頁框執行寫入,寫入完成後修改頁面和頁框的對映關係,然後回到引起陷入的位置繼續執行。

上面提到了頁表是記錄頁面和頁框對映關係的一個結構,下面我們就來聊一下頁表項的結構。不同機器的頁表結構是不一樣的,但是大多數頁表項都具有下面這個結構。

image-20220127163637596

對於頁表項這個結構來說,最重要的就是頁框號,頁框號相當於是頁表的身份證,這就跟我們的身份證一樣,所以非常重要。

  • 在/不在位我們上面聊過了,這個位就是判斷頁面和頁框有沒有進行對映的一項。
  • 保護位指出在這個頁面上允許什麼樣的型別訪問,是讀還是寫,一般讀是 1,寫是 0 ,這是一位的形式,不過還有另外一種形式,是三位,分別對應讀、寫、執行。
  • 修改位 用於判斷這個頁面是否被修改過,在對頁面進行寫入時會自動設定修改位,如果一個頁面已經被修改過,那麼必須將它寫會磁碟,我們可以認為這個頁面是一個髒頁。
  • 無論讀寫,都會設定訪問位,訪問位的意義用來讓作業系統淘汰頁面所用,沒有訪問過的頁面要比多次訪問的頁面更容易被淘汰,訪問位在頁面置換演算法中非常有用。
  • 快取位用於判斷是否禁用快取記憶體,有的時候 CPU 需要讀取最新的使用者輸入,而不是從快取記憶體中讀取已有的資料。

聊完頁表之後,我們來回顧一下頁面到頁框的對映過程(上面有寫,這裡不再闡述了)

img

思考過後,你會發現,頁面到頁框的對映過程比較繁瑣,又是檢索頁面、又是作業系統切換、又是記憶體和磁碟的互動,想必這部分的效能容易出現問題,所以提升這部分的效能就成為了優先順序比較高的一個課題。

我們知道,這個對映過程主要是在 MMU 中完成的,所以能不能加快虛擬地址到實體地址的對映速度呢?,而且很多作業系統的虛擬地址空間往往很大,所以需要的頁表也會越來越大,那麼如何處理日益劇增的頁表呢?

首先我們要聊一下的就是如何加快虛擬地址到實體地址的對映速度,加快對映速度有兩種方式,一種是使用硬體進行加速,一種是使用軟體進行加速。

經過多年的研究表明,大多數程式會對少量的頁面進行多次訪問,而不會對大量的頁面進行多次訪問或者訪問次數大差不差,因此,只有極少數的頁表會被頻繁訪問,而大多數頁面卻照顧不到。由於這種區域性性原理,使用硬體的方式是設定一個轉換檢測緩衝區(TLB),它能夠直接將虛擬地址對映成為實體地址,而不必再訪問頁表。

TLB 又叫做快表或者相聯儲存器

TLB 可以放在 CPU 和 CPU 快取(CPU cache)之間,用於快取虛擬地址,TLB 可以放在 CPU 和 記憶體之間,用於快取實體地址,但是一般快取虛擬地址比較常見,由此可見,TLB 相當於是又多加了一個快取層。

虛擬頁面在 TLB 中

當一個資料的虛擬地址交由 MMU 進行轉換時,MMU 首先會將這個虛擬地址和 TLB 中快取的虛擬地址進行匹配,判斷虛擬地址所在的頁面是否被快取,如果已經快取,就會訪問 TLB(這裡有個前提就是判斷訪問位)將虛擬頁面對應的頁框號取出,而不必再訪問頁表。

image-20220131102144209

虛擬頁面不在 TLB 中

如果 MMU 沒有檢測到虛擬地址所在的頁面,就說明沒有查詢到匹配項,就會走頁表訪問,通過頁表查詢到實體地址後,會從 TLB 中淘汰一個頁面,用新找到的頁面進行替換。

image-20220131102530290

雖然內建一個硬體 TLB 能夠加快虛擬地址到實體地址的對映速度,不過現代 os 大多數都是使用軟體 TLB 來實現這個加速過程的,使用軟體 TLB 就意味著不再使用硬體,轉而擁抱作業系統。

這也就是說,當發生 TLB 失效時,不再是通過 MMU 到頁表中查詢,而是生成一個 TLB 失效並交給作業系統來解決。失效會有兩種發生的可能性:當要訪問的頁面在記憶體中時,這種失效叫做軟失效,軟失效比較好處理,找到該頁面然後直接更新 TLB 中對應的項即可。當要訪問的頁面不再記憶體中時,這種失效叫做硬失效,硬失效涉及磁碟訪問,頁面調入,硬失效的處理時間往往是軟失效的幾百萬倍。

上述都是理想情況下的失效,但實際情況下會存在既不是軟失效也不是硬失效的情況。當程式訪問一個非法地址時,根本不需要向 TLB 更新對映,此時 os 會直接報告段錯誤來終止程式,這種缺頁屬於程式錯誤;而軟失效通常稱為次要缺頁錯誤,硬失效稱為嚴重缺頁錯誤。

解決完第一個問題之後,我們再來看第二個問題,即如何處理日益劇增的頁表呢?

一般有兩種處理方式,使用多級頁表倒排頁表

第一種方案是使用多級頁表,下面是一個例子

image-20200303074356295

32 位的虛擬地址被劃分為 10 位的 PT1 域,10 位的 PT2 域,還有 12 位的 Offset 域。因為偏移量是 12 位,所以頁面大小是 4 KB,公有 2^20 次方個頁面。

引入多級頁表的原因是避免把全部頁表一直儲存在記憶體中。不需要的頁表就不應該保留

多級頁表是一種分頁方案,它由兩個或多個層次的分頁表組成,也稱為分層分頁。級別1(level 1)頁面表的條目是指向級別 2(level 2) 頁面表的指標,級別2頁面表的條目是指向級別 3(level 3) 頁面表的指標,依此類推。最後一級頁表儲存的是實際的資訊。

下面是一個二級頁表的工作過程

image-20200303172746157

在最左邊是頂級頁表,它有 1024 個表項,對應於 10 位的 PT1 域。當一個虛擬地址被送到 MMU 時,MMU 首先提取 PT1 域並把該值作為訪問頂級頁表的索引。因為整個 4 GB (即 32 位)虛擬地址已經按 4 KB 大小分塊,所以頂級頁表中的 1024 個表項的每一個都表示 4M 的塊地址範圍。

由索引頂級頁表得到的表項中含有二級頁表的地址或頁框號。頂級頁表的表項 0 指向程式正文的頁表,表項 1 指向含有資料的頁表,表項 1023 指向堆疊的頁表,其他的項(用陰影表示)表示沒有使用。現在把 PT2 域作為訪問選定的二級頁表的索引,以便找到虛擬頁面的對應頁框號。

針對分頁層級結構中不斷增加的替代方法是使用倒排頁表(inverted page tables)。採用這種解決方案的有 PowerPC、UltraSPARC 和 Itanium。在這種設計中,實際記憶體中的每個頁框對應一個表項,而不是每個虛擬頁面對應一個表項。

雖然倒排頁表節省了大量的空間,但是它也有自己的缺陷:那就是從虛擬地址到實體地址的轉換會變得很困難。當程式 n 訪問虛擬頁面 p 時,硬體不能再通過把 p 當作指向頁表的一個索引來查詢物理頁。而是必須搜尋整個倒排表來查詢某個表項。另外,搜尋必須對每一個記憶體訪問操作都執行一次,而不是在發生缺頁中斷時執行。

解決這一問題的方式時使用 TLB。當發生 TLB 失效時,需要用軟體搜尋整個倒排頁表。一個可行的方式是建立一個雜湊表,用虛擬地址來雜湊。當前所有記憶體中的具有相同雜湊值的虛擬頁面被連結在一起。如下圖所示

image-20200303203816038

如果雜湊表中的槽數與機器中物理頁面數一樣多,那麼雜湊表的衝突鏈的長度將會是 1 個表項的長度,這將會大大提高對映速度。一旦頁框被找到,新的(虛擬頁號,物理頁框號)就會被裝在到 TLB 中。

總結

這篇文章我從 MMU 這個知識點進行切入,來為你展開了作業系統中的虛擬地址的概念,作業系統是如何處理日益增加的地址空間的,以及記憶體管理面臨的挑戰,如何優化等。

如果這篇文章對你有幫助,求點贊,求轉發,求關注,你的支援是我更文最大的動力。

原文連結:從 MMU 看記憶體管理

相關文章