【Linux】 cache 一致性

一塊mem或者外設寄存器可能會被CPU和另外一個master 去訪問,導致data 不一致的問題。

工程中一般有兩種情況:

(1)寄存器地址空間。寄存器是CPU與外設交流的接口,有些狀態寄存器是由外設根據自身狀態進行改變,這個操作對CPU是不透明的。有可能這次CPU讀入該狀態寄存器,下次再讀時,該狀態寄存器已經變了,但是CPU還是讀取的cache中緩存的值。但是寄存器操作在kernel中是必須保證一致的,這是kernel控制外設的基礎,IO空間通過ioremap進行映射到內核空間。ioremap在映射寄存器地址時頁表是配置爲uncached的。數據不走cache,直接由地址空間中讀取。保證了數據一致性。

這種情況kernel已經保證了data的一致性,應用場景簡單。

(2)DMA緩衝區的地址空間。DMA操作對於CPU來說也是不透明的,DMA導致內存中數據更新,對於CPU來說是完全不可見的。反之亦然,CPU寫入數據到DMA緩衝區,其實是寫到了cache,這時啓動DMA,操作DDR中的數據並不是CPU真正想要操作的。

這種情況是,CPU 和DMA 都可以異步的對mem 進行操作,導致data不一致性。

對於cpu 和 dma 都能訪問的mem ,kernel有專業的管理方式,分爲兩種:1. mem 硬件上uncache 2. 使用過程中,通過flush cache 保證data 一致性。

通用DMA層主要分爲2種類型的DMA映射:

(1)一致性映射,代表函數:
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);

一般驅動使用多,申請一片uncache mem ,這樣無需考慮data 一致性。代碼流程:對page property,也就是是kernel頁管理的頁面屬性設置成uncache,在缺頁異常填TLB時,該屬性就會寫到TLB的存儲屬性域中。保證了dma_alloc_coherent映射的地址空間是uncached的。

dma_alloc_coherent首先對分配到的緩衝區進行cache刷新,之後將該緩衝區的頁表修改爲uncached,以此來保證之後DMA與CPU操作該塊數據的一致性。

(2)流式DMA映射,代表函數:
dma_addr_t dma_map_single(struct device *dev, void *cpu_addr, size_t size, enum dma_data_direction dir)
void dma_unmap_single(struct device *dev, dma_addr_t handle, size_t size, enum dma_data_direction dir)

void dma_sync_single_for_cpu(struct device *dev, dma_addr_t handle, size_t size, enum dma_data_direction dir)
void dma_sync_single_for_device(struct device *dev, dma_addr_t handle, size_t size, enum dma_data_direction dir)

int dma_map_sg(struct device *, struct scatterlist *, int, enum dma_data_direction);
void dma_unmap_sg(struct device *, struct scatterlist *, int, enum dma_data_direction);

流式,這個定義應該是基於數據的方向流,來操作cache,失效或者清除,所以稱爲流式。 

dma_map_single爲例,包括DMA_FROM_DEVICE,設備通過DMA寫到mem,CPU 讀data時,需要做invalid無效。方向爲DMA_TO_DEVICE,則v7_dma_clean_range寫回該段地址cache。保證了cache數據一致性

當只有一個緩衝區要被傳輸的時候,第一步:使用dma_map_single函數映射它.這句話的實質是說,mem中的data流向變動時,需要flush cache。比如,mem一直由CPU操作,這時mem要通過DMA送給sdio,那麼必須通過這個API來傳送mem,API實質是根據流向flush cache。第二步:返回值是總線地址,把它傳遞給設備。第三步:當傳輸完畢後,使用dma_unmap_single函數刪除映射。到此cache driver 自成一體,很簡單了。


流式DMA映射的幾條原則:
*緩衝區只能用於這樣的傳送,即其傳送方向匹配於映射時給定的方向。
*一旦緩衝區被映射,它將屬於設備,而不是處理器。
直到緩衝區被撤銷映射前,驅動程序不能以任何方式訪問其中的內容。
*在DMA處於活動期間內,不能撤銷對緩衝區映射,否則會嚴重破壞系統的穩定性。

驅動程序需要不經過撤銷映射就訪問流式DMA緩衝區的內容,有如下調用:
void dma_sync_single_for_cpu(struct device *dev,dma_handle_t bus_addr,
size_t size,enum dma_data_direction direction);
將緩衝區所有權交還給設備:
void dma_sync_single_for_device(struct device *dev,dma_handle_t bus_addr,
size_t size,enum dma_data_direction direction);


可以看出來,LDD3講到,流式DMA映射對於CPU何時可以操作DMA緩衝區有嚴格的要求,只能等到dma_unmap_single後CPU纔可以操作該緩衝區。究其原因,是因爲流式DMA緩衝區是cached,在map時刷了下cache,在設備DMA完成unmap時再刷cache(根據數據流向寫回或者無效),來保證了cache數據一致性,在unmap之前CPU操作緩衝區是不能保證數據一致的。因此kernel需要嚴格保證操作時序。當然kernel也提供函數dma_sync_single_for_cpu與dma_sync_single_for_device,可以在未釋放時操作緩衝區,很明顯這2個函數實現中肯定是再次進行刷新cache的操作保證數據一致性。
DMA的2種類型映射都分析完了,很清晰的看出一致性映射與流式DMA映射核心區別就是在於緩衝區頁表映射是否爲cached,一致性映射採用uncached頁表保證了CPU與外設都可以同時訪問。
不過這些都是內核爲驅動開發者已經封裝好的接口函數,驅動開發者並不需要關心cache問題,只需要按照LDD3的規定調用這些接口即可。這也就是爲什麼在驅動中很少見到cache操作,內核代碼將cache操作做到對驅動不透明瞭。
這也讓我想起了在開發網卡驅動時,DMA描述符的分配是一致性映射,是因爲DMA描述符需要CPU與設備同時操作。而數據收發緩衝區分配是流式的,隨用隨分配,用完釋放後CPU纔可以操作數據!

到目前爲止,我所接觸到的內核TLB映射,做了uncached映射的只有2個:寄存器空間(ioremap)和一致性DMA緩衝區(dma_alloc_coherent),其他地址空間都是cached,來保證系統性能。


 

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