介紹
歷史
第一臺電腦
早期的計算機有一塊記憶體,程式設計師將程式碼和資料放入其中,並在此環境中執行 CPU。鑑於計算機非常昂貴,不幸的是它會完成一項工作,停止並等待下一個工作載入到它中,然後處理那個工作。
多使用者,多處理
因此,計算機很快就變得更加複雜,同時支援多個使用者和/或程式 - 但是,當簡單的一塊記憶體想法開始出現問題時。如果計算機同時執行兩個程式,或者為多個使用者執行相同的程式 - 當然每個使用者需要單獨的資料 - 那麼對該記憶體的管理變得至關重要。
例
例如:如果一個程式被編寫為在記憶體地址 1000 工作,但另一個程式已經載入到那裡,則無法載入新程式。解決這個問題的一種方法是使程式使用相對定址 - 程式載入的位置並不重要,它只是相對於載入它的記憶體地址做了所有事情。但這需要硬體支援。
詭辯
隨著計算機硬體變得越來越複雜,它能夠支援更大的記憶體塊,允許更多的同步程式,並且編寫不干擾已經載入的程式的程式變得更加棘手。一個雜散記憶體引用不僅可以降低當前程式,還可以降低記憶體中的任何其他程式 - 包括作業系統本身!
解決方案
需要的是一種允許記憶體塊具有動態地址的機制。這樣一個程式可以被編寫為在其識別的地址處使用其儲存塊 - 並且無法訪問其他程式的其他塊(除非一些合作允許)。
分割
實現這一目標的一種機制是 Segmentation。這允許定義所有不同大小的記憶體塊,並且程式需要定義它想要一直訪問哪個 Segment。
問題
這種技術很強大 - 但它的靈活性是一個問題。由於 Segments 基本上將可用記憶體細分為不同大小的塊,因此這些段的記憶體管理是一個問題:分配,釋放,增長,縮小,碎片 - 所有必需的複雜例程,有時需要大規模複製才能實現。
分頁
一種不同的技術將所有記憶體劃分為相同大小的塊,稱為頁面,這使得分配和釋放例程非常簡單,並且消除了增長,縮小和碎片(除了內部碎片,這僅僅是一個問題浪費)。
虛擬定址
通過將儲存器分成這些塊,可以根據需要將它們分配給不同的程式,無論程式需要什麼地址。儲存器的實體地址和程式所需地址之間的這種對映非常強大,並且是當今每個主要處理器(Intel,ARM,MIPS,Power 等人)儲存器管理的基礎。
硬體和作業系統支援
硬體自動且連續地執行重新對映,但需要記憶體來定義要執行的操作的表。當然,與這種重新對映相關的內務管理必須由某種東西控制。作業系統必須根據需要分配記憶體,並管理硬體所需的資料表以支援所需的程式。
分頁功能
一旦硬體可以執行此重對映,它允許什麼?主要的驅動因素是多處理 - 執行多個程式的能力,每個程式都有自己的記憶體,相互保護。但另外兩個選項包括稀疏資料和虛擬記憶體。
多
每個程式都有自己的虛擬地址空間 - 一系列地址,它們可以將實體記憶體對映到所需的任何地址。只要有足夠的實體記憶體可以到處(雖然參見下面的虛擬記憶體),但可以同時支援許多程式。
更重要的是,這些程式無法訪問未對映到其虛擬地址空間的記憶體 - 程式之間的保護是自動的。如果程式需要通訊,他們可以要求作業系統安排共享記憶體塊 - 一塊實體記憶體,同時對映到兩個不同程式的地址空間。
稀疏資料
允許一個巨大的虛擬地址空間(通常為 4 GB,與這些處理器通常具有的 32 位暫存器相對應)本身並不浪費記憶體,如果該地址空間的大面積未對映的話。這允許建立巨大的資料結構,其中任何時候僅對映某些部分。想象一下每個方向上有一個 1000 位元組的三維陣列:通常需要十億位元組! 但是程式可以保留一個虛擬地址空間塊以保留這些資料,但只能在填充它們時對映小部分。這樣可以實現高效的程式設計,同時不會浪費記憶體來處理不需要的資料。
虛擬記憶體
上面我使用術語虛擬定址來描述硬體執行的虛擬到物理定址。這通常被稱為虛擬記憶體 - 但該術語更準確地對應於使用虛擬定址來支援提供比實際可用記憶體更多的記憶體的假設的技術。
它的工作原理如下:
-
當程式被載入並請求更多記憶體時,作業系統會從可用記憶體中提供記憶體。除了跟蹤已對映的記憶體之外,作業系統還會跟蹤實際使用記憶體的時間 - 硬體支援標記已使用的頁面。
-
當作業系統耗盡實體記憶體時,它會檢視已經分發的所有記憶體,無論使用哪個頁面最少,或者使用時間最長。它將特定頁面的內容儲存到硬碟,記住它的位置,將其標記為原始所有者的硬體不存在,然後將頁面歸零並將其提供給新的所有者。
-
如果原始所有者再次嘗試訪問該頁面,則硬體會通知作業系統。作業系統然後分配一個新頁面(可能需要再次執行上一步!),載入舊頁面的內容,然後將新頁面交給原始程式。
需要注意的重要一點是,由於任何頁面都可以對映到任何地址,並且每個頁面的大小相同,因此只要內容保持不變,一頁就可以與其他頁面一樣好!
-
如果程式訪問未對映的記憶體位置,則硬體會像以前一樣通知作業系統。這一次,作業系統注意到它不是一個已被儲存的頁面,因此將其識別為程式中的錯誤,並終止它!
這實際上是當你的應用程式神祕地消失在你身上時會發生的事情 - 也許是來自作業系統的 MessageBox。這也是(通常)導致臭名昭著的藍屏或悲傷 Mac 的原因 - 錯誤的程式實際上是一個作業系統驅動程式訪問它不應該的記憶體!
尋呼決定
硬體架構師需要對 Paging 做出一些重大決策,因為設計會直接影響 CPU 的設計! 一個非常靈活的系統會產生很高的開銷,需要大量記憶體才能管理 Paging 基礎架構本身。
一個頁面應該有多大?
在硬體中,最簡單的 Paging 實現是獲取一個 Address 並將其分為兩部分。上半部分是要訪問哪個頁面的指示符,而下半部分是所需位元組的頁面索引:
+-----------------+------------+
| Page index | Byte index |
+-----------------+------------+
儘管小頁面需要為每個程式提供大量索引,但很快就會變得明顯:即使是未對映的記憶體,也需要表中的條目來指示這一點。
因此,使用多層索引。地址分為多個部分(下面的例子中顯示了三個部分),頂部(通常稱為目錄)索引到下一部分,依此類推,直到最後一個位元組索引進入最終頁面:
+-----------+------------+------------+
| Dir index | Page index | Byte index |
+-----------+------------+------------+
這意味著 Directory 索引可以指示大量地址空間的未對映,而不需要大量的頁面索引。
如何優化頁表的使用?
必須對映 CPU 將進行的每個地址訪問 - 因此虛擬到物理過程必須儘可能高效。如果要實現上述三層系統,那就意味著每次記憶體訪問實際上都是三次訪問:一次進入目錄; 一進頁表; 然後最終得到所需的資料。如果 CPU 也需要執行內務處理,例如指示此頁面現在已被訪問或寫入,那麼這將需要更多訪問來更新欄位。
記憶體可能很快,但這會在分頁期間對所有記憶體訪問造成三倍減速! 幸運的是,大多數程式都有範圍內的位置 - 也就是說,如果它們訪問記憶體中的一個位置,那麼將來的訪問可能就在附近。由於 Pages 不是太小,只需要在訪問新頁面時執行對映轉換:不是絕對每次訪問。
但更好的方法是實現最近訪問過的 Pages 的快取,而不僅僅是最新的。問題在於跟上頁面的訪問許可權和未訪問的內容 - 硬體必須在每次訪問時掃描快取以找到快取的值。因此,快取被實現為內容可定址快取:不是通過地址訪問,而是由內容訪問 - 如果請求的資料存在,則提供它,否則標記為空填充的位置。快取管理所有這些。
此內容可定址快取通常稱為轉換後備緩衝區(TLB),並且需要由 OS 作為虛擬定址子系統的一部分進行管理。當作業系統修改目錄或頁表時,它需要通知 TLB 更新其條目 - 或者只是使它們無效。