虛擬記憶體是一種作業系統提供的機制,用於將每個程序分配的獨立的虛擬地址空間對映到實際的實體記憶體地址空間上。通過使用虛擬記憶體,作業系統可以有效地解決多個應用程式直接操作實體記憶體可能引發的衝突問題。
在使用虛擬記憶體的情況下,每個程序都有自己的獨立的虛擬地址空間,它們不能直接存取實體記憶體地址。當程式存取虛擬記憶體地址時,作業系統會進行地址轉換,將虛擬地址對映到實體地址上,這樣不同的程序執行時,寫入的是不同的實體地址,避免了互相覆蓋指標的問題。
虛擬記憶體的使用使得每個程序都可以擁有相同的虛擬地址空間,而不用擔心與其他程序的地址衝突。作業系統負責管理虛擬地址和實體地址之間的對映關係,並在需要時進行地址轉換。這樣,程序可以以一種透明的方式存取記憶體,無需關心記憶體的實際物理位置。
通過虛擬記憶體機制,作業系統能夠更好地管理系統記憶體資源,提供更高的安全性和穩定性。它可以為每個程序提供獨立的地址空間,保護程序間的資料隔離,同時也可以有效地利用實體記憶體,將不常用的資料交換到磁碟上(交換區),以提供更大的可用記憶體空間。
在分段機制下,虛擬地址由兩部分組成:段選擇子和段內偏移量。段選擇子是一個索引,用於指定要存取的段的起始地址和長度。段內偏移量則表示在該段內的具體位置。
作業系統會維護一個段表,其中包含了每個段的起始地址和長度資訊。當程式存取一個虛擬地址時,作業系統會通過段選擇子從段表中找到對應的段描述符,然後根據段描述符中的資訊計算出實體地址。
具體的對映過程如下:
不過,需要注意的是,分段機制可能會導致記憶體碎片的問題,因為不同段的大小可能不同,導致一些碎片化的空間無法被利用。當不夠記憶體分配的時候,會選擇使用記憶體交換,先把一塊正在使用的記憶體移到磁碟中,然後再移回來把中間留的記憶體縫隙全用上,雖然解決了記憶體碎片的問題,但是這個交換操作很慢,效率低,看下圖示:
虛擬記憶體、分段和記憶體交換似乎解決了同時執行多個程式的問題,但仍存在效能瓶頸。由於硬碟存取速度較慢,每次記憶體交換都需要將大段連續的記憶體資料寫入硬碟。因此,如果交換的是佔用大量記憶體空間的程式,整個系統會變得卡頓。
為了解決記憶體分段的碎片和提高記憶體交換效率,引入了記憶體分頁機制。
記憶體分頁是將整個虛擬和實體記憶體空間劃分為固定大小的連續記憶體塊,稱為頁(Page)。在Linux下,每一頁的大小通常為4KB。虛擬地址與實體地址之間通過頁表進行對映,頁表儲存在CPU的記憶體管理單元(MMU)中,從而CPU可以直接通過MMU找到實際存取的實體記憶體地址。
虛擬地址與實體地址之間通過頁表來對映,如下圖:
由於記憶體空間事先劃分為固定大小的頁,不會像分段機制那樣產生碎片。當釋放記憶體時,以頁為單位進行釋放,避免了無法利用的小記憶體塊。
如果記憶體空間不足,作業系統會將其他正在執行的程序中的"最近未使用"的記憶體頁面暫時儲存到硬碟上,稱為換出(Swap Out)。當需要時,再將頁面載入回記憶體,稱為換入(Swap In)。因此,每次寫入硬碟的是少量的一頁或幾頁,不會花費太多時間,從而提高了記憶體交換的效率。
簡單分頁存在空間上的缺陷。在作業系統可以同時執行大量程序的情況下,頁表會變得非常龐大。在32位元環境下,虛擬地址空間為4GB,假設頁的大小為4KB,就需要大約100萬個頁。每個頁表項需要4位元組來儲存,所以整個4GB空間的對映需要4MB的記憶體來儲存頁表。
儘管4MB的頁表看起來並不算太大,但要注意每個程序都有自己的虛擬地址空間,也就是說每個程序都有自己的頁表。如果有100個程序,就需要400MB的記憶體來儲存頁表,這對於記憶體來說是相當大的開銷,更不用說64位元環境下了。
要解決上述問題,我們可以採用一種叫做多級頁表(Multi-Level Page Table)的解決方案。在之前我們已經瞭解到,在32位元環境下,頁大小為4KB的情況下,一個程序的頁表需要儲存100多萬個頁表項,每個項佔用4位元組的空間,因此一個頁表需要4MB的記憶體空間。
為了節省記憶體空間,我們可以將單級頁表進行分頁,將一個頁表(一級頁表)分為1024個頁表(二級頁表),每個二級頁表包含1024個頁表項,形成二級分頁結構。這樣一級頁表覆蓋整個4GB的虛擬地址空間,而對於未使用的頁表項,不會建立對應的二級頁表,只在需要時才建立。如下圖所示:
換個角度來看,大多數程式未使用到整個4GB的虛擬地址空間,因此部分頁表項是空的,沒有分配實際的記憶體空間。在實體記憶體緊張的情況下,作業系統會將最近一段時間未存取的頁表換出到硬碟,從而釋放實體記憶體。使用二級分頁,一級頁表只需要覆蓋整個4GB的虛擬地址空間,而未使用的頁表項不需要建立對應的二級頁表。假設只有20%的一級頁表項被使用,那麼頁表佔用的記憶體空間只有0.804MB,相比於單級頁表的4MB,記憶體節約非常巨大。
為什麼不分級的頁表無法實現這樣的記憶體節約呢?從頁表的性質來看,頁表儲存在記憶體中,其主要作用是將虛擬地址翻譯為實體地址。如果在頁表中找不到對應的頁表項,計算機系統將無法正常工作。因此,頁表必須覆蓋整個虛擬地址空間。而不分級的頁表需要100多萬個頁表項進行對映,而二級分頁只需要1024個頁表項(一級頁表覆蓋整個虛擬地址空間,二級頁表在需要時建立)。
TLB(Translation Lookaside Buffer)是一個位於CPU晶片中的快取,用於儲存程式中最常存取的頁表項,以加快虛擬地址到實體地址的轉換速度。多級頁表雖然解決了空間上的問題,但是增加了轉換的工序,導致時間上的開銷。然而,由於程式的區域性性原理,程式執行期間通常僅限於某一部分,存取的儲存空間也侷限於某個記憶體區域。因此,通過將最常存取的頁表項儲存到TLB這個硬體快取中,可以更快地進行地址轉換。
在CPU晶片中,記憶體管理單元(Memory Management Unit)晶片負責處理地址轉換和TLB的存取與互動。當CPU進行定址時,首先會查詢TLB,如果找到了對應的頁表項,就可以直接進行實體地址的存取,避免了繼續查詢常規頁表的開銷。
由於TLB中儲存的是程式最常存取的幾個頁表項,所以TLB的命中率通常是很高的。這是因為程式執行過程中,存取的頁表項相對固定。通過利用TLB,可以大大提高地址轉換的速度,加快程式的執行效率。
Linux記憶體管理涉及邏輯地址和線性地址的轉換。邏輯地址是程式使用的地址,而線性地址是通過段式記憶體管理對映的地址,也稱為虛擬地址。
Linux的虛擬地址空間分為核心空間和使用者空間兩部分。32位元系統中,核心空間佔用1G,剩下的3G是使用者空間;64位元系統中,核心空間和使用者空間都是128T,分別佔據記憶體空間的最高和最低處。如下所示:
程序在使用者態時只能存取使用者空間記憶體,進入核心態後才能存取核心空間記憶體。雖然每個程序都有獨立的虛擬記憶體,但虛擬記憶體中的核心地址關聯的是相同的實體記憶體,這樣程序切換到核心態後就可以方便地存取核心空間記憶體。
虛擬記憶體是作業系統提供的一種機制,通過將每個程序分配的獨立的虛擬地址空間對映到實際的實體記憶體地址空間上,解決了多個應用程式直接操作實體記憶體可能引發的衝突問題。虛擬記憶體的使用使得每個程序都可以擁有相同的虛擬地址空間,而不用擔心與其他程序的地址衝突。通過虛擬記憶體機制,作業系統能夠更好地管理系統記憶體資源,提供更高的安全性和穩定性。虛擬記憶體的實現方式有分段和分頁,其中分頁機制更為常用,採用多級頁表的方式節約了記憶體空間。頁錶快取TLB能夠加快虛擬地址到實體地址的轉換速度。Linux的記憶體管理涉及邏輯地址和線性地址的轉換,將虛擬地址空間分為核心空間和使用者空間,方便程序存取核心空間記憶體。