讀懂操作系統之快表(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的實際作用,下一節我們進入關於操作系統內存管理最後一小節內容作爲總結,然後我們開始進入程序執行、線程、進程等內容,感謝您的閱讀,我們下節見。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章