深入理解緩存 TLB 原理

今天分享一篇TLB的好文章,希望大家夯實基本功,讓我們一起深入理解計算機(jī)系統(tǒng)。
TLB 是 translation lookaside buffer 的簡稱。首先,我們知道 MMU 的作用是把虛擬地址轉(zhuǎn)換成物理地址。
詳細(xì)講解MMU

MMU工作原理
虛擬地址和物理地址的映射關(guān)系存儲在頁表中,而現(xiàn)在頁表又是分級的。64 位系統(tǒng)一般都是 3~5 級。常見的配置是 4 級頁表,就以 4 級頁表為例說明。分別是 PGD、PUD、PMD、PTE 四級頁表。在硬件上會有一個叫做頁表基地址寄存器,它存儲 PGD 頁表的首地址。

Linux分頁機(jī)制
MMU 就是根據(jù)頁表基地址寄存器從 PGD 頁表一路查到 PTE,最終找到物理地址(PTE頁表中存儲物理地址)。這就像在地圖上顯示你的家在哪一樣,我為了找到你家的地址,先確定你是中國,再確定你是某個省,繼續(xù)往下某個市,最后找到你家是一樣的原理。一級一級找下去。這個過程你也看到了,非常繁瑣。如果第一次查到你家的具體位置,我如果記下來你的姓名和你家的地址。下次查找時,是不是只需要跟我說你的姓名是什么,我就直接能夠告訴你地址,而不需要一級一級查找。
四級頁表查找過程需要四次內(nèi)存訪問。延時可想而知,非常影響性能。頁表查找過程的示例如下圖所示。以后有機(jī)會詳細(xì)展開,這里了解下即可。

TLB 的本質(zhì)是什么
TLB 其實就是一塊高速緩存。
數(shù)據(jù) cache 緩存地址(虛擬地址或者物理地址)和數(shù)據(jù)。TLB 緩存虛擬地址和其映射的物理地址。TLB 根據(jù)虛擬地址查找 cache,它沒得選,只能根據(jù)虛擬地址查找。
所以 TLB 是一個虛擬高速緩存。硬件存在 TLB 后,虛擬地址到物理地址的轉(zhuǎn)換過程發(fā)生了變化。虛擬地址首先發(fā)往 TLB 確認(rèn)是否命中 cache,如果 cache hit 直接可以得到物理地址。
否則,一級一級查找頁表獲取物理地址。并將虛擬地址和物理地址的映射關(guān)系緩存到 TLB 中。既然 TLB 是虛擬高速緩存(VIVT),是否存在別名和歧義問題呢?如果存在,軟件和硬件是如何配合解決這些問題呢?
TLB 的特殊
虛擬地址映射物理地址的最小單位是 4KB。所以 TLB 其實不需要存儲虛擬地址和物理地址的低 12 位(因為低 12 位是一樣的,根本沒必要存儲)。
另外,我們?nèi)绻?cache,肯定是一次性從 cache 中拿出整個數(shù)據(jù)。所以虛擬地址不需要 offset 域。index 域是否需要呢?這取決于cache的組織形式。
如果是全相連高速緩存。那么就不需要 index。如果使用多路組相連高速緩存,依然需要index。
下圖就是一個四路組相連 TLB 的例子?,F(xiàn)如今 64 位 CPU 尋址范圍并沒有擴(kuò)大到 64 位。64 位地址空間很大,現(xiàn)如今還用不到那么大。
因此硬件為了設(shè)計簡單或者解決成本,實際虛擬地址位數(shù)只使用了一部分。這里以 48 位地址總線為例說明。

TLB 的別名問題
我先來思考第一個問題,別名是否存在。我們知道 PIPT 的數(shù)據(jù) cache 不存在別名問題。物理地址是唯一的,一個物理地址一定對應(yīng)一個數(shù)據(jù)。但是不同的物理地址可能存儲相同的數(shù)據(jù)。
也就是說,物理地址對應(yīng)數(shù)據(jù)是一對一關(guān)系,反過來是多對一關(guān)系。由于?TLB?的特殊性,存儲的是虛擬地址和物理地址的對應(yīng)關(guān)系。
因此,對于單個進(jìn)程來說,同一時間一個虛擬地址對應(yīng)一個物理地址,一個物理地址可以被多個虛擬地址映射。
將 PIPT 數(shù)據(jù) cache 類比 TLB,我們可以知道TLB 不存在別名問題。而 VIVT Cache 存在別名問題,原因是 VA 需要轉(zhuǎn)換成PA,PA 里面才存儲著數(shù)據(jù)。中間多經(jīng)傳一手,所以引入了些問題。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ??


