【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

楊柳_發表於2019-01-24

學習交流加

  • 個人qq: 1126137994
  • 個人微信: liu1126137994
  • 學習交流資源分享qq群: 962535112

上一篇文章學習了中斷的概念與意義,以及中斷的應用-斷點除錯原理。點選連結複習上一篇文章:中斷的概念與意義

本片文章繼續學習處理器相關的知識-記憶體管理。包括:記憶體管理單元MMU的作用,虛擬記憶體與實體記憶體之間的對映方式,頁表的概念,快取記憶體(Cache)的作用,實體記憶體與快取記憶體之間的對映關係等。當然,想要深入瞭解,本文並不適合,本文只是從原理上,講述以上幾者之間的關係。

@[TOC]

1、記憶體管理單元MMU

這裡假設大家瞭解虛擬記憶體的由來。參考《深入理解計算機系統》講虛擬記憶體的章節

實際上我們寫的程式,都是面向虛擬記憶體的。我們在程式中寫的變數的地址,實際上是虛擬記憶體中的地址,當CPU想要訪問該地址的時候,記憶體管理單元MMU會將該虛擬地址翻譯成真實的實體地址,然後CPU就去真實的實體地址處取得資料。

這裡說的虛擬地址,是指虛擬地址空間中地址。這裡我們說的虛擬地址空間,實際上是在磁碟上的一塊空間(常見的是4G的程式虛擬地址空間)。具體這4G的虛擬地址空間的來龍去脈,參考《深入理解計算機系統》第九章。

MMU:記憶體管理單元。它是一個硬體,不是軟體。它用於將虛擬地址翻譯成實際的實體記憶體地址。同時它還可以將特定的記憶體塊設定成不同的讀寫屬性,進而實現記憶體保護的功能。注意,MMU是硬體管理,不是軟體實現記憶體管理。

總結來說,MMU能實現以下功能:

  • 虛擬記憶體。有了虛擬記憶體,可以在處理器上執行比實際實體記憶體大的應用程式。為了使用虛擬記憶體,作業系統通常要設定一個交換區(通常在硬碟上),通過將記憶體中不活躍的資料與指令放到交換區,以騰出實體記憶體來為其他程式服務。
  • 記憶體保護。通過這一功能,可以將特定的記憶體塊設定為讀、寫或者可執行的屬性。比如將不可變的資料或者程式碼設為只讀的,這樣可以防止被惡意串改。

1.1、虛擬記憶體

程式的概念大家都知道。

每一個程式都獨立的執行在自己的虛擬地址空間。為了理解這一個概念。我們可以看一個而簡單的例子:

看一下下面的程式碼: main.c

#include <stdio.h>
#include <stdlib.h>

int g_int = 1;
int main() {
	printf("g_int = %d\n",g_int);
	printf("&g_int = %d\n",&g_int);

	system("pause");//此處程式會停止執行,不會執行到return 0
	return 0;
}
複製程式碼

如果我同時執行該程式兩次。列印結果會是一樣麼?答案是結果肯定一樣,執行結果都為:

在這裡插入圖片描述

當然,這是在我的計算機上,在你的計算機上g_int地址可能不一樣,但是同時執行該程式兩次,結果肯定是一樣的。其實這個答案很多人都知道是一樣的,初學者都知道。但是初學者說不清楚是為什麼。

  • 分析

這個程式執行兩份例項的時候。在實體記憶體中,實際上是以下分佈情況:

【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

程式1和程式2 位於不同的地址。但是我們程式列印的g_int全域性變數的地址值,是一樣的。

這裡就引入了虛擬記憶體的概念。我們寫程式,面向的是虛擬地址空間。寫的程式的內容,都可以看成是在虛擬地址空間中執行(實際上最終是將虛擬地址空間對映到了實體地址空間)。如下圖:

在這裡插入圖片描述

以上只是簡圖。

我們可以看到。main.o可執行程式,執行兩份例項時,相當於兩個程式。這兩個程式都有自己獨立的虛擬地址空間。然後將虛擬地址空間裡的程式碼資料對映到記憶體中,從而被CPU執行與處理。在實體記憶體中,g_int這個全域性變數的實體地址確實不同。但是在虛擬記憶體中,由於程式1與程式2的虛擬地址空間完全一樣(同一個可執行程式程式碼),那麼g_int地址,實際上就是一樣的。

CPU在執行指令與資料時,獲得的是虛擬記憶體的地址。但是CPU只能去實體記憶體定址。此時,MMU就派上用場了。MMU負責,將虛擬地址,翻譯成,真正執行時的實體地址。

