3. Cache 一致性问题' K; B* }$ \6 P$ C0 C. z
3.1 什么是 cache 一致性问题7 f; B# E! W$ i, U
所谓的 Cache 一致性问题, 主要指的是由于 D-cache 存在时,表现在有多个 Host(典型的如 MCU 的 Core, DMA 等)访问同一块内存时, 由于数据会缓存在 D-cache 中而没有更新实际的物理内存。
# d3 {: z0 w( S2 \& }8 A) d* Z! L2 k9 e) R) e8 k. R' |
在实际应用中,有以下两种情况:! j r- V% l4 D3 [ n
" c, D S( E$ G第一种情况是当有写物理内存的指令时,Core 会先去更新相应的 cache-line(Write-back 策略),在没有 clean 的情况下,会导致其对应的实际物理内存中的数据并没有被更新,如果这个时候有其它的 Host(如 DMA)访问这段内存时,就会出现问题(由于实际物理内存并未被更新,和 D-cache 中的不一致),这就是所谓的 cache 一致性的问题。8 ]: O K) ]* A( y* u$ S% q
5 ~$ h$ d$ a8 g1 i# c
2 f* s! U& p, h$ p
$ a' y6 B. H4 X9 k3 _图3.1 Cache 一致性问题 第1种情况
; ]+ W6 W& _. @! S4 V. y
2 ~9 ^" `. t: ?# {. L+ n, h) R第二种情况是 DMA 更新了某段物理内存(DMA 和 cache 直接没有直接通道),而这个时候 Core 再读取这段内存的时候,由于相对应地址的 cache-line 没有被 invalidate,导致 Core 读到的是 cache-line 中的数据,而非被 DMA 更新过的实际物理内存的数据。
+ x( ^+ v" f0 {5 k
7 F( A! {( j1 \- n- S( W9 g J$ p$ i2 o6 C6 i. m9 U
7 J3 q. \; h3 f
图3.2 Cache 一致性问题 第2种情况
- F" [. q7 M2 `# F5 E. |& `+ N6 q" ~0 A7 L2 K6 |
3.2 如何处理 cache 一致性问题; A1 Q1 H0 z' T' j, j P% m
我们知道,Cache 机制是为了提高存储系统的平均读写性能而设计的,但是这种机制带来了数据一致性问题,然而,却没有对一致性的硬件支持。5 x4 f6 a3 O9 E8 j2 z
1 {1 t3 e L7 x6 d# K% F1 z! t因此为了解决一致性问题,一个办法就是禁用 Cache(cache 都禁用了,肯定不会有 cache 一致性的问题啦~)。但是如果你选择使用 STM32F7 这样高性能的微控制器,又不使用其带来的高性能特性,那你为什么要用 F7 呢,用 F3、F4 不就得了么?所以为了提高性能,还是使能 cache,并积极解决 cache 一致性问题吧。
$ c) }6 e% C$ M- n# o' V0 I: d0 U9 V) L6 v
好吧,解决 STM32F7 的 cache 一致性问题,有两种可选方案:$ b, z4 L- J+ B
4 Y( }2 O0 h6 F
所有的共享存储器都定义为共享属性
) Z5 r0 F" g5 C! v6 o3 U% \
/ N8 p# w5 F: u6 ^# _1 Z( A- i/ q# ]这些区域将默认不被缓存到 D-Cache。) G; M! V9 q w
所有的操作都直接针对二级存储器(内部Flash,外部存储器),性能降低。 j0 X* }, O2 Q$ |- B
因为缓存对这些区域是透明的,写软件更容易。
! j3 [" X/ O4 t1 C+ r/ C% w
6 y6 P. o* s, G2 F/ N& F通过软件进行cache的维护6 j% Y5 \3 N Z- _
(1)Cortex-M7 的写操作要是全局可见的4 W. g' B- h" d6 V; |! E% L
5 p+ s' |6 n! ~, ^) [; Z
使用透写属性(通过 MPU 设置)。0 c2 C1 `$ j* T2 ?" r; S
使用 SIWT@CACR(Shared = Write Through)。
0 E, h3 T) K+ l: _5 R通过指令清 D-cache,然后所有更新位置禁止 D-Cache操作。* O! C- d1 @' x
(2)其他主设备的写操作要对 Cortex-M7 可见$ N6 x4 x7 T: ~; l
: T; z1 T' J7 ?5 \6 C
比如作废 Cortex-M7 Dache 中数据。; W- A+ l: `6 ]3 Y4 w& _
3.3 示例
: v" x0 `* t7 E2 x& q3.3.1 程序描述
4 C/ ]5 h: V( }+ O% o% w(1)首先将地址 0x20020000(SRAM1)处开始的 128 字节初始化为 0x55。3 O- V- V0 z. h& o
(2)将 Flash 中的 128 字节的常量数组 aSRC_Const_Buffer 拷贝到 SRAM1 地址 0x20020000(pBuffer)。8 O: q& E/ @+ w: b
(3)配置并使能 DMA,通过 DMA 将数据从 SRAM1 的地址 0x20020000 处拷贝到 DTCM RAM 中的数组 aDST_Buffer 中。. t" p! X( S& d" Y: i, O) |: w- K
(4)将 Flash 中的数组 aSRC_Const_Buffer 与 DMA 读出的数组 aDST_Buffer 进行比较。
+ m% a# d- S( H* [8 b, ~9 ]6 l) s; l" Q) {
显然,这个例子中的 cache 一致性问题, 展示的是上面(图3.1)的第一种情况。也就是在 Write-back 策略下,CPU 先去更新相应的 cache-line,然后 DMA 去访问对应的内存,从而导致数据不一致的现象。 ^, _3 z- [- S6 Q2 c
) i' V. n' p1 ]程数据的传输流程和路径如下图所示:
" R! U" B" S2 c
3 {7 e3 d" a) ?$ u
7 ?* q' e: C! `: d- H5 [ S+ P1 u( s: q( l( h q# K. o% |
图3.3 Cache 示例数据传输框图 / h; g$ z+ T ]- f
8 y" H6 d, E( q5 \0 t0 z( h3.3.2 复现 cache 一致性问题6 D: C$ Y) l! U6 d, \2 J
我们先来按照示例要求编写代码,复现 cache 一致性问题。有些人可能会疑惑,变量数据怎么放到 Flash、SRAM1、DTCM?实际上,可以通过一些相关的配置文件进行设置,比如 icf 文件、scatter 文件等,当然,这跟所使用开发环境和编译工具链有关。1 s! J1 ?0 M# O/ a5 u
; o7 U2 t8 q6 Y
本文所使用的环境是 IAR,其链接文件 *.icf 如下:
2 P, S1 p; P: }5 D, R3 {; P0 S+ [
! y. m, ~0 ^9 U. g. \3 ~4 z v, c' G4 C( t _" J
g2 A& W% S6 k( ?( w3 T/ `然后将 aSRC_Const_Buffer 数组定义为常量,即可分配到 RO 区域,aDST_Buffer 定义为普通的全局变量或静态变量即可,因为内存区域从 0x20000000 开始,也就是 DTCM RAM。+ h! e1 d9 _* n, X
% b8 [% r7 F2 O好了,代码主体部分如下:
- D# x* @2 @* y: z$ U; k2 u) o( Q' E6 r
- #define SRAM1_ADDRESS_START (0x20020000UL)6 M3 u S2 d6 H$ `% [ Z1 @9 ?
$ P7 h$ O" S- D! q8 S' d U& s- static const uint32_t aSRC_Const_Buffer[BUFFER_SIZE] =0 t" q9 B& t0 b; q. h9 h
- {
" {* D T' O: B+ ^: ] - 0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
- @- @6 e5 x5 @0 s9 U8 F' k - 0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20," j! C. P3 f5 V( W7 \. D
- 0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30,9 b: r! O7 ?5 V% r2 J
- 0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,0 a, D6 V. x. ?0 _
- 0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50,
# l- b- i1 } p, \ - 0x51525354, 0x55565758, 0x595A5B5C, 0x5D5E5F60,1 k0 r, y% J: F: l6 l
- 0x61626364, 0x65666768, 0x696A6B6C, 0x6D6E6F70,
9 ^6 f9 ?) h% C3 b - 0x71727374, 0x75767778, 0x797A7B7C, 0x7D7E7F80
+ I- p* I2 j! S! E - };3 ?% _! r+ [6 U6 M% z7 e- G
- ' v9 N+ K5 n% _. e+ G" Q# c5 C# m
- static uint32_t aDST_Buffer[BUFFER_SIZE];1 g" |7 A2 C$ @/ x, A- E: G
- + R" n' g" }0 u' ~! m
- int main(void)
! u- T N- F- N; C+ A8 @* [ - {2 p; m3 y& ^5 B6 [) E) o0 p: r, W
- uint32_t counter = 0;4 G7 p L" _+ x( N9 Z0 r
- uint32_t *pBuffer = (uint32_t*)SRAM1_ADDRESS_START;
# M1 g- @# y: g% }6 D# N' p% p - 8 D" E' D3 D* f- Y
- if (HAL_Init() != HAL_OK)8 B. E8 e0 F+ w
- {
8 O1 X6 z& c! I: a: G& D; O f! \& y2 b - Error_Handler();
/ I# G4 ~; E9 x. } - }
/ X* E x# t5 ]' `- F
) t+ l7 c; `5 M. i- l/ d- /* Initialize LEDs */
) Z3 I" y4 L" o - BSP_LED_Init(LED1);
' u5 h. ], j3 X: v) \. v. ~1 {
Q5 y2 k, ^1 w9 E. t M- /* Configure the system clock to 216 MHz */- r4 o8 z$ L2 H( I) i" v
- SystemClock_Config();
8 r. T$ z& x4 }" v/ g- w - BSP_LCD_Config();
8 c/ h/ c& v7 E. \; o - / @$ p- @. h2 O$ q2 s% D% `" V5 v
- /* Set to 1 if an transfer error is detected */
( n. N$ k: @8 M7 m" o; x, N7 v! o - transferErrorDetected = 0;5 k9 G3 U3 g1 S* h- D
-
' U; F$ e$ O4 c5 U- c% i2 C - /* Fill 128 bytes with 0x55 pattern */
3 C& Q5 J! a0 J5 E* f3 F - memset((uint8_t*)SRAM1_ADDRESS_START, 0x55, sizeof(aSRC_Const_Buffer));$ t5 k: ~: K. N2 d5 f
7 S5 ~, Z7 A) e* V# H: t! @- /* TODO:Enable MPU and change SRAM region attribute ( j" [1 B v/ e* ]4 N+ X
- * set write-back policy on SRAM */
+ E" {* x' S/ T3 F - MPU_Config();; H/ n5 w$ C6 D( t+ w3 [* e1 s; k
- + j/ l) F1 j0 c- w
- /* Enable Data cache */+ H/ @1 A e; N& A# S
- SCB_EnableDCache();
7 ~4 _) x* u0 K) T( S& M - 5 {$ z/ k# S6 j- R, S* M9 z
- /* Copy data from Flash to SRAM by CPU */
4 A Z A2 V u P - for (counter = 0; counter < (sizeof(aSRC_Const_Buffer)/4); counter++)) G& O3 @! e* \1 g
- {
, [: s5 o, p, N4 Z - *pBuffer++ = aSRC_Const_Buffer[counter];" Q" V! B! L Z$ {' ?- m- f( y
- }( f5 s7 q `+ [) g5 Z T
- 7 T( C$ Z3 k7 C# Q7 K
- //* Configure and enable the DMA stream for Memory to Memory transfer */
, w% s& V* j+ d2 P0 f3 ]; O* M - DMA_Config();
+ k0 X6 l+ @0 s: L( Y - ) n& B: E# R% |- G0 Z4 U
- /* Wait for DMA end-of-transfer */9 T2 z: q3 n+ V. r3 Y! c# {" A
6 Z" a1 Z* q$ T& g2 r- while(TransferCompleteFlag == RESET)/ T0 `3 x T# `; j' r6 ~2 S
- {1 [0 {0 ^' E) A5 _! d1 [* L
- }
( ^3 E8 a: P: }. S1 _# h; S8 v
' x) t; _$ f3 c4 R6 p, n0 L O z* w- /* Check data integrity*/
4 l* h* b0 ]; u3 \5 A* L - pBuffer = (uint32_t*)&aDST_Buffer;" \% Z s7 t. [6 Q" v2 e
- for(counter = 0; counter <(sizeof(aSRC_Const_Buffer)/4); counter++)
, ?9 }- ^0 l- [& e. h7 O: \; M - {0 G C( ?( {6 `/ R
-
- M1 s1 ?% Y) P1 p" G - if(aSRC_Const_Buffer[counter] != *pBuffer)
' Y9 m3 ^9 V! @6 e# Q - { L* H, j$ A A( s' J
- compareErrorDetected++;. q& A1 \% e% w3 z
- }
) U8 \2 b G2 J+ s5 E' ~4 W - pBuffer++;
6 A/ w& \( {) k( [6 y5 @$ v& M) g - }
$ }, z# P' a+ p. Y. k - / I2 B: m& n Z: G' J- o
- if (compareErrorDetected != 0)* A& |7 }$ ]$ m" `( {, K6 o
- { s, Z- B$ c7 M4 e# S
- /* Toggle LED1 */9 ~/ I, K( E# N, L' z, P
- BSP_LED_Off(LED1);
1 d0 p3 i- A, w$ [' f2 G0 ^ - compareErrorDetected = 0; ( q6 D9 c l! D% w) s
- BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation failed! ");7 Y" L. u1 W# g6 [
- }
, C C* O7 L& J+ B- G/ B - else. f2 j2 E: T2 F% H6 y) b
- {
2 f8 A" y$ y# r- v - /* Turn LED1 on */3 L* E) J, {/ @/ i4 {, L
- BSP_LED_On(LED1);/ d" W" ^, H* ~7 m# e! x
- BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation success! ");" W: c4 C, `9 d' T, H
- }) }% B) Y! f0 [
- & g$ L l4 o1 J" y/ z$ m- k
- while (1)
9 z* d& H7 v: f# e2 K - { Y5 |' m1 e/ k [+ ]& F) u
- : ?* q6 g+ J: H; j& G+ V7 j
- }
" {2 ?# F8 j+ V) E+ B+ m - }
: X9 K, r6 n! g3 w& M! X8 V - 0 U. I; i8 t3 b6 _7 l6 i1 | M+ B
- static void MPU_Config(void)* k4 L" x) T4 l2 C
- {
2 Z$ o8 u8 G# f2 t9 ~5 h; _ - /* Disable MPU */' ?% l6 t; D" U! W
- MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
: a% @; o7 L, D' {2 x. L - 4 _6 J# j' Q2 A1 f1 x$ k$ U+ P
- /* Configure RAM region as Region N°0, 256kB of size and R/W region */
4 p/ Z t- [* d P+ a9 b s2 o - MPU->RNR = SRAM1_REGION_NUMBER;
2 v# o, r2 `; b& q- { - MPU->RBAR = SRAM1_ADDRESS_START;
: i' j% ^' \: L/ {) c -
- q% T! s7 r: f U; e# u! ? - /* Write-Back policy */) a9 U* y; ?# T/ P8 e( F
- MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | MPU_RASR_B_Msk | SRAM1_ACCESS_PERMISSION | 1<<MPU_RASR_TEX_Pos;
2 W' g+ \* |' i7 C( ^" j
$ k6 h4 V% L; s. n7 ~( q% R- /* Enable MPU */
( N4 @4 q2 d( `9 s, R( r( V3 p" P8 V - MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
5 U, m' M0 K6 A) { e - }
7 B @+ Q0 p1 S$ W, S/ l
复制代码 4 h: A/ w5 m0 B
为了确保 aSRC_Const_Buffer 在 Flash,aDST_Buffer 在 DTCM,我们可以在编译完之后查看 *.map 文件,如下:( x9 `, I* Z; l# Y/ H8 L
/ @) t) K$ S6 D- p# C" |4 y
6 X, L9 L" Z' A6 v5 K0 i
( a* D) E4 o+ v图3.4 检查常量和变量分配情况 # @0 K9 U% S- n$ S$ S
: }/ X8 `4 E, X; x( _( c+ o下载到 STM32F769I-DISCO 板子上,显然,由于此时开启了 D-Cache,会出现数据不一致的现象,执行结果如下所示:
% {" R; v1 N% O E! D* N! B# b5 ^
0 }0 i! C& D5 i6 F7 C$ }
2 N+ }+ J! q/ Z: K, g) Y
, g4 Q/ W+ s* u; l. J5 T% e图3.5 Cache 数据不一致 : [; Z) I" Z" T$ B6 @
% C' X! r4 w' A# x, ~5 _6 |
3.3.3 解决方案; p' N/ U. h" r. {1 Z; W3 \
(1)不启动 D-Cache
) D# y; l- F ^) u" z0 w3 i" r8 V% k1 _; ^5 e. Z
注释掉 SCB_EnableDCache();2 `3 W: \7 `7 X9 G8 E% B( C
& D) q/ j3 I, M& ^" |- P2 ~8 x不启动 D-Cache,当然也就没有了 Cache 数据不一致的问题啦~7 \/ w. K8 f- B7 |& d
6 _. h1 S% I! Y" Y4 n1 p5 f
(2)将 SRAM1 相应区域设置为 shareable
) D1 C$ @" U+ V2 z* l* z3 P3 S2 Q. `! O3 Q. z: U7 |
通过 MPU 将 SRAM1 相应区域设置为 shareable,MPU_Config() 函数处理如下:- D' ?* `, S* i/ M' x
- static void MPU_Config(void)
# w% Y& X% T/ a5 A, C$ _ - {7 A- X, Z* { o; ?2 F
- /* Disable MPU */9 T8 v" B% g) G4 f
- MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
; |- B+ ~0 k+ l* L! l - & _, i: l) ^5 B2 { [% B
- /* Configure RAM region as Region N°0, 256kB of size and R/W region */. U" J- d8 f& A3 \5 }* C' Z2 F0 Y
- MPU->RNR = SRAM1_REGION_NUMBER;
t! @/ ~1 i1 O3 b3 F7 L - MPU->RBAR = SRAM1_ADDRESS_START;
* L$ X% O9 c6 V0 | - : l2 W% h0 |0 O3 Y, I
- /* Shareable */* D. r/ y) l: f% ~3 w6 ?9 L
- MPU->RASR = SRAM1_SIZE | MPU_RASR_S_Msk | SRAM1_ACCESS_PERMISSION;$ I/ n* Q9 W! M* ^! g
- 6 h- t4 a0 R$ o$ Q
- /* Enable MPU */& J( d# b% x0 p: ?, E
- MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
! C3 c: c& z/ v# m! f9 J - }
复制代码
: J' R" ?. Z/ R" F" ^(3)DMA 访问 SRAM1 前先 Clean cache
- a8 |& ^; Z6 |0 U( f x, b# z& J0 X F
在启动 DMA 访问之前,程序员需要在合适的地方将 D-Cache 数据回写到主内存中,也就是 Clean 的操作。4 ]& E t8 v0 s. i
1 | J* D5 i ~ @! p! w在本示例中,可以在 DMA_Config(); 前调用:
( \% `$ S* |0 N2 N! q/ l或者( b+ S* x: G# `% J" k' C
- SCB_CleanDCache_by_Addr((uint32_t*)SRAM1_ADDRESS_START, sizeof(aSRC_Const_Buffer));
复制代码
: r& j: N$ p3 w2 V7 n% T9 J(4)将 SRAM1 相应区域设置为 Write-through 策略" B4 o# @1 s& r) i! `" d
D6 P9 j5 n7 `9 [$ {8 w% L
通过 MPU 将 SRAM1 相应区域设置为透写模式(Write-through),MPU_Config() 函数处理如下:
' Y) Y( Z/ v1 w% \( s% `, A9 u1 d, ~7 O/ U3 z
- static void MPU_Config(void)
. ^5 n/ [, q' v" f2 {2 L" L - {
! D& E* }/ M4 x0 d - /* Disable MPU */( ? _' G" d# [% G% {/ b
- MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;* k! [- \, L$ t! z Y
+ b6 b1 W6 S9 a/ l* l; R8 j- /* Configure RAM region as Region N°0, 256kB of size and R/W region */
( @* g3 V' q2 U4 N - MPU->RNR = SRAM1_REGION_NUMBER;$ e' u1 {. M) X* }' p1 T
- MPU->RBAR = SRAM1_ADDRESS_START;) ~" X5 J$ {' q0 {1 S3 Z
! v' u9 f4 {( o2 W/ ]1 O- /*Write Through policy*/* {! w {" k1 Q! T! {
- MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | SRAM1_ACCESS_PERMISSION;
/ E v7 e0 @3 d: s" q# o% ~ - 6 E6 h( v9 k3 v e
- /* Enable MPU */
# _0 w! Z" N0 J2 M6 f - MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;3 m' T" c! r3 L9 S+ q
- }
复制代码
$ U3 a4 h* P" }0 h4 _, C8 F$ x* w(5)将所有 cacheable 的空间全部强制 Write-though
6 T4 e( t6 y7 I+ S% r) i( h7 @2 R. M" ` K3 |6 O0 ?0 m- h! _
通过 cache 控制寄存器,将所有 cacheable 的空间全部强制 Write-though 模式。
- ?! x1 N$ i: [1 B; ?; s( M) T+ v3 h7 f$ i) I5 s7 z
3 O4 u/ V0 s5 X5 H5 _/ n) L( I ?1 U' b6 g, G$ O
图3.6 CACR 寄存器(来自 PM0253) ; M9 ^% U1 l0 y* l+ P9 T
. T% e9 R. d9 g' t
在初始化的时候进行设置:: g7 X3 R1 `; q7 B
2 z4 }$ e: @" `- F宏定义为: Z: b* T/ e7 |/ t
- #define __FORCE_WRITE_THROUGH() *(__IO uint32_t *)0xE000EF9C = 1UL<<2
复制代码 $ {( n j$ t. W a
以上这是都是较为常用的方法,在实际的开发过程中,为了提高性能,一般都会开启 cache,同时将其配置为 WB 策略,这就需要开发者在使用时特别小心!
) v% Y7 Q* {: l2 S+ k
7 {( s3 M2 Z* H% J* I$ c' P值得一提的是:对于第二种情况(图3.2),就不是 clean 操作了,而是 invalidate。需要先调用 SCB_InvalidateDCache() 或 SCB_InvalidateDCache_by_Addr() 去 invalidate 相应的 cache-line, 这样当 CPU 在读取时,会忽略 D-cache 中的内容,去真实的物理地址读取对应的数据。; b7 ~3 ^1 G) x( E+ P
# B% j9 P1 H" k! N$ G# a5 i; G8 w- \7 e
6 M9 v/ }; F. _ ]2 P图3.7 Cache 数据一致 , C1 a& n" w( b Z: |* X0 U
( x& d. [" I9 h好啦,通过上述几种方法,就可以解决 cache 数据一致性问题。当然,除了我这里提供的,还有其他方案,各种方案各有利弊,要根据实际应用场景去衡量,这就是嵌入式程序员展示才华的时候啦~
# n6 p" C1 ]* E
) G# J' v& I. s; @+ |+ D: r4 s! u
- y0 [. L& O0 h8 Q, k |