tlb刷新的懶惰模式

tlb是一種緩存,緩存的內容是虛擬地址和物理頁面的對應關係,每當一個新進程投入運行的時候,tlb就要被刷新,否則就會出現混亂,但是因爲linux 的所有進程的內核部分的頁面映射是一樣的,而且內核線程又不訪問用戶空間,所以當內核線程運行的時候,tlb是不必馬上刷新的,呵呵,這又給內核線程的淫 亂創造了機會。爲啥這麼說呢?因爲tlb沒有被刷新,那麼比如說內核線程運行前一個用戶線程t在運行,其tlb中的虛擬地址a對應物理頁面p,那麼內核線 程運行後由於沒有刷新tlb,那麼完全可以訪問虛擬地址a,實際上根本就沒有重新加載cr3,內核線程還是用的原來進程的頁表。
在smp多處理器的機制下,tlb的懶惰刷新模式更有說頭,在單處理器的情況之下,tlb的刷新是主動調用的,如果你想刷新tlb,那麼你就重新加載 cr3,這個行爲隻影響到你自己,內核線程並不訪問用戶空間,那麼到底在用誰的頁表就無關緊要了,大家的內核部分的頁表映射是一樣的,因此爲了節省開銷,就引入了懶惰模式,內核線程不必接受刷新tlb的要求。再smp下,情況就複雜了,試想:一個內核線程搶佔了一個用戶線程,借用了該用戶線程的頁目錄,頁表,然後這個用戶線程在內核線程執行的同時被調度到了另外一個cpu上執行,並且在該內核線程退出之前那個用戶線程就在另一個cpu上退出了,退出前它肯 定要求刷新所有的tlb,如果此時內核線程運行的cpu還不接受刷新請求的話,那就麻煩了,那個用戶線程的頁表馬上全部被釋放,然後借用這些頁表的內核線 程將會無窮無盡的發生缺頁,而缺頁在內核,又不是vmalloc,這是不應該的,最不應該的當前的pgd已經被釋放了,該pgd佔用的那塊內存可能有別的 用途。因此此時即使運行內核線程的cpu也要接受刷新tlb的請求,調用一個叫做leave_mm的函數來回歸本位,smp的刷新是靠中斷來觸發的,而不 僅僅是主動調用的。具體實現懶惰模式的時候需要維持一個數據結構,下面簡單說說linux懶惰模式的實現。
每個cpu都有一個每cpu變量來存儲懶惰模式的tlb的當前狀態,該結構有兩個字段,一個是狀態值,代表懶惰與否,另一個是一個mm_struct結構,代表當前活躍的mm(對於普通用戶線程就是它的mm,對於內核線程就是它借用的用戶線程的mm),該mm有一個cpu掩碼,指示哪些cpu接受tlb 刷新中斷,如果一個內核線程投入運行,那麼它就將這個每cpu變量的狀態字設置爲懶惰模式,然後該cpu在第一次接受到tlb刷新中斷請求時將自己從這個 每cpu變量的mm字段的cpu掩碼中剝離,這就意味着本cpu在懶惰模式,不再接受刷新tlb的請求。接受tlb刷新請求的cpu掩碼一定要明白和誰相關,它是和地址空間相關的,當然是這樣的,tlb本質上就是一種地址映射的緩存,於是linux內核的這部分代碼理解起來就很簡單了,再次強調,tlb刷 新以mm_struct爲中心,以cpu爲對象在單cpu下直接重載cr3,在smp下則是向此mm_struct的tlb相關的cpu掩碼代表的cpu 們發送ipi(處理器間中斷)


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