一,cache概述
從下圖可以看出,從M7內核才開始有的cache,這對於從M0,M3,M4一路走來的小伙伴來說,多了一個cache就多了一個障礙。
Cortex-M7 core with 32K/32K L1 I/D-Cache!這提供了極高的性能,代碼無論是從芯片上的內存,外部閃存,還是外部內存運行!介紹種類包括:L1 cache, memory types, attributes and MPU(Memory Protection Unit). 指導用戶如何使用緩存開發以正確和高性能方式運行應用程序。
cache價值:由於對這些存儲器的子系統,比如 OCRAM,FlexSPI,SEMC的訪問可能需要多個周期(特別是在具有多個等待狀態的外部存儲器接口上),因此L1緩存的設計目的是加快對存儲器的讀/寫操作。這帶來了很大的性能提升。
I/DTCM(FlexRAM banks configured as TCM)由CPU核直接訪問,繞過L1緩存。因此,建議將關鍵代碼和數據放入TCM中,就像向量表vector table一樣
flush:在有的書中稱作invalidate
存貯器結構如下圖:
Cortex-M7是6級雙發射流水線,下圖是一個示意圖。
高速緩存cache基礎知識:
程序執行的局部規律性包括時間局部規律性和空間局部規律性。
時間局部規律性:在程序執行過程中,剛剛被訪問的信息可能很快被再次訪問,典型情況是程序中存在大量的循環。
空間局部規律性:在程序執行過程中,那些與被訪問的地址相臨近的信息也有可能很快被訪問。典型情況是程序中存在大量的順序執行。
按照程序執行的“局部性規律”,程序中的數據或者代碼被訪問后,該數據和代碼以及鄰近的數據代碼近期再被訪問的概率要遠大於,近期未被訪問的數據或者代碼被訪問的概率。因此,當數據或代碼被訪問后,被認為是經常訪問的數據和代碼,將被存入到cache,當再次訪問該數據或者代碼時直接從該cache讀取數據或者代碼的值,而不是到內存中重新讀取。
有了cache后,core對內存中的數據訪問流程如下圖所示:
二.Cortex-M7內核的L1 Cache
L1 Cache由多行內存區組成,每行有32字節,每行都配有一個地址標簽。
數據緩沖DCache:是每4行為一組,稱為4-way set associative。
指令緩沖區ICache:是2行為一組, 稱為2-way set-associative
這樣節省地址標簽,不用每個行都標記一個地址。
Cache hit:要訪問的數據/指令在cache里面.
Cache miss:要訪問的數據/指令不在cache里面.
Core 讀cache時:
若hit,則直接從cache讀出數據即可。
若miss,有兩種處理方式:
>read through ,直接從內存中讀出
>read allocate,先把數據讀到cache,再從cache讀出。如果 CPU 要讀取的 SRAM 區數據在 Cache 中已經加載好,就可以直接從 Cache 里面讀取。如果沒有,就用到配置 read allocate 了,意思就是在 Cache 里面開辟區域,將 SRAM 區數據加載進來,后續的操作,CPU 可以直接從 Cache 里面讀取,從而時間加速。
Core 寫cache時:
若hit,兩種處理方式:
>write-through模式:
可以直接寫到內存中同時放到Cache里面,優點是內存和cache同步更新,沒有多總線訪問造成的數據一致性問題,缺點是無法在寫操作上面發揮性能。
>Write back模式:Cache line會被標為dirty,等到此行被evicted(驅逐,趕出, flush)時,才會執行實際的寫操作,將Cache Line里面的數據寫入到相應的存 儲區。Write back安全隱患:如果 Cache 命中的情況下,此時僅 Cache 更新了,而 SRAM,SDRAM 沒有更新,那么 DMA 直接從 SRAM 里面讀出來的就是錯誤的。
若miss,兩種處理方式:
>write-allocate:先把要寫的數據載入到cache,寫cache,然后再flush進內存。
>no-write-allocate:直接寫入內存。
Cache 命中是訪問的地址落在了給定的 Cache Line 里面,所以硬件需要做少量的地址比較工作,以檢查此地址是否被緩存。如果命中了,將用於緩存讀操作或者寫操作。如果沒有命中,則分配和標記新行,填充新的讀寫操作。如果所有行都分配完畢了,Cache 控制器將支持 eviction 操作。根據 Cache Line 替換算法,一行將被清除 Clean,無效化 Invalid 或者重新配置。數據緩存和指令緩存是采用的偽隨機替換算法。Cache支持的4種基本操作,1.使能,2.禁止,3.清空,4.無效化。
Clean清空操作是將Cache Line中標記為dirty的數據寫入到內存里面,無效化Invalid是將 Cache Line 標記為無效,等同於刪除操作。這樣Cache 空間就都騰出來了,可以加載新的指令/數據。
三. cache配合MPU使用
MPU(memory protection unit),首先需要通過MPU配置相應memory的屬性(normal, strongly-ordered, device, XN etc.)
RT1052存儲器默認映射和屬性
四.什么是 cache 一致性問題
對於指令緩沖I-Cache,用戶不用管,這里主要說的是數據緩存 D-Cache。
所謂的 Cache 一致性問題, 主要指的是由於 D-cache 存在時,表現在有多個 Host(典型的如 MCU 的 Core, DMA 等)訪問同一塊內存時, 由於數據會緩存在 D-cache 中而沒有更新實際的物理內存。
在實際應用中,有以下兩種情況:
4.1
第一種情況是當有core寫物理內存(SRAM,0x20200000)的指令時,(對應SDK例程:*(uint8_t *)(startAddr + count) = 0xffu;)Core 會先去更新相應的 cache-line(Write-back 策略),在沒有 clean 的情況下,會導致其對應的實際物理內存中的數據並沒有被更新,如果這個時候有其它的 Host(如 DMA)訪問這段內存時,就會出現問題(由於實際物理內存並未被更新,和 D-cache 中的不一致),如果clean一下(對應SDK例程:L1CACHE_CleanDCacheByRange(startAddr, MEM_DMATRANSFER_LEN);)這就是所謂的 cache 一致性的問題。
4.2
第二種情況是 DMA 更新了某段物理內存(DMA 和 cache 直接沒有直接通道),而這個時候 Core 再讀取這段內存的時候,由於相對應地址的 cache-line 沒有被 invalidate,導致 Core 讀到的是 cache-line 中的數據,而非被 DMA 更新過的實際物理內存的數據。
五。解決 cache 一致性問題
有兩種可選方案:
一.所有的共享存儲器都定義為共享屬性
• 這些區域將默認不被緩存到 D-Cache。
• 所有的操作都直接針對二級存儲器(內部Flash,外部存儲器),性能降低。
• 因為緩存對這些區域是透明的,寫軟件更容易
二.通過軟件進行cache的維護
(1)Cortex-M7 的寫操作要是全局可見的
• 使用透寫屬性(通過 MPU 設置)。
• 使用 SIWT@CACR(Shared = Write Through)。
• 通過指令清 D-cache,然后所有更新位置禁止 D-Cache操作
這種情況,在DMA從SRAM搬運數據到SDRAM時,需要先執行clean。
L1CACHE_CleanInvalidateDCache();
(2)其他主設備的寫操作要對 Cortex-M7 可見
• 比如作廢 Cortex-M7 Dache 中數據
這種情況,在DMA從SDRAM搬運數據到SRAM時,需要搬運后執行Invalidate。
L1CACHE_CleanInvalidateDCache()
六.常見函數解釋
SCB_EnableICache() 和 SCB_EnableDCache()
使能 I-cache 或 D-cache。
SCB_DisableICache() 和 SCB_DisableDCache()
禁用 I-cache 或 D-cache。
SCB_InvalidateICache()
使 I-cache 無效,I-cache 被 invalidate 之后,當讀取指令時,會忽略相應的 cache-line 中的內容(因為被 validate 了),而從真實的物理地址中去獲取相應的指令
SCB_InvalidateDCache()
使 D-cache 無效,D-cache 被 invalidate 之后,當有 Host(如 core,DMA 等)讀取數據時,會忽略相應的 cache-line 中的內容( 因為被 validate 了),從真實的物理地址中去獲取相應的數據
SCB_InvalidateDCache_by_Addr()
根據地址信息無效其對應的 cache-line。
SCB_CleanDCache()
Clean 所有的 cache-line,即將 dirty 的 cache-line 全部寫到 cache line 對應的真實的物理地址中所謂的 drity 屬性,
即寫操作時, 更新了相應的 cache-line,但是沒有更新到真實的物理地址,而這個 clean 的動作, 就是將 cache 中的內容更新到真實的物理地址中。
SCB_CleanDCache_by_Addr()
根據地址信息 clean 其對應的 cache-line
SCB_CleanInvalidateDCache_by_Addr()
根據地址信息 clean 並 invalidate 其對應的 cache-line。
SCB_CleanInvalidateDCache()
七. 其他指令解釋
功能描述
DMB
數據存儲器隔離。DMB 指令保證: 僅當所有在它前面的存儲器訪問操作
都執行完畢后,才提交(commit)在它后面的存儲器訪問操作。
DSB
數據同步隔離。比 DMB 嚴格: 僅當所有在它前面的存儲器訪問操作
都執行完畢后,才執行在它后面的指令(亦即任何指令都要等待存儲器訪 問操作——譯者注)
ISB
指令同步隔離。最嚴格:它會清洗流水線,以保證所有它前面的指令都執
行完畢之后,才執行它后面的指令。
對於高級底層技巧:“自我更新”(self-mofifying)代碼,非常有用。舉例 來說,如果某個程序從下一條要執行的指令處更新了自己,
但是先前的舊指令已經被預取到流水線 中去了,此時就必須清洗流水線,把舊版本的指令洗出去,再預取新版本的指令。因此,必須在被 更新代碼段的前面使用 ISB,以保證舊的代碼從流水線中被清洗出去,不再有機會執行
Bootloader在跳轉到app之前的清洗,就是1個例子,如下:
--------------------------------------------
LPUART_Deinit(LPUART1);
vSceneRenew();
__ISB();
__DSB();
/* Enable I cache and D cache */
SCB_DisableDCache();
SCB_DisableICache();
vControlSwitch();
--------------------------------------------------------------------
L1CACHE_InvalidateICacheByRange函數也是1個例子:
/**Invalidate cortex-m7 L1 instruction cache by range.***/
void L1CACHE_InvalidateICacheByRange(uint32_t address, uint32_t size_byte)
{
#if (__DCACHE_PRESENT == 1U)
uint32_t addr = address & (uint32_t)~(FSL_FEATURE_L1ICACHE_LINESIZE_BYTE - 1);
int32_t size = size_byte + address - addr;
uint32_t linesize = 32U;
__DSB();
while (size > 0)
{
SCB->ICIMVAU = addr;
addr += linesize;
size -= linesize;
}
__DSB();
__ISB();
#endif
}
以上內容參考了網上很多博客,還有官方文檔,再次表示感謝,若有侵權,請聯系刪除,謝謝!
技術交流微信 18124528727.
-------------------------------------------------END---------------------------------------------------------------