1. 程式人生 > 實用技巧 >讀懂作業系統之快表(TLB)原理(七)

讀懂作業系統之快表(TLB)原理(七)

前言

前不久、我們詳細分析了TLB基本原理,本節我們通過一個簡單的示例再次敘述TLB的演算法和原理,希望藉此示例能加深我們對TLB(又稱之為快表,深入理解計算機系統(第三版)又稱之為翻譯後備緩衝區)的理解。

使用分頁作為支援虛擬記憶體的核心機制可能會導致高效能開銷,通過將地址空間劃分成固定大小的小單元(即頁面),分頁需要對映大量資訊,由於該對映資訊通常儲存在實體記憶體中,因此在邏輯上分頁需要針對程式生成的每個虛擬地址進行額外的記憶體查詢,在每條指令獲取或顯式載入或儲存之前進入記憶體獲取翻譯資訊的速度會非常慢。因此,我們的問題演變為:如何對邏輯地址進行高速翻譯?我們如何加快地址轉換的速度從而避免在分頁時需要額外的記憶體引用?硬體支援是必要的嗎?哪些操作需要作業系統參與?

TLB(快表,翻譯後備緩衝區)原理和練習

當我們想加快查詢速度時,通常需要藉助於作業系統通常來完成,那就是來自作業系統的老朋友:硬體。為了加快地址翻譯速度,我們將新增一個稱為翻譯後備緩衝區(TLB),TLB是晶片的記憶體管理單元(MMU)的一部分,因此,TLB是虛擬地址到實體地址翻譯的硬體快取。在每次引用虛擬記憶體時,硬體首先檢查TLB,以檢視是否已保留了所需的翻譯(PTE),否則,硬體將檢查TLB。如此這樣將執行快速翻譯而不必每次都去查閱頁表(包含所有翻譯),因每次進行記憶體引用必將對對效能的巨大影響,所以從另外一個角度講,實際上TLB使虛擬記憶體成為可能。邏輯地址並計算出VPN、如何查詢TLB、TLB缺失後如何處理等等通過圖解方式來進行詳細敘述,這裡我們通過虛擬碼方式來進一步講解TLB整個大概原理過程若有錯誤之處,還請批評指正,存有疑惑之處,還請先看作業系統專輯中對此更容易理解的敘述

 1 VPN = (VirtualAddress & VPN_MASK) >> SHIFT;
 2 (Success, TlbEntry) = TLB_Lookup(VPN);
 3 if (Success == true)
 4 {
 5     if (CanAccess(TlbEntry.ProtectBits) == true)
 6     {
 7         Offset = VirtualAddress & OFFSET_MASK;
 8         PhysAddr = (TlbEntry.PFN << SHIFT) | Offset;
 
9 Register = AccessMemory(PhysAddr); 10 } 11 else 12 { 13 RaiseException(PROTECTION_FAULT); 14 } 15 } 16 else 17 { 18 PTEAddr = PTBR + (VPN * sizeof(PTE)); 19 PTE = AccessMemory(PTEAddr); 20 if (PTE.Valid == False) 21 RaiseException(SEGMENTATION_FAULT); 22 else if (CanAccess(PTE.ProtectBits) == false) 23 RaiseException(PROTECTION_FAULT); 24 else 25 TLB_Insert(VPN, PTE.PFN, PTE.ProtectBits); 26 RetryInstruction(); 27 }

從邏輯地址提取出VPN(第1行),呼叫TLB_Lookup方法查詢TLB中是否存持有VPN的翻譯(第2行),獲取到TLB是否命中的標識(Success)和TLB條目。若TLB命中(第3行),獲取TLB條目中的保護位並呼叫CanAccess方法判斷是否可訪問(第5行),若可訪問,從邏輯地址中提取出偏移量(第7行)。通過TLB條目中的物理頁幀號即PFN、頁偏移量以及基於頁起始位置偏移計算出實體地址(第8行),傳遞實體地址並呼叫訪問主存方法AccessMemory,使得暫存器指向主存地址空間(第9行)若TLB缺失(第16行),通過VPN和頁表基暫存器計算PTE地址(第18行),傳遞PTE地址呼叫訪問主存方法AccessMemory,這裡是訪問主存頁表從而獲取PTE,很顯然,訪問主存頁表將導致額外的記憶體引用,操作成本很高(第19行),若PTE有效位為無效(第20行),則引發錯誤(第21行),同理若不可訪問則引發保護位錯誤,這個時候將交由作業系統來進行缺頁處理進行頁面置換。否則呼叫TLB_Insert方法,將VPN和PTE中的PFN和保護位插入到TLB條目(第25行),一旦更新了TLB條目,那麼硬體將重試缺頁指令,如此將加速翻譯,快速處理記憶體引用(第26行)