MMU是如何將虛擬地址翻譯成實體地址的,這個後面講。現在先要了解一下交換區的概念。

  • 交換區: 實際上就是一塊磁碟空間(硬碟空間)。虛擬記憶體與實體記憶體對映的時候,是將虛擬記憶體的程式碼放到交換區中,以後在CPU想要執行相關的指令或者資料時,如果記憶體中沒有,先去交換區將需要的指令與資料對映到實體記憶體,然後CPU再執行。

虛擬記憶體與交換取的這種概念,實現了大記憶體需求量的(多個)程式,能夠(同時)執行在較小的實體記憶體中。如下圖所示:

在這裡插入圖片描述

上圖中,說的是程式的區域性程式碼在實體記憶體中執行。是因為程式具有區域性性原則,所以在某一段很小的時間段內,只有很少一部分程式碼會被CPU執行。具體可以參考下一篇文章。

到這裡,我們應該大致明白了虛擬記憶體的作用與簡單機制。還剩下MMU如何翻譯虛擬地址為實體地址的,這放到最後講解。現在先總結一下虛擬記憶體機制:

  • 虛擬記憶體需要重新對映到實體記憶體
  • 虛擬地址對映到實體地址中的實際地址
  • 每次只有程式的少部分程式碼會在實體記憶體中執行
  • 大部分程式碼依然位於磁碟中(儲存器硬碟)

1.2、 頁式記憶體管理

上一節籠統的介紹了虛擬記憶體的概念。接下來學習記憶體管理中的一種方式:頁式記憶體管理。 頁式記憶體管理中我們需要了解:

  • 頁的概念
  • 頁表的概念
  • 缺頁的概念與頁命中的概念
  • 分配頁面
  • 程式的區域性性原則

1.21 頁的概念

由1.1的內容,我們知道了交換區。我們知道交換區裡面存放的是大部分的可執行程式碼與資料。而實體記憶體中,執行的是少部分的可執行程式碼與資料。那麼當實體記憶體中的程式碼與資料執行完需要執行接下來的程式碼,而剛好接下來的程式碼還在交換區中沒有對映到實體記憶體(這稱為缺頁,後面會講),那麼此時就需要從交換區獲取程式的程式碼,將它拿到實體記憶體執行。那麼一次拿多少程式碼過來呢?這是一個問題!

為了CPU的高效執行以及方便的記憶體管理(詳細原因見以後的文章),每次需要拿一個頁的程式碼。這個頁,指的是一段連續的儲存空間(常見的是4Kb),也叫作塊。假設頁的大小為P。在虛擬記憶體中,叫做虛擬頁(VP)。從虛擬記憶體拿了一個頁的程式碼要放到實體記憶體,那麼自然實體記憶體也得有一個剛好一般大小的頁才能存放虛擬頁的程式碼。實體記憶體中的頁叫做物理頁(PP)

在任何時刻,虛擬頁都是以下三種狀態中的一種:

  • 未分配的:VM系統還未分配的頁(或者未建立)。未分配的頁還沒有任何資料與程式碼與他們相關聯,因此也就不佔用任何磁碟。
  • 快取的: 當前已快取在實體記憶體中的已分配頁
  • 未快取的:未快取在實體記憶體中的已分配頁

下圖展示了一個8個虛擬頁的小虛擬記憶體。其中:虛擬頁0和3還沒有被分配,因此在磁碟上還不存在。虛擬頁1、4、 6被快取在實體記憶體中。虛擬頁2、 5、 7已經被分配,但是還沒有快取到實體記憶體中去執行。

在這裡插入圖片描述

1.22 頁表的概念

1.21節用到了快取這個詞。這裡假設大家都理解快取的概念。

虛擬記憶體中的一些虛擬頁是快取在實體記憶體中被執行的。理所應當,應該有一種機制,來判斷虛擬頁,是否被快取在了實體記憶體中的某個物理頁上。如果不命中(需要一個頁的程式碼,但是這個頁未快取在實體記憶體中),系統還必須知道這個虛擬頁存放在磁碟上的哪個位置,從而在實體記憶體中選擇一個空閒頁或者替換一個犧牲頁,並將需要的虛擬頁從磁碟複製到實體記憶體中。

這些功能,是由軟硬體結合完成的。 包括作業系統軟體,MMU中的地址翻譯硬體,和一個存放在實體記憶體中的頁表的資料結構。

上一節說將虛擬頁對映到物理頁,實際上就是MMU地址翻譯硬體將一個虛擬地址翻譯成實體地址時,都會去讀取頁表的內容。作業系統負責維護頁表的內容,以及在磁碟與實體記憶體之間來回傳送頁。

下圖是一個頁表的基本組織結構(實際上不止那些內容):

【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

如上圖所示:

