dma訪問主存時 Cache和DMA一致性

2021-10-14 07:15:23 字數 3196 閱讀 8463

dma應該多多少少知道點吧。dma(direct memory access)是指在外置可以不用cpu干預,直接把資料傳輸到記憶體的技術。這個過程中可以把cpu解放出來,可以很好的提公升系統效能。那麼dma和cache有什麼關係呢?這也需要我們關注?

我們知道dma可以幫我們在i/o和主存之間搬運資料,且不需要cpu參與。快取記憶體是cpu和主存之間的資料互動的橋梁。而dma如果和cache之間沒有任何關係的話,可能會出現資料不一致。例如,cpu修改了部分資料依然躺在cache中(採用寫回機制)。dma需要將資料從記憶體搬運到裝置i/o上,如果dma獲取的資料是從主存那裡,那麼就會得到舊的資料。導致程式的不正常執行。這裡告訴我們,dma通過匯流排獲取資料時,應該先檢查cache是否命中,如果命中的話,資料應該來自cache而不是主存。但是是否先需要檢查cache呢?這取決於硬體設計。

還記得《cache組織方式》文章提到的pipt cache嗎?它是作業系統最容易管理的cache。pipt cache也很容易實現匯流排監視技術。什麼是匯流排監視技術呢?其實就是為了解決以上問題提出的技術,cache控制器會監視匯流排上的每一條記憶體訪問,然後檢查是否命中。根據命中情況做出下一步操作。我們知道dma操作的位址是實體地址,既然cache控制器可以監視匯流排操作,說明系統使用的cache必須是支援實體地址查詢的。而pipt完全符合條件。vivt是根據虛擬位址查詢cache,所以不能實現匯流排監視技術。vipt可以嗎?沒有別名的vipt也可以實現匯流排監視,但是有別名的情況的vipt是不行的(當然硬體如果強行檢查所有可能產生別名的cache line,或許也可以)。匯流排監視對於軟體來說是透明的,軟體不需要任何干涉即可避免不一致問題。但是,並不是所有的硬體都支援匯流排監視,同時作業系統應該相容不同的硬體。因此在不支援匯流排監視的情況下,我們在軟體上如何避免問題呢?

當我們使用dma時,首先是配置。我們需要在記憶體中申請一段記憶體當做buffer,這段記憶體用作需要使用dma讀取i/o裝置的快取,或者寫入i/o裝置的資料。為了避免cache的影響,我們可以將這段記憶體對映nocache,即不使用cache。對映的最小單位是4kb,因此在記憶體對映上至少4kb是nocahe的。這種方法簡單實用,但是缺點也很明顯。如果只是偶爾使用dma,大部分都是使用資料的話,會由於nocache導致效能損失。這也是linux系統中dma_alloc_coherent()介面的實現方法。

為了充分使用cache帶來的好處。我們對映依然採用cache的方式。但是我們需要格外小心。根據dma傳輸方向的不同,採取不同的措施。

如果dma負責從i/o讀取資料到記憶體(dma buffer)中,那麼在dma傳輸之前,可以invalid dma buffer位址範圍的快取記憶體。在dma傳輸完成後,程式讀取資料不會由於cache hit導致讀取過時的資料。

如果dma負責把記憶體(dma buffer)資料傳送到i/o裝置,那麼在dma傳輸之前,可以clean dma buffer位址範圍的快取記憶體,clean的作用是寫回cache中修改的資料。在dma傳輸時,不會把主存中的過時資料傳送到i/o裝置。

注意,在dma傳輸沒有完成期間cpu不要訪問dma buffer。例如以上的第一種情況中,如果dma傳輸期間cpu訪問dma buffer,當dma傳輸完成時。cpu讀取的dma buffer由於cache hit導致取法獲取最終的資料。同樣,第二情況下,在dma傳輸期間,如果cpu試圖修改dma buffer,如果cache採用的是寫回機制,那麼最終寫到i/o裝置的資料依然是之前的舊資料。所以,這種使用方法程式設計開發人員應該格外小心。這也是linux系統中流式dma對映dma_map_single()介面的實現方法。