為了更清楚說明TLB的作用,我們通過一個簡單的示例來跟蹤虛擬地址翻譯,最後看看TLB如何改善其效能。在此示例中,假設記憶體中有10個4位元組整數的陣列,從虛擬地址100開始。進一步假設我們有一個小的8位虛擬地址空間,具有16位元組頁面;因此,虛擬地址分為4位VPN(有16個虛擬頁面)和4位偏移(即每頁面有16個位元組)。如下求陣列元素之和

int sum = 0;

var array = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

for (int i = 0; i < 10; i++)
{
    sum += array[i];
}

上述陣列中的10個元素在頁表中的存放大概如下圖所示,第1個元素位於(VPN = 6,偏移量 = 4),所以在第6頁只能容納3個4個位元組整數元素,緊接著其他元素則分別放在第7和第8頁。

為了簡單起見,我們假設迴圈生成的唯一記憶體訪問就是對陣列的訪問,忽略變數i和sum以及指令本身。當訪問第一個陣列元素(a[0])時,CPU將載入100的虛擬地址,硬體從地址中提取VPN(VPN = 06),並使用該地址檢查TLB的有效轉換,假設這是程式第一次訪問陣列,則結果將是引起TLB缺失。下一步開始訪問元素a[1],好訊息是:TLB命中,因為陣列的第二個元素緊挨著第一個元素,所以它位於同一頁上,因為在訪問陣列的第1個元素時已經訪問了此頁面,所以翻譯已經載入到了TLB中。當訪問元素a[3]時,此時又會引起頁缺失,但接下來的a[4]..a[6]都將命中,因為它們和a[3]都在記憶體中同一頁上。我們總結一下對陣列的10次訪問期間的TLB活動:未命中,命中,命中,未命中,命中,命中,命中,未命中,命中,命中。因此,我們的TLB命中率(即命中數除以訪問總數)為70%

雖然這不是太高(實際上,我們希望命中率接近100%),但它不是零,這可能令人驚訝。即使這是程式第1次訪問該陣列時,由於空間區域性性,TLB還是會提高效能。陣列的元素緊密地包裝在頁面中(即它們在空間上彼此靠近),因此只有第1次訪問頁面上的元素才會產生TLB未命中。其實這也我們聯想到了為何要進行快取預熱,因為第1次訪問會導致快取強制缺失。如果頁面大小隻是原來的2倍(32位元組,而非16位元組),則陣列訪問將遭受更少的丟失。由於典型的頁面大小是4KB,因此這些型別的密集,基於陣列的訪問可凸顯出出色的TLB效能,每頁訪問僅遇到一次未命中。

關於TLB效能的最後一點:如果程式在此迴圈完成後不久再次訪問了陣列,則假設我們有足夠大的TLB來快取所需的翻譯,那麼我們可能會看到更好的結果:命中,命中,命中,命中,命中,命中,命中,命中,命中,命中。在這種情況下,由於時間區域性性即時間上的快速重新引用,TLB命中率會很高,像任何快取記憶體一樣,TLB依賴於時間和空間區域性性而獲得成功,程式具備這兩個屬性,往往大多時候,此二者不可同時兼得。

總結

相信之前和通過本節專述TLB原理,同時結合一個簡單的示例詳細的講解,能夠讓大家更加明白TLB的實際作用,下一節我們進入關於作業系統記憶體管理最後一小節內容作為總結,然後我們開始進入程式執行、執行緒、程序等內容,感謝您的閱讀,我們下節見。