頁表實際上就是一個陣列。這個陣列存放的是一個稱為頁表條目(PTE)的結構。虛擬地址空間的每一個頁在頁表中,都有一個對應的頁表條目(PTE)。虛擬頁地址(首地址)翻譯的時候就是查詢的各個虛擬頁在頁表中的PTE,從而進行地址翻譯的。

現在假設每一個PTE都有一個有效位和一個n位欄位的地址。其中

  • 有效位:表示對應的虛擬頁是否快取在了實體記憶體中。0表示未快取。1表示已快取。
  • n位地址欄位:如果未快取(有效欄位為0),n位地址欄位不為空的話,這個n位地址欄位就表示該虛擬頁在磁碟上的起始的位置。如果這個n位欄位為空,那麼就說明該虛擬頁未分配。如果已快取(有效欄位為1),n位地址欄位肯定不為空,它表示該虛擬頁在實體記憶體中的起始地址。

綜上分析,就得知,上圖中:四個虛擬頁VP1 , VP2, VP4 , VP7 是被快取在實體記憶體中。 兩個虛擬頁VP0, VP5還未被分配。但是剩下的虛擬頁VP3 ,VP6已經被分配了,但是還沒有快取到實體記憶體中去執行。

注意:任意的物理頁,都可以快取任意的虛擬頁。(因為實體記憶體是全相聯的)

1.23 頁命中

考慮下圖的情形: 【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

假設現在CPU想讀取VP2頁面中的某一個位元組的內容。會發生什麼呢?

當CPU得到一個地址vaddr想要訪問它(這個addr就是上面想要訪問的某一個位元組的地址),通過後面會學習的MMU地址翻譯硬體,將虛擬地址addr作為索引定位到頁表的PTE條目中的PTE2(這裡假設是PTE2),從記憶體中去讀到PTE2的有效位為1,說明該虛擬頁面已經被快取了,所以CPU使用該PTE2條目中的實體記憶體地址(這個實體記憶體地址是PP1中的起始地址)構造出vaddr的實體地址paddr(這個地址是PP1頁面起始地址或後面的某一個地址)。然後CPU就會去paddr這個實體記憶體地址去取資料。這種情況,就是也命中。

實際上,上面的VP2的起始地址與paddr地址,很類似於記憶體的分段機制(X86以前就是分段機制),CPU訪問記憶體的地址是“段地址偏移地址”或者叫做“CS:IP”。而我們現在學習的是分頁機制,他們都是一種記憶體管理機制。

1.24 缺頁

什麼是缺頁?

考慮以下圖示情形:

【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

當CPU想訪問VP3頁面中的某一個位元組。會發生什麼情況?

由1.23小節的分析知,當地址翻譯硬體MMU找到了PTE3後,發現有效位為0,則說明VP3並未快取在實體記憶體中,並且觸發一個缺頁異常。缺頁異常呼叫核心中的缺頁異常處理程式,該程式會在實體記憶體中查詢是否有空閒頁面。如果實體記憶體中有空閒頁面,則將VP3頁面的內容從磁碟中複製到(對映)實體記憶體中的空閒頁面。如果實體記憶體中沒有空閒頁面,則缺頁異常處理程式就選擇一個犧牲頁,在此例中就是存放在PP3中的VP4。如果VP4已經被修改了,那麼核心就會將它複製回磁碟。

然後此時因為VP3已經在實體記憶體中被快取了,就需要將頁表更新,也就是更新PTE3。

隨後缺頁異常處理程式返回。它會重新啟動導致缺頁的指令,該指令會重新將剛剛導致缺頁的虛擬地址傳送到MMU硬體翻譯,但是此時,因為VP3已經被快取,所以會頁命中。

下圖是在經過了缺頁後,我們的示例頁表的狀態:【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

  • 以上有一個過程是替換頁面的過程,其中包含一個頁面排程演算法。這個以後會學習。

1.25 分配頁面

當你在程式中呼叫malloc或者new分配記憶體時,發生了什麼?呼叫malloc後,會在虛擬記憶體中分配頁面。(注意malloc分配的記憶體時虛擬記憶體,當CPU訪問的時候,首先肯定會發生缺頁,然後再將該頁快取到實體記憶體中)

如下圖所示: 本身沒有VP5這個虛擬頁面,現在malloc後,新分配了一個虛擬頁面VP5。

【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

分配好VP5這個虛擬頁面後,還需要更新PTE條目,使得PTE5指向VP5。

1.26 程式的區域性性原則

虛擬記憶體這種機制會有什麼問題?經常缺頁會不會導致程式的執行效率低下?

實際上,雖然會產生不命中現象,但是虛擬記憶體機制工作的很好。這主要與程式區域性性原則有關!!!什麼是程式的區域性性?

