簡體   English   中英

從 MMIO 預取?

[英]Prefetch from MMIO?

是否可以為 PCIe BAR 中的 MMIO 區域支持的地址發出預取(並通過 UC 或 WC 頁表條目映射)? 我目前正在為此地址發出負載,這會導致超線程停止相當長的一段時間。 通過PREFETCHNTA有一個非臨時訪問提示,所以這似乎是可能的。

如果可能的話,您是否知道預取值存儲在哪里以及在我能夠為它發出加載之前可能導致它失效的原因是什么? 例如,如果我為不相關的東西發出諸如sfence類的同步指令,這會導致預取值失效嗎?


來自英特爾軟件開發手冊:

“來自不可緩存或 WC memory 的預取被忽略。......應該注意的是,處理器可以自由地從系統 memory 區域中推測性地獲取和緩存數據,這些區域被分配了允許推測性讀取的內存類型(即 WB,WC和 WT memory 類型)。”


MMIO 區域所在的 PCIe BAR 被標記為可預取,因此我不確定這是否意味着預取將適用於上面手冊中的語言。

我要感謝 Peter Cordes、John D McCalpin、Neel Natu、Christian Ludloff 和 David Mazières 幫助解決這個問題!

為了預取,您需要能夠將 MMIO 讀取存儲在 CPU 緩存層次結構中。 當您使用 UC 或 WC 頁表條目時,您不能這樣做。 但是,如果您使用 WT 頁表條目,則可以使用緩存層次結構。

唯一需要注意的是,當您使用 WT 頁表條目時,以前的 MMIO 讀取的陳舊數據可能會滯留在緩存中。 您必須在軟件中實施一致性協議以從緩存中清除陳舊的緩存行並通過 MMIO 讀取讀取最新數據。 這對我來說沒問題,因為我控制 PCIe 設備上發生的事情,所以我知道什么時候刷新。 但是,您可能不知道在所有情況下何時刷新,這可能會使這種方法對您沒有幫助。

以下是我設置系統的方式:

  1. 將 map 到 PCIe BAR 的頁表條目標記為 WT。 您可以為此使用ioremap_wt() ioremap_change_attr()如果 BAR 已經映射到內核,則可以使用 ioremap_change_attr())。

  2. 根據https://sandpile.org/x86/coherent.htm ,PAT類型和MTRR類型之間存在沖突。 PCIe BAR 的 MTRR 類型也必須設置為 WT,否則 PAT WT 類型將被忽略。 您可以使用以下命令執行此操作。 請務必使用 PCIe BAR 地址(您可以使用lspci -vv )和 PCIe BAR 大小更新命令。 大小是以字節為單位的十六進制值。

echo "base=$ADDRESS size=$SIZE type=write-through" >| /proc/mtrr
  1. 作為此時的快速檢查,您可能希望在循環中向 BAR 中的同一緩存行發出大量 MMIO 讀取。 在第一次 MMIO 讀取后,您應該會看到每次 MMIO 讀取的成本 go 大幅下降。 第一次 MMIO 讀取仍然會很昂貴,因為您需要從 PCIe 設備中獲取值,但后續讀取應該便宜得多,因為它們都是從緩存層次結構中讀取的。

  2. 您現在可以向 PCIe BAR 中的地址發出預取,並將預取的緩存行存儲在緩存層次結構中。 Linux 有prefetch() function 來幫助發出預取。

  3. 您必須在軟件中實施一個簡單的一致性協議,以確保從緩存中清除由 PCIe BAR 支持的陳舊緩存行。 您可以使用clflush刷新陳舊的緩存行。 Linux 有clflush() function 來幫助解決這個問題。

關於clflush在這個場景中的注意事項:由於memory類型是WT,所以每個store都會去cache中的cache line和MMIO。 因此,從 CPU 的角度來看,緩存中緩存行的內容始終與 MMIO 的內容相匹配。 因此, clflush只會使緩存中的緩存行無效——它不會將陳舊的緩存行寫入 MMIO。

  1. 請注意,在我的系統中,我會在clflush之后立即發出預取。 但是,下面的代碼是不正確的:
clflush(address);
prefetch(address);

此代碼不正確,因為根據https://c9x.me/x86/html/file_module_x86_id_252.html ,預取可以在clflush之前重新排序。 因此,可以在clflush之前發出預取,並且預取可能會在clflush發生時失效。

要解決此問題,根據鏈接,您應該在clflush和預取之間發出cpuid

int eax, ebx, ecx, edx;

clflush(address);
cpuid(0, &eax, &ebx, &ecx, &edx);
prefetch(address);

Peter Cordes 說用lfence代替上面的cpuid就足夠了。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM