例說STM32F7快取記憶體——Cache一致性問題(三)
3. Cache 一致性問題
3.1 什麼是 cache 一致性問題
所謂的 Cache 一致性問題, 主要指的是由於 D-cache 存在時,表現在有多個 Host(典型的如 MCU 的 Core, DMA 等)訪問同一塊記憶體時, 由於資料會快取在 D-cache 中而沒有更新實際的實體記憶體。
在實際應用中,有以下兩種情況:
第一種情況是當有寫實體記憶體的指令時,Core 會先去更新相應的 cache-line(Write-back 策略),在沒有 clean 的情況下,會導致其對應的實際實體記憶體中的資料並沒有被更新,如果這個時候有其它的 Host(如 DMA)訪問這段記憶體時,就會出現問題(由於實際實體記憶體並未被更新,和 D-cache 中的不一致),這就是所謂的 cache 一致性的問題。
第二種情況是 DMA 更新了某段實體記憶體(DMA 和 cache 直接沒有直接通道),而這個時候 Core 再讀取這段記憶體的時候,由於相對應地址的 cache-line 沒有被 invalidate,導致 Core 讀到的是 cache-line 中的資料,而非被 DMA 更新過的實際實體記憶體的資料。
3.2 如何處理 cache 一致性問題
我們知道,Cache 機制是為了提高儲存系統的平均讀寫效能而設計的,但是這種機制帶來了資料一致性問題,然而,卻沒有對一致性的硬體支援
因此為了解決一致性問題,一個辦法就是禁用 Cache(cache 都禁用了,肯定不會有 cache 一致性的問題啦~)。但是如果你選擇使用 STM32F7 這樣高效能的微控制器,又不使用其帶來的高效能特性,那你為什麼要用 F7 呢,用 F3、F4 不就得了麼?所以為了提高效能,還是使能 cache,並積極解決 cache 一致性問題吧。
好吧,解決 STM32F7 的 cache 一致性問題,有兩種可選方案:
所有的共享儲存器都定義為共享屬性
• 這些區域將預設不被快取到 D-Cache。
• 所有的操作都直接針對二級儲存器(內部Flash,外部儲存器),效能降低。
• 因為快取對這些區域是透明的,寫軟體更容易。
通過軟體進行cache的維護
(1)Cortex-M7 的寫操作要是全域性可見的
• 使用透寫屬性(通過 MPU 設定)。
• 使用 [email protected](Shared = Write Through)。
• 通過指令清 D-cache,然後所有更新位置禁止 D-Cache操作。
(2)其他主裝置的寫操作要對 Cortex-M7 可見
• 比如作廢 Cortex-M7 Dache 中資料。
3.3 示例
3.3.1 程式描述
(1)首先將地址 0x20020000(SRAM1)處開始的 128 位元組初始化為 0x55。
(2)將 Flash 中的 128 位元組的常量陣列 aSRC_Const_Buffer
拷貝到 SRAM1 地址 0x20020000(pBuffer)。
(3)配置並使能 DMA,通過 DMA 將資料從 SRAM1 的地址 0x20020000 處拷貝到 DTCM RAM 中的陣列 aDST_Buffer
中。
(4)將 Flash 中的陣列 aSRC_Const_Buffer
與 DMA 讀出的陣列 aDST_Buffer
進行比較。
顯然,這個例子中的 cache 一致性問題, 展示的是上面(圖3.1)的第一種情況。也就是在 Write-back 策略下,CPU 先去更新相應的 cache-line,然後 DMA 去訪問對應的記憶體,從而導致資料不一致的現象。
程資料的傳輸流程和路徑如下圖所示:
3.3.2 復現 cache 一致性問題
我們先來按照示例要求編寫程式碼,復現 cache 一致性問題。有些人可能會疑惑,變數資料怎麼放到 Flash、SRAM1、DTCM?實際上,可以通過一些相關的配置檔案進行設定,比如 icf 檔案、scatter 檔案等,當然,這跟所使用開發環境和編譯工具鏈有關。
本文所使用的環境是 IAR,其連結檔案 *.icf 如下:
然後將 aSRC_Const_Buffer
陣列定義為常量,即可分配到 RO 區域,aDST_Buffer
定義為普通的全域性變數或靜態變數即可,因為記憶體區域從 0x20000000 開始,也就是 DTCM RAM。
好了,程式碼主體部分如下:
(完整的程式碼可以在 http://download.csdn.net/download/luckydarcy/10104739 下載)
#define SRAM1_ADDRESS_START (0x20020000UL)
static const uint32_t aSRC_Const_Buffer[BUFFER_SIZE] =
{
0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,
0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30,
0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,
0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50,
0x51525354, 0x55565758, 0x595A5B5C, 0x5D5E5F60,
0x61626364, 0x65666768, 0x696A6B6C, 0x6D6E6F70,
0x71727374, 0x75767778, 0x797A7B7C, 0x7D7E7F80
};
static uint32_t aDST_Buffer[BUFFER_SIZE];
int main(void)
{
uint32_t counter = 0;
uint32_t *pBuffer = (uint32_t*)SRAM1_ADDRESS_START;
if (HAL_Init() != HAL_OK)
{
Error_Handler();
}
/* Initialize LEDs */
BSP_LED_Init(LED1);
/* Configure the system clock to 216 MHz */
SystemClock_Config();
BSP_LCD_Config();
/* Set to 1 if an transfer error is detected */
transferErrorDetected = 0;
/* Fill 128 bytes with 0x55 pattern */
memset((uint8_t*)SRAM1_ADDRESS_START, 0x55, sizeof(aSRC_Const_Buffer));
/* TODO:Enable MPU and change SRAM region attribute
* set write-back policy on SRAM */
MPU_Config();
/* Enable Data cache */
SCB_EnableDCache();
/* Copy data from Flash to SRAM by CPU */
for (counter = 0; counter < (sizeof(aSRC_Const_Buffer)/4); counter++)
{
*pBuffer++ = aSRC_Const_Buffer[counter];
}
//* Configure and enable the DMA stream for Memory to Memory transfer */
DMA_Config();
/* Wait for DMA end-of-transfer */
while(TransferCompleteFlag == RESET)
{
}
/* Check data integrity*/
pBuffer = (uint32_t*)&aDST_Buffer;
for(counter = 0; counter <(sizeof(aSRC_Const_Buffer)/4); counter++)
{
if(aSRC_Const_Buffer[counter] != *pBuffer)
{
compareErrorDetected++;
}
pBuffer++;
}
if (compareErrorDetected != 0)
{
/* Toggle LED1 */
BSP_LED_Off(LED1);
compareErrorDetected = 0;
BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation failed! ");
}
else
{
/* Turn LED1 on */
BSP_LED_On(LED1);
BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation success! ");
}
while (1)
{
}
}
static void MPU_Config(void)
{
/* Disable MPU */
MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
/* Configure RAM region as Region N°0, 256kB of size and R/W region */
MPU->RNR = SRAM1_REGION_NUMBER;
MPU->RBAR = SRAM1_ADDRESS_START;
/* Write-Back policy */
MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | MPU_RASR_B_Msk | SRAM1_ACCESS_PERMISSION | 1<<MPU_RASR_TEX_Pos;
/* Enable MPU */
MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
}
為了確保 aSRC_Const_Buffer
在 Flash,aDST_Buffer
在 DTCM,我們可以在編譯完之後檢視 *.map 檔案,如下:
下載到 STM32F769I-DISCO 板子上,顯然,由於此時開啟了 D-Cache,會出現資料不一致的現象,執行結果如下所示:
3.3.3 解決方案
(1)不啟動 D-Cache
註釋掉 SCB_EnableDCache();
不啟動 D-Cache,當然也就沒有了 Cache 資料不一致的問題啦~
(2)將 SRAM1 相應區域設定為 shareable
通過 MPU 將 SRAM1 相應區域設定為 shareable,MPU_Config()
函式處理如下:
static void MPU_Config(void)
{
/* Disable MPU */
MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
/* Configure RAM region as Region N°0, 256kB of size and R/W region */
MPU->RNR = SRAM1_REGION_NUMBER;
MPU->RBAR = SRAM1_ADDRESS_START;
/* Shareable */
MPU->RASR = SRAM1_SIZE | MPU_RASR_S_Msk | SRAM1_ACCESS_PERMISSION;
/* Enable MPU */
MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
}
(3)DMA 訪問 SRAM1 前先 Clean cache
在啟動 DMA 訪問之前,程式設計師需要在合適的地方將 D-Cache 資料回寫到主記憶體中,也就是 Clean 的操作。
在本示例中,可以在 DMA_Config();
前呼叫:
SCB_CleanDCache();
或者
SCB_CleanDCache_by_Addr((uint32_t*)SRAM1_ADDRESS_START, sizeof(aSRC_Const_Buffer));
(4)將 SRAM1 相應區域設定為 Write-through 策略
通過 MPU 將 SRAM1 相應區域設定為透寫模式(Write-through),MPU_Config()
函式處理如下:
static void MPU_Config(void)
{
/* Disable MPU */
MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
/* Configure RAM region as Region N°0, 256kB of size and R/W region */
MPU->RNR = SRAM1_REGION_NUMBER;
MPU->RBAR = SRAM1_ADDRESS_START;
/*Write Through policy*/
MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | SRAM1_ACCESS_PERMISSION;
/* Enable MPU */
MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
}
(5)將所有 cacheable 的空間全部強制 Write-though
通過 cache 控制暫存器,將所有 cacheable 的空間全部強制 Write-though 模式。
在初始化的時候進行設定:
__FORCE_WRITE_THROUGH();
巨集定義為:
#define __FORCE_WRITE_THROUGH() *(__IO uint32_t *)0xE000EF9C = 1UL<<2
以上這是都是較為常用的方法,在實際的開發過程中,為了提高效能,一般都會開啟 cache,同時將其配置為 WB 策略,這就需要開發者在使用時特別小心!
值得一提的是:對於第二種情況(圖3.2),就不是 clean 操作了,而是 invalidate。需要先呼叫 SCB_InvalidateDCache()
或 SCB_InvalidateDCache_by_Addr()
去 invalidate 相應的 cache-line, 這樣當 CPU 在讀取時,會忽略 D-cache 中的內容,去真實的實體地址讀取對應的資料。
好啦,通過上述幾種方法,就可以解決 cache 資料一致性問題。當然,除了我這裡提供的,還有其他方案,各種方案各有利弊,要根據實際應用場景去衡量,這就是嵌入式程式設計師展示才華的時候啦~