儘管在程式整個執行的生命週期,引用的不同的頁面總數可能會超過實體記憶體的大小,但是區域性性原則保證了在任意時刻程式將趨向於在一個較小的活動頁面集合上工作。 這個集合成為工作集或者常駐集合。在最開始,也就是將工作集頁面排程到實體記憶體中之後,接下來對這個工作集的引用將導致頁命中,而不會產生額外的磁碟流量。

上面看似很完美,但是也有可能會出現這樣一種情況:工作集的大小超過了實體記憶體的大小!! 此時,頁面會不停的換入換出。這種狀態叫做抖動!!!

當然,現在的計算機的實體記憶體的大小都非常大,一般不會出現抖動的現象!!!

1.3 虛擬記憶體作為記憶體管理工具

虛擬記憶體為什麼說是一種記憶體管理工具?

虛擬記憶體大大地簡化了記憶體管理,並提供了一種自然的保護記憶體的方法。

到目前為止,我們都假設有一個單獨的頁表,將一個虛擬地址空間對映到實體地址空間。實際上,作業系統為每一個程式提供了一個獨立的頁表,因而也就是一個獨立的虛擬地址空間。如下圖: 【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

注意:多個虛擬頁面,可以對映到同一個共享物理頁面上。

按需頁面排程和獨立的虛擬地址空間的結合,對系統中記憶體的使用和管理產生了深遠的影響!!!如下:

  • 簡化連結。
  • 簡化載入
  • 簡化共享
  • 簡化記憶體分配

具體參考CSAPP:9.4節內容。

1.4、虛擬記憶體作為記憶體保護工具

上一節學習了虛擬記憶體作為記憶體管理工具。

其實虛擬記憶體還可以作為記憶體保護工具。如何做到?

想一想,CPU在訪問一個虛擬記憶體頁面時,需要讀取頁表條目中的PTE條目。如果在PTE條目中加一些額外的許可位來控制對虛擬記憶體的訪問,當CPU讀到相應的許可位,就可以知道該虛擬記憶體是否可讀或者可寫,或者可執行? 這樣看來我們的頁表就要變化一下,就如下圖所示: 【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

上圖中:

  • SUP表示程式是否必須執行在核心模式(超級使用者)下才能訪問該頁。
  • READ表示是否可讀
  • WRITE表示是否可寫

如果一條指令違反了這些許可條件,那麼CPU就會觸發一個一般保護故障,將控制傳遞給一個核心中的異常處理程式。Linux shell 一般將這種異常報告為“段錯誤(segmentation fault)”

2、地址翻譯

上面一直在說MMU通過讀取頁表的PTE將虛擬地址翻譯成實體地址。到底是如何翻譯的?

如下圖,展示了MMU是如何翻譯地址的: 【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

看到這麼複雜的圖,不要害怕!!! 下面講解很容易懂!

  • CPU中有一個控制暫存器,頁表基址暫存器(PTBR)指向當前頁表。
  • n位的虛擬地址,包含兩個部分:虛擬頁面偏移VPO(p位)與虛擬頁號VPN(n-p位)
  • MMU利用虛擬記憶體的高n-p位VPN作為索引找到頁表的對應的PTE條目,然後獲取PTE條目對應的物理頁號PPN
  • 然後將PPN與VPO串聯連線起來,就得到了實際的實體地址。(實際上就是PPN左移p位然後加上VPO,VPO=PPO)

到這裡實際上我們已經更加的將這種地址串聯與X86處理器中的分段機制很像。X86-16位的分段機制 也是將段地址CS左移4位然後與偏移地址IP相加,得到最終的實體地址。這是不是與上面的分頁機制的地址翻譯過程很像? 實際上它們一個是真實模式,一個是保護模式而已!

MMU的地址翻譯過程是不是很簡單?如果不理解,就反覆看,就理解了!!!

3、總結

下面來總結一下,分頁機制中,CPU獲得一個虛擬地址後,有哪些步驟需要做:

【軟體開發底層知識修煉】三 深入淺出處理器之三 記憶體管理與記憶體管理單元(MMU)

本片文章,我學會了:

  • 虛擬記憶體的概念與交換區的概念

  • MMU的作用

  • 虛擬記憶體機制的意義

    • 虛擬記憶體作為記憶體管理工具
    • 虛擬記憶體作為記憶體保護工具
  • 頁表的概念

  • 頁命中與缺頁

  • 程式的區域性性在虛擬記憶體中的作用

  • MMU的地址翻譯過程

本文章參考狄泰軟體學院相關課程與深入理解計算機系統第九章內容 想學習的可以加狄泰軟體學院群, 群聊號碼:199546072

本人積累了無數的技術電子書籍與各類技術的視訊教程,可以加好友共同探討學習交流。 學習探討加個人: qq:1126137994 微信:liu1126137994

相關文章