1. 程式人生 > >DMA導致的CACHE一致性問題解決方案

DMA導致的CACHE一致性問題解決方案

轉載時請標明作者 waterhawk,  原文地址:http://blog.csdn.net/waterhawk/article/details/50723677

先簡單說一下DMA的CACHE一致性是個啥問題。

複製:

CPU在訪問記憶體時,首先判斷所要訪問的內容是否在Cache中,如果在,就稱為“命中(hit)”,此時CPU直接從Cache中呼叫該內容;否則,就 稱為“ 不命中”,CPU只好去記憶體中呼叫所需的子程式或指令了。CPU不但可以直接從Cache中讀出內容,也可以直接往其中寫入內容。由於Cache的存取速 率相當快,使得CPU的利用率大大提高,進而使整個系統的效能得以提升。
Cache的一致性就是直Cache中的資料,與對應的記憶體中的資料是一致的。


DMA是直接操作匯流排地址的,這裡先當作實體地址來看待吧(系統匯流排地址和實體地址只是觀察記憶體的角度不同)。如果cache快取的記憶體區域不包括DMA分配到的區域,那麼就沒有一致性的問題。但是如果cache快取包括了DMA目的地址的話,會出現什麼什麼問題呢?
問題出在,經過DMA操作,cache快取對應的記憶體資料已經被修改了,而CPU本身不知道(DMA傳輸是不通過CPU的),它仍然認為cache中的數 據就是記憶體中的資料,以後訪問Cache對映的記憶體時,它仍然使用舊的Cache資料。這樣就發生Cache與記憶體的資料“不一致性”錯誤。

來個外語的:

The cache.
This entity is essentially "memory state" as the flush architecture views it. In general it has the following properties:
It will always hold copies of data which will be viewed as uptodate by the local processor.
Its proper functioning may be related to the TLB and process/kernel page mappings in some way, that is to say they may depend upon each other.
It may, in a virtually cached configuration, cause aliasing problems if one physical page is mapped at the same time to two virtual pages, and due to to the bits of an address used to index the cache line, the same piece of data can end up residing in the cache twice, allowing inconsistancies to result.
Devices and DMA may or may not be able to see the most up to date copy of a piece of data which resides in the cache of the local processor.
Currently, it is assumed that coherence in a multiprocessor environment is maintained by the cache/memory subsystem. That is to say, when one processor requests a datum on the memory bus and another processor has a more uptodate copy, by whatever means the requestor will get the uptodate copy owned by the other processor.
(NOTE: SMP architectures without hardware cache coherence mechanisms are indeed possible, the current flush architecture does not handle this currently. If at at some point a Linux port to some system where this is an issue occurrs, I will add the necessary hooks. But it will not be pretty.)

以前接觸比較多的幾種記憶體機制:

帶CACHE的記憶體有兩種,寫回(writeback)、寫穿(writethrough);或者非CACHE空間。

搞DMA的時候發現非CACHE其實還可以細分兩種,一致(coherent),寫快取(writecombine)。

其實後面這兩種,網上也找不到標準的翻譯方法,以前書上也沒具體介紹過,純屬自己瞎翻譯。

又一個因為複製貼上導致的網路上流行結果:

一致性DMA對映申請的快取區能夠使用cache,並且保持cache一致性。一致性對映具有很長的生命週期,在這段時間內佔用的對映暫存器,即使不使用也不會釋放。生命週期為該驅動的生命週期
千萬別被這句話誤導了,這句是錯誤的。因為,同樣的網頁裡以下這段是正確的。
dma_alloc_coherent 在 arm 平臺上會禁止頁表項中的 C (Cacheable) 域以及 B (Bufferable)域。
而 dma_alloc_writecombine 只禁止 C (Cacheable) 域.

 C 代表是否使用高速緩衝儲存器, 而 B 代表是否使用寫緩衝區。

這樣,dma_alloc_writecombine 分配出來的記憶體不使用快取,但是會使用寫緩衝區。而 dma_alloc_coherent  則二者都不使用。
C B 位的具體含義
0 0 無cache,無寫緩衝;任何對memory的讀寫都反映到總線上。對 memory 的操作過程中CPU需要等待。
0 1 無cache,有寫緩衝;讀操作直接反映到總線上;寫操作,CPU將資料寫入到寫緩衝後繼續執行,由寫緩衝進行寫回操作。
1 0 有cache,寫通模式;讀操作首先考慮cache hit;寫操作時直接將資料寫入寫緩衝,如果同時出現cache hit,那麼也更新cache。
1 1 有cache,寫回模式;讀操作首先考慮cache hit;寫操作也首先考慮cache hit。

效率最高的寫回,其次寫通,再次寫緩衝,最次非CACHE一致性操作。

其實,寫緩衝也是一種非常簡單得CACHE,為何這麼說呢。

