TLB一致性維護

yooooooo發表於2024-06-02

TLB 是頁表項的物理 cache,用於加速虛擬地址到實體地址的轉換。CPU 在訪問一個虛擬地址時,首先會在 TLB 中查詢,如果找不到對應的表項,那麼就稱之為 TLB miss,此時就需要去記憶體裡查詢頁表,如果頁表項是合法的,那麼就會把它新增到 TLB 中。如果核心修改了頁表,那麼就需要主動的去清空一下當前的 TLB。

在 ARM64 上,清空 TLB 的指令是 TLBI,在 Linux 中,與 TLB 清空相關的宏都在 arch/arm64/include/asm/tlbflush.h 檔案中定義。清空 TLB 的一般流程在檔案開頭的注視裡有說明:

 *    DSB ISHST    // Ensure prior page-table updates have completed
 *    TLBI ...    // Invalidate the TLB
 *    DSB ISH        // Ensure the TLB invalidation has completed
 *      if (invalidated kernel mappings)
 *        ISB    // Discard any instructions fetched from the old mapping

在 TLBI 指令執行前後需要幾個記憶體屏障指令的輔助,來防止在 TLB 清空過程中發生的不確定情況。主要的幾個宏如下所示:

名稱 說明
flush_tlb_all() 核心+使用者空間的地址在所有的CPU上都清空
flush_tlb_mm(mm) 把使用者空間的地址在所有的CPU上都清空
flush_tlb_range(vma, start, end) 使用者空間的一段範圍地址清空
flush_tlb_kernel_range(start, end) 核心空間的一段範圍地址清空
flush_tlb_page(vma, adds) 使用者空間的一個 page 地址對映清空

flush_tlb_kernel_range使用

flush_tlb_kernel_range 為例:
flush_tlb_kernel_range 是 Linux 核心中的一個函式,用於使一段範圍內的翻譯後備緩衝區 (TLB) 條目失效。TLB 是一個快取,用於儲存最近的從虛擬記憶體地址到實體記憶體地址的轉換,這有助於加快使用虛擬記憶體系統的記憶體訪問速度。

主要作用:

  • 使 TLB 條目失效flush_tlb_kernel_range 的主要作用是確保指定範圍內的 TLB 中任何過時或無效的條目都被移除。當核心修改頁表(例如在記憶體管理操作中)時,需要將這些變化反映到 TLB 中。

使用場景:

  • 核心記憶體管理:該函式特別用於核心記憶體管理的上下文中。當核心在自己的地址空間(核心虛擬記憶體)中更改對映時,必須確保 TLB 不包含指向舊對映的陳舊條目。

使用方法:

  • 引數:該函式通常接受兩個引數,指定要重新整理的地址範圍的開始和結束。
    • start:要重新整理的範圍的起始虛擬地址。
    • end:要重新整理的範圍的結束虛擬地址。

操作方式:

  • 與架構相關flush_tlb_kernel_range 的實現與具體的 CPU 架構相關,因為不同的 CPU 架構(例如 x86,ARM)的 TLB 結構和操作方式不同。
  • 使 TLB 條目失效:該函式使用特定的 CPU 指令使對應地址範圍的 TLB 條目失效。

示例使用場景:

當核心更新其頁表時,例如重新對映核心記憶體、新增新頁面或更改訪問許可權時,需要使受影響的 TLB 條目失效,以確保 CPU 不會使用過時的轉換。如果不這樣做,可能會導致記憶體訪問錯誤,帶來潛在的安全風險或系統不穩定性。

程式碼示例:

一個函式原型的示例可能如下所示(實際實現細節因架構而異):

void flush_tlb_kernel_range(unsigned long start, unsigned long end);

總結:

總之,flush_tlb_kernel_range 是維護 TLB 一致性的重要函式,用於核心記憶體管理的上下文中。它確保核心頁表中的變化準確反映到 TLB 中,從而防止陳舊條目導致錯誤的記憶體訪問。

flush_tlb_kernel_range 實現

flush_tlb_kernel_range 的實現是與具體的 CPU 架構密切相關的。不同的架構有不同的 TLB 管理方式,因此該函式的實現方式也會有所不同。下面我們以 x86 架構為例,簡單介紹一下 flush_tlb_kernel_range 的可能實現方式。

x86 架構實現示例

在 x86 架構上,TLB 重新整理可以透過重新載入控制暫存器 CR3 來實現,這會導致整個 TLB 被重新整理。對於特定範圍的 TLB 重新整理,可以使用頁表條目無效(INVLPG)指令。

示例程式碼:

以下是一個可能的 flush_tlb_kernel_range 實現,針對 x86 架構:

#include <linux/mm.h>
#include <linux/smp.h>
#include <asm/tlbflush.h>

void flush_tlb_kernel_range(unsigned long start, unsigned long end)
{
    unsigned long addr;

    // 對於每個地址,使用INVLPG指令
    for (addr = start; addr < end; addr += PAGE_SIZE) {
        __flush_tlb_one(addr);
    }

    // 同時在多處理器系統上處理
    smp_mb();
    smp_call_function(flush_tlb_mm_range, &current->mm, 1);
}

解釋:

  1. 迴圈重新整理每個頁

    • 使用 INVLPG 指令重新整理指定範圍內的每個頁。
    • __flush_tlb_one(addr) 是一個內聯彙編函式,使用 INVLPG 指令重新整理指定的地址。
  2. 處理多處理器系統

    • smp_mb() 確保記憶體屏障,確保前面的 TLB 重新整理操作在後續操作之前完成。
    • smp_call_function(flush_tlb_mm_range, &current->mm, 1) 在多處理器系統上呼叫,以確保所有 CPU 都執行 TLB 重新整理操作。

通用實現框架

對於不同的架構,具體實現方式會有所不同,但總體的實現框架大致如下:

  1. 識別要重新整理的地址範圍:確定起始地址和結束地址。
  2. 使用架構特定的指令:使用特定的指令來重新整理對應的 TLB 條目。
  3. 考慮多核處理器的同步:在多處理器系統上同步 TLB 重新整理操作,以確保所有 CPU 的 TLB 都被重新整理。

參考資料

可以參考 Linux 核心原始碼中的實際實現。例如,可以檢視 arch/x86/include/asm/tlbflush.h 檔案中的實現細節。不同架構的實現可以在相應的架構目錄中找到。

總結

flush_tlb_kernel_range 的具體實現依賴於 CPU 架構,x86 架構通常透過 INVLPG 指令和 CR3 暫存器重新載入來實現 TLB 重新整理。上述示例展示了一個基本實現,具體實現應參考 Linux 核心原始碼,並根據具體需求和架構進行調整。