TLB的歧義問題
我們知道不同的進(jìn)程之間看到的虛擬地址范圍是一樣的,所以多個進(jìn)程下,不同進(jìn)程的相同的虛擬地址可以映射不同的物理地址。這就會造成歧義問題。
例如,進(jìn)程A將地址?0x2000 映射物理地址?0x4000。進(jìn)程?B?將地址?0x2000?映射物理地址?0x5000。當(dāng)進(jìn)程 A?執(zhí)行的時候?qū)?0x2000?對應(yīng)0x4000?的映射關(guān)系緩存到 TLB?中。當(dāng)切換?B?進(jìn)程的時候,B?進(jìn)程訪問?0x2000?的數(shù)據(jù),會由于命中?TLB?從物理地址0x4000取數(shù)據(jù)。
這就造成了歧義。如何消除這種歧義,我們可以借鑒 VIVT 數(shù)據(jù) cache 的處理方式,在進(jìn)程切換時將整個 TLB 無效。切換后的進(jìn)程都不會命中 TLB,但是會導(dǎo)致性能損失。
如何盡可能地避免 flush TLB
首先需要說明的是,這里的 flush 理解成使無效的意思。我們知道進(jìn)程切換的時候,為了避免歧義,我們需要主動 flush 整個 TLB。如果我們能夠區(qū)分不同的進(jìn)程的 TLB 表項就可以避免 flush TLB。
我們知道 Linux 如何區(qū)分不同的進(jìn)程,每個進(jìn)程擁有一個獨一無二的進(jìn)程 ID。如果 TLB 在判斷是否命中的時候,除了比較 tag 以外,再額外比較進(jìn)程 ID 該多好呢!這樣就可以區(qū)分不同進(jìn)程的TLB表項。
進(jìn)程 A 和 B 雖然虛擬地址一樣,但是進(jìn)程 ID 不一樣,自然就不會發(fā)生進(jìn)程 B 命中進(jìn)程 A 的 TLB 表項。所以,TLB 添加一項 ASID(Address Space ID) 的匹配。
ASID 就類似進(jìn)程 ID 一樣,用來區(qū)分不同進(jìn)程的 TLB 表項。這樣在進(jìn)程切換的時候就不需要 flush TLB。但是仍然需要軟件管理和分配 ASID。

如何管理 ASID
ASID 和進(jìn)程 ID 肯定是不一樣的,別混淆二者。進(jìn)程 ID 取值范圍很大。但是ASID 一般是 8 或 16 ?bit。所以只能區(qū)分 256 或 65536 個進(jìn)程。我們的例子就以 8 位ASID說明。
所以我們不可能將進(jìn)程 ID 和 ASID 一一對應(yīng),我們必須為每個進(jìn)程分配一個ASID,進(jìn)程 ID 和每個進(jìn)程的 ASID 一般是不相等的。
每創(chuàng)建一個新進(jìn)程,就為之分配一個新的 ASID。當(dāng) ASID 分配完后,flush 所有 TLB,重新分配 ASID。
所以,如果想完全避免 flush TLB的話,理想情況下,運行的進(jìn)程數(shù)目必須小于等于 256。然而事實并非如此,因此管理 ASID 上需要軟硬結(jié)合。
Linux kernel 為了管理每個進(jìn)程會有個 task_struct 結(jié)構(gòu)體,我們可以把分配給當(dāng)前進(jìn)程的 ASID 存儲在這里。頁表基地址寄存器有空閑位也可以用來存儲ASID。當(dāng)進(jìn)程切換時,可以將頁表基地址和 ASID (可以從 task_struc t獲得)共同存儲在頁表基地址寄存器中。
當(dāng)查找 TLB 時,硬件可以對比 tag 以及 ASID 是否相等(對比頁表基地址寄存器存儲的 ASID 和 TLB 表項存儲的 ASID)。如果都相等,代表 TLB hit。否則TLB miss。當(dāng) TLB miss 時,需要多級遍歷頁表,查找物理地址。然后緩存到TLB 中,同時緩存當(dāng)前的 ASID。
多個進(jìn)程共享
我們知道內(nèi)核空間和用戶空間是分開的,并且內(nèi)核空間是所有進(jìn)程共享。既然內(nèi)核空間是共享的,進(jìn)程 A 切換進(jìn)程 B 的時候,如果進(jìn)程 B 訪問的地址位于內(nèi)核空間,完全可以使用進(jìn)程 A 緩存的 TLB。但是現(xiàn)在由于 ASID 不一樣,導(dǎo)致 TLB miss。
我們針對內(nèi)核空間這種全局共享的映射關(guān)系稱之為 global 映射。針對每個進(jìn)程的映射稱之為 non-global 映射。
所以,我們在最后一級頁表中引入一個 bit (non-global (nG) bit)代表是不是 global 映射。當(dāng)虛擬地址映射物理地址關(guān)系緩存到 TLB 時,將 nG bit 也存儲下來。
當(dāng)判斷是否命中 TLB 時,當(dāng)比較 tag 相等時,再判斷是不是 global 映射,如果是的話,直接判斷 TLB hit,無需比較 ASID。當(dāng)不是 global 映射時,最后比較 ASID 判斷是否 TLB hit。

什么時候應(yīng)該flush TLB
我們再來最后的總結(jié),什么時候應(yīng)該 flush TLB。
當(dāng) ASID 分配完的時候,需要 flush 全部 TLB,ASID 的管理可以使用 bitmap 管理,flush TLB 后 clear 整個 bitmap。
當(dāng)我們建立頁表映射的時候,就需要 flush 虛擬地址對應(yīng)的 TLB 表項。第一印象可能是修改頁表映射的時候才需要 flush TLB,但是實際情況是只要建立映射就需要 flush TLB。原因是,建立映射時你并不知道之前是否存在映射,例如,建立虛擬地址 A 到物理地址 B 的映射,我們并不知道之前是否存在虛擬地址 A 到物理地址 C 的映射情況,所以就統(tǒng)一在建立映射關(guān)系的時候 flush TLB。
原文作者:【一起學(xué)嵌入式