我們知道,DDR是以突發讀寫的,一次讀寫總線上實際會傳輸一個burst的長度,這個長度一般等於一個cache line的長度。

cache line是32bytes。即使讀1個位元組資料,也會傳輸32位元組,放棄31位元組。

寫緩衝是以CACHE LINE進行的,所以寫效率會高很多。

簡單寫了一個測試程式,驗證在ARM平臺上的DMA一致性問題。


dst 地址0xab800000, dst 原始資料0x00,長度8

src 地址0xac200000, src 原始資料0x62,長度8

為保證驗證可靠,源地址是一致性記憶體,目的地址是寫回記憶體。

實驗步驟:

1. 讀兩個記憶體資料,用於後續比較,因為源地址是非CACHE的,所以修改一定會被寫到DDR上。

2. 對目的地址的讀8遍。

3. DMA拷貝源到目的地址。

4. 讀目的地址。

實驗結果說明,目的地址讀取到的值依然是0.

解釋:

第二步驟實際作用是為了在cache中,目的地址不被替換出cache。

我們知道一般OS採用的CACHE替換演算法都是基於區域性性原理,當一個數據在連續時間內經常被操作,則對應的cache line就有更大概率被保留。

而DMA拷貝完成前,只要目的地址的CACHE LINE沒有被替換出去,則DMA完成後,CPU會訪問目的地址時,一定是原始資料。

也就是圖中最終結果,目的地址資料依然是原始資料0x00.

此時CPU訪問的是CACHE,但也有可能訪問的是DDR,甚至可能出現正確結果0x62。

1. 訪問CACHE

這是最簡單的了,因為CACHE LINE沒有被替換,CACHE HIT。所以,CPU不知道DDR資料已經變化,返回原始資料。

2. 訪問DDR

同樣好理解,因為如果之前設定了目的地址的資料,這時該cache line會成為dirty狀態。

在DMA完成之後,如果該cache line被替換到DDR上,那麼DDR的資料就又成了原始資料。

目的DDR的變化是 0x00 --> 0x62 (DMA)  --> 0x00 (cache writeback).

3. 出現正確結果

這個涉及的內容最多。如果之前沒有設定目的地址的值,只是讀目的地址的值,則該cache line是乾淨的。

在DMA完成之後,該cache line如果被替換,則cache line裡的0x00錯誤資料會被丟棄(因為是乾淨的,CACHE認為沒必要寫回)

而CPU在沒有CACHE HIT時,就會從DDR上讀到正確資料0x62.

這同樣說明2個道理,

第一,錯誤的DMA操作,導致了資料一致性的問題,但一定條件下,這個錯誤是不會被感知到的。

第二,即使讀到的100%是正確資料,你也不能從經驗斷定這個程式是對的,但只要有一次錯誤,就可以認為程式是錯誤的了。

我的解決方案:

先上結果


在DMA拷貝前,進行一次CACHE CLEAN,或CACHE FLUSH。

DMA拷貝完成之後,進行一次CACHE FLUSH。

就能很大概率避免一致性的問題。

這個解決方案不是標準的解決方案,使用一致性記憶體或寫緩衝記憶體,是無論何時100%正確的。

而這個方案,是一個很大概率的正確方案,在絕大部分合理場景下,可以以最高效率訪問記憶體。

其實,這個解決方案,大概率按照第一個實驗的條件3執行的。

為什麼要執行這2個操作呢。

DMA拷貝前,CACHE CLEAN保證了條件3中所述,目的地址的CACHE LINE是乾淨的。最大概率保證DMA傳輸時間內,不會再有寫回動作。

DMA拷貝完成後,CACHE FLUSH保證了,CACHE會重新構建,目的地址的值一定是從DDR上讀取最新資料。

而在拷貝過程中,CPU如果只是讀取目的地址,會直接訪問CACHE,而不訪問DDR。

這個方案使用起來很簡單,但一定要符合以下條件才能取得合理結果(適用於大部分使用DMA的情況):

1. DMA的資料一定是大資料搬移(至少是DCACHE的10倍以上)。因為僅1次CACHE FLUSH就會毀掉你整個DCACHE的資料,至少嵌入式平臺也是128KB以上,這些資料訪問DDR重建也是要時間的。 (這一條保證使用DMA的時間效率)

2.在DMA拷貝期間,不會對目的地址進行寫操作。(這一條可以保證資料絕對正確)

OK,本篇完成了。最後看到linux下老外寫了一個文章,說DMA導致的一致性,linux有一套解決方案,但網上很少人有說,慢慢看。

文章地址:http://www.tldp.org/LDP/khg/HyperNews/get/memory/flush.html

轉載時請標明作者 waterhawk,  原文地址:http://blog.csdn.net/waterhawk/article/details/50723677