假設我們有2個全域性變數temp和buffer,buffer用作dma快取。初始值temp為5。temp和buffer變數毫不相關。可能buffer是當前dma操作程序使用的變數,temp是另外乙個無關程序使用的全域性變數。

int temp = 5;

char buffer[64] = ;

假設,cacheline大小是64位元組。那麼temp變數和buffer位於同乙個cacheline,buffer橫跨兩個cacheline。

假設現在想要啟動dma從外設讀取資料到buffer中。我們進行如下操作:

按照上一節的理論,我們先invalid buffer對應的2行cacheline。

啟動dma傳輸。

當dma傳輸到buff[3]時,程式改寫temp的值為6。temp的值和buffer[0]-buffer[60]的值會被快取到cache中,並且標記dirty bit。

dma傳輸還在繼續,當傳輸到buff[50]的時候,其他程式可能讀取資料導致temp變數所在的cacheline需要替換,由於cacheline是dirty的。所以cacheline的資料需要寫回。此時,將temp資料寫回,順便也會將buffer[0]-buffer[60]的值寫回。

在第4步中,就出現了問題。由於寫回導致dma傳輸的部分資料(buff[3]-buffer[49])被改寫(改寫成了沒有dma傳輸前的值)。這不是我們想要的結果。因此,為了避免出現這種情況。我們應該保證dma buffer不會跟其他資料共享cacheline。所以我們要求dma buffer首位址必須cacheline對齊,並且buffer的大小也cacheline對齊。這樣就不會跟其他資料共享cacheline。也就不會出現這樣的問題。

linux中,我們要求dma buffer不能是從棧和全域性變數分配。這個主要原因是沒辦法保證buffer是cacheline對齊。我們可以通過kmalloc分配dma buffer。這就要求某些不支援匯流排監視的架構必須保證kmalloc分配的記憶體必須是cacheline對齊。所以linux提供了乙個巨集,保證kmalloc分配的object最小的size。例如arm64平台的定義如下:

#define arch_dma_minalign	(128)
arm64使用的cacheline大小一般是64或者128位元組。為了保證分配的記憶體是cacheline對齊,取了最大值128。而x86_64平台則沒有定義,因為x86_64硬體保證了dma一致性。所以我們看到x86_64平台,slub管理的kmem cache最小的是kmalloc-8。而arm64平台,slub管理的kmem cache最小的是kmalloc-128。其實arm64平台分配小記憶體的代價挺高的。即使申請8位元組記憶體,也給你分配128位元組的object,確實有點浪費。

dma訪問主存時 為什麼 CPU 訪問硬碟很慢

機械硬碟 hard disk drive hdd 和固態硬碟 solid state drive ssd 是兩種最常見的硬碟,作為計算機的外部儲存,cpu 想要訪問它們儲存的資料需要很長時間,如下表所示,在 ssd 中隨機訪問 4kb 資料所需要的時間是訪問主存的 1,500 倍,機械磁碟的尋道時間...

Cache和主存位址對映關係

cache的位址映像方式中,發生塊衝突次數最小的是 a 全相聯映像 b 組相聯映像 c 直接映像 d 無法確定的 全相聯映像塊衝突最小,其次為組相聯映像,直接映像塊衝突最大。容量為64塊的cache採用組相聯方式映像,字塊大小為128位元組,每4塊為一組,若主容量為4096塊,且以字編址,那麼主存位...

主存和cache的位址對映

cache是一種高速緩衝暫存器,是為解決cpu和主存之間速度不匹配而採用的一項重要技術。主存與cache的位址對映方式有全相聯方式 直接方式和組相聯方式三種。多對一的對映關係,但乙個主存塊只能拷貝到cache的乙個特定行位置上去。cache的行號i和主存的塊號j有如下函式關係 i j mod m m...