3. Cache 一致性问题
) N2 P2 J/ ~8 _7 ?3.1 什么是 cache 一致性问题# m7 t9 n4 ~1 K7 J* b1 m
所谓的 Cache 一致性问题, 主要指的是由于 D-cache 存在时,表现在有多个 Host(典型的如 MCU 的 Core, DMA 等)访问同一块内存时, 由于数据会缓存在 D-cache 中而没有更新实际的物理内存。" c% c! G) r+ {" W9 X# v6 l# B
) W; Q3 }% ?$ e7 ^* R7 p
在实际应用中,有以下两种情况:
' y* x/ N5 X% u R0 x# H$ |
; ~; [- p9 d( t* Y' c第一种情况是当有写物理内存的指令时,Core 会先去更新相应的 cache-line(Write-back 策略),在没有 clean 的情况下,会导致其对应的实际物理内存中的数据并没有被更新,如果这个时候有其它的 Host(如 DMA)访问这段内存时,就会出现问题(由于实际物理内存并未被更新,和 D-cache 中的不一致),这就是所谓的 cache 一致性的问题。# [; K; U% @: Q% I; e4 _; m
9 W+ r, l- r# k1 d K/ [7 v
8 ^# o- z( o, S5 \/ @0 p' }3 b" g' _
图3.1 Cache 一致性问题 第1种情况
" ?3 R# h, h4 f" O# _
6 ~* z& e* N4 z5 b, H第二种情况是 DMA 更新了某段物理内存(DMA 和 cache 直接没有直接通道),而这个时候 Core 再读取这段内存的时候,由于相对应地址的 cache-line 没有被 invalidate,导致 Core 读到的是 cache-line 中的数据,而非被 DMA 更新过的实际物理内存的数据。& Y$ _$ A. i. X# ?6 `
1 j; C4 e2 D' w% N
' G/ `( r+ n* |* y$ a8 y+ N, q2 x+ B. f( S' }% b) F L) Z
图3.2 Cache 一致性问题 第2种情况 + w' F) v" _; j/ `) v
7 x0 ?+ ^+ P- m c
3.2 如何处理 cache 一致性问题* z5 U" c J2 i/ l: i
我们知道,Cache 机制是为了提高存储系统的平均读写性能而设计的,但是这种机制带来了数据一致性问题,然而,却没有对一致性的硬件支持。# Y( Y9 B3 r: c* h6 \
3 s: ~% O: }) ?7 A
因此为了解决一致性问题,一个办法就是禁用 Cache(cache 都禁用了,肯定不会有 cache 一致性的问题啦~)。但是如果你选择使用 STM32F7 这样高性能的微控制器,又不使用其带来的高性能特性,那你为什么要用 F7 呢,用 F3、F4 不就得了么?所以为了提高性能,还是使能 cache,并积极解决 cache 一致性问题吧。. i5 W3 A) Q' p0 }) X
" @1 T2 L( `4 L9 k
好吧,解决 STM32F7 的 cache 一致性问题,有两种可选方案:+ |* C: X0 R; j' n& K% y
! u$ L! f% z! }
所有的共享存储器都定义为共享属性( r; a9 V: f, K/ ~/ Y! q9 Y' d0 Z
$ {: e* o8 C3 Z. l1 A8 h# g
这些区域将默认不被缓存到 D-Cache。
2 Y. `% B( N' X2 p, j: A3 F, t. J* K所有的操作都直接针对二级存储器(内部Flash,外部存储器),性能降低。( c4 ]6 w3 Q$ S$ a/ @2 O [
因为缓存对这些区域是透明的,写软件更容易。 t+ T9 r; A, U3 Q& a7 G6 D
; V+ J4 N1 v# S" d6 q" l
通过软件进行cache的维护( j: u x$ z7 k, G* E
(1)Cortex-M7 的写操作要是全局可见的& R. L2 _1 r$ e9 t1 O. F# |, D1 U8 _
3 L% @2 }( i0 y' t" f {使用透写属性(通过 MPU 设置)。
3 R2 T; S; {% `' _4 ^5 H' o' |使用 SIWT@CACR(Shared = Write Through)。1 ]) W1 c& h7 e$ I
通过指令清 D-cache,然后所有更新位置禁止 D-Cache操作。$ H6 d) w$ P5 q) V9 C
(2)其他主设备的写操作要对 Cortex-M7 可见' n3 e$ u4 o8 w; _
+ Q" Z* Y% S2 b1 ?$ o比如作废 Cortex-M7 Dache 中数据。" O; }. p* k) u! N2 M, S
3.3 示例) x$ r3 S. y% h4 k
3.3.1 程序描述0 n( n3 D2 J N+ }0 a8 I; {
(1)首先将地址 0x20020000(SRAM1)处开始的 128 字节初始化为 0x55。
' w* g- d8 V5 N* g R% A, U(2)将 Flash 中的 128 字节的常量数组 aSRC_Const_Buffer 拷贝到 SRAM1 地址 0x20020000(pBuffer)。
8 s- ^+ Z* `# d! [3 f(3)配置并使能 DMA,通过 DMA 将数据从 SRAM1 的地址 0x20020000 处拷贝到 DTCM RAM 中的数组 aDST_Buffer 中。
( Q- f6 S4 I/ a+ g(4)将 Flash 中的数组 aSRC_Const_Buffer 与 DMA 读出的数组 aDST_Buffer 进行比较。
5 ]* d/ f2 Y+ S) `0 Z, A
" e5 s. L2 ~6 F( ~9 O' h! v显然,这个例子中的 cache 一致性问题, 展示的是上面(图3.1)的第一种情况。也就是在 Write-back 策略下,CPU 先去更新相应的 cache-line,然后 DMA 去访问对应的内存,从而导致数据不一致的现象。
# t& M$ a% K! j. }! B
b2 {7 e# P! _3 |. `- F6 h程数据的传输流程和路径如下图所示:
8 e$ B- S& F( E# S4 a9 H& G. S. ]$ |, X- |4 L! Q) A
$ X# U+ r& r) t
1 q" z( Y# ?/ {) e) Z. F3 d. K图3.3 Cache 示例数据传输框图
2 D* n7 v9 u# o+ y. f& o$ ]4 w: Q/ @, V
3.3.2 复现 cache 一致性问题
. L4 e* d7 l) F+ |我们先来按照示例要求编写代码,复现 cache 一致性问题。有些人可能会疑惑,变量数据怎么放到 Flash、SRAM1、DTCM?实际上,可以通过一些相关的配置文件进行设置,比如 icf 文件、scatter 文件等,当然,这跟所使用开发环境和编译工具链有关。. o$ e4 W! D+ w6 n% R2 t" W
@8 v# k# P3 R# v/ R
本文所使用的环境是 IAR,其链接文件 *.icf 如下:* e9 n% `+ J5 E2 E' k
9 N% v/ r0 b8 }& x
4 h( w3 e# D: r* {+ q
k' J5 c8 F5 Q) r! E v然后将 aSRC_Const_Buffer 数组定义为常量,即可分配到 RO 区域,aDST_Buffer 定义为普通的全局变量或静态变量即可,因为内存区域从 0x20000000 开始,也就是 DTCM RAM。4 K% P# ~# ~. d, e
# S& e6 U6 `9 k: |2 R1 @好了,代码主体部分如下: e7 r. J. g4 J9 k6 h0 \# q& N
) M& |7 b. D+ l. n6 V* R- #define SRAM1_ADDRESS_START (0x20020000UL)5 N- [8 W4 C9 W
: h7 t; I' ~- K1 \9 U3 O- static const uint32_t aSRC_Const_Buffer[BUFFER_SIZE] =: B$ ?( y1 e! z( V+ A+ D. y$ p
- {7 C1 P- R8 M! \: k& O& b( l8 i* ]& H
- 0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,; v' }' e& J& G' I+ U' a
- 0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,6 k( y( [; `/ [7 O5 F
- 0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30,3 N" n: ?9 {+ j; x( q# m7 V0 t# ~
- 0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,- b3 c# I- j$ w$ q5 [
- 0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50,
0 v9 n3 [3 Z- Z) R - 0x51525354, 0x55565758, 0x595A5B5C, 0x5D5E5F60,& L d& ?$ m* q1 q5 {
- 0x61626364, 0x65666768, 0x696A6B6C, 0x6D6E6F70,! z I* ~% F# _. \2 ~6 e7 w
- 0x71727374, 0x75767778, 0x797A7B7C, 0x7D7E7F80
( ~* l- k% Q8 N* D/ w$ U - };! U/ m& E' u7 p9 L: R
- N1 p5 k- w2 h6 T B: r$ G- static uint32_t aDST_Buffer[BUFFER_SIZE];
0 k' C; }+ S1 f: G
: x1 b D! [' H$ L# t p& s5 L! o8 d- int main(void)
$ _3 r. l6 V4 \. } - {) c9 L* c/ k0 S t0 J+ \4 I ]
- uint32_t counter = 0;9 n2 N T1 m9 ~6 _# [% A
- uint32_t *pBuffer = (uint32_t*)SRAM1_ADDRESS_START;" l" \1 T& R5 ]0 V3 k( R
- % W" f6 x9 S. e) R
- if (HAL_Init() != HAL_OK)) j, K! |7 C6 i7 W
- {/ m" A/ i# {/ a) Y- c* v
- Error_Handler();' L2 S( i( ` Z7 R+ Z, [2 R
- }
$ \2 C- W: c4 Z - ) R# B8 }# z# O
- /* Initialize LEDs */
2 i7 `+ W P W7 n$ |) w4 s - BSP_LED_Init(LED1);6 y% u. J0 w8 n+ @2 V
" a0 v9 C/ H, C6 B/ j8 J- /* Configure the system clock to 216 MHz */
0 G( F6 G! A% ]+ G5 m7 L; N9 d - SystemClock_Config();4 K/ _" V! {: r5 v: T) X
- BSP_LCD_Config();
6 n% d2 `) E/ m8 L8 I" a6 v - ' f# m5 X; F" ^) Z
- /* Set to 1 if an transfer error is detected */
( l. o2 ^9 R0 g& n4 R - transferErrorDetected = 0;) U) {' ^7 A( _' E
-
1 A; A# e2 S0 ^6 Z7 { - /* Fill 128 bytes with 0x55 pattern */
8 N& k5 q* `, F - memset((uint8_t*)SRAM1_ADDRESS_START, 0x55, sizeof(aSRC_Const_Buffer));
2 i2 }2 u. v4 ?' K
1 W9 k, ^- W+ r+ n, [- /* TODO:Enable MPU and change SRAM region attribute ; U q5 T. }# }' _9 ~6 w
- * set write-back policy on SRAM */! O: g. n# p7 `5 s% w
- MPU_Config();* v: n( T9 f2 ~) ?. c; T" }0 c1 Y5 n
5 i5 f. U! j) T8 m1 S3 w) M3 h, ~# E- /* Enable Data cache */ H8 J6 I% J$ i2 R
- SCB_EnableDCache();5 j( V5 n2 B' P' G3 g! P
- ) L8 `& j# s8 E: q' R
- /* Copy data from Flash to SRAM by CPU */& H6 _& z. g+ K
- for (counter = 0; counter < (sizeof(aSRC_Const_Buffer)/4); counter++)
1 Z0 P& ~. k8 A- M$ I9 q - {
- T; q m& V. ]9 H* Z0 e - *pBuffer++ = aSRC_Const_Buffer[counter];3 y% D! {0 I4 @4 P
- }8 A# E' h9 V8 i
-
$ K0 L$ G$ J+ M8 b2 X - //* Configure and enable the DMA stream for Memory to Memory transfer */
9 E- P7 K7 C/ e3 T - DMA_Config();0 S9 e' ^' o$ w% E
- - h3 v9 ~6 z Y6 S' ^( M6 s/ h
- /* Wait for DMA end-of-transfer */0 q( a! ]( c" H; O' W9 N
' o! J3 p7 N7 G: A( z) f- while(TransferCompleteFlag == RESET)! v! p+ ]# g/ Z
- {: a; X+ g; F( ]' P* }0 _* r4 [
- }' B' t3 o; D2 r2 m
' T: a7 G1 I0 ?! C( ^- /* Check data integrity*/$ U5 l9 V( e; Y! `( t1 V
- pBuffer = (uint32_t*)&aDST_Buffer;
- ?. E0 y9 z d& v - for(counter = 0; counter <(sizeof(aSRC_Const_Buffer)/4); counter++)( X5 Z1 Y8 J: c% }: [
- {
9 _9 O# d6 w& f4 ?( U( G - + B3 L: h# B0 r1 I
- if(aSRC_Const_Buffer[counter] != *pBuffer); T3 e2 k1 \- x' L' q6 i. _
- {1 i9 p6 C) C' P1 Y+ T
- compareErrorDetected++;
4 X* I; W5 t9 s% m# T E - }4 ^) N3 d' m; B) Y
- pBuffer++;) d" |! H% w& U3 a
- }5 n+ s P0 m2 J% R |& k
- / \/ L9 }5 ?& F
- if (compareErrorDetected != 0)
, Y3 p! ?( ^% G$ g. o - {
& O/ z/ }! N! F5 b( T4 l6 p) n. O - /* Toggle LED1 */
8 q5 a0 S& b7 ?" k+ W8 w v* J( a - BSP_LED_Off(LED1);% p) ]# g: _) G5 {7 b7 w& }
- compareErrorDetected = 0; , F2 }7 p1 l, o1 V: \7 t) u
- BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation failed! ");
. M9 R8 k3 ?' B Z1 D- I - }
+ P) p: Z' N7 V - else
" d; q. V3 Q9 Y( s& ^8 {7 o: ]7 Q7 R - {, }& \3 f* i) b! e( e* R+ ? T
- /* Turn LED1 on *// ^! e8 c, s: r$ c( Q5 M! k+ z
- BSP_LED_On(LED1);2 |9 Z4 L1 J P+ [* O- ?5 \& b6 r
- BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation success! ");( O0 E2 \6 i0 P: Z0 z: F8 r
- }
{5 O% d6 o0 H8 q5 f+ { - " l4 h9 r- e5 W e, c( `: x- G/ P9 ~
- while (1)
! f6 {4 b) l! y. K; J3 X: o B - {+ z S$ p4 H5 x/ ^! t/ b% v
3 F' d# j3 ]! ~1 m& r- } r2 H2 \2 k v. y% L
- }, u9 P1 G7 @ e* d1 o
- . \" b- J' `3 Z8 {% z: i" r: v, H
- static void MPU_Config(void)2 `; ?2 S$ h j. @3 G7 d
- {( Y, y u; Z( w5 h
- /* Disable MPU */
; h# P1 P( e, [" Y. p& |7 c - MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;) z# P/ i2 o0 S, v1 M; x0 ~8 d
. d ` O# Y4 [' d( k; Z- /* Configure RAM region as Region N°0, 256kB of size and R/W region */2 m6 m- X" i- `; r. X, x8 p, W7 x. C* E
- MPU->RNR = SRAM1_REGION_NUMBER;1 F; `; P# j$ w
- MPU->RBAR = SRAM1_ADDRESS_START;
- P3 f' T7 d @3 L1 k r - " Q8 w: E9 }$ @ n- E9 b9 V' @
- /* Write-Back policy */
! e& Q2 n! X8 T2 A; b+ K# }6 ~* X - MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | MPU_RASR_B_Msk | SRAM1_ACCESS_PERMISSION | 1<<MPU_RASR_TEX_Pos;
, w& Y* W- C$ I$ ~& L2 e - ' L8 t6 |$ W# F8 j
- /* Enable MPU */3 H+ D# ]! J6 f4 | C2 m
- MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
; T5 y: E1 U7 I* D - }
3 N' g0 O6 k* O
复制代码
% t8 A: E( i& R为了确保 aSRC_Const_Buffer 在 Flash,aDST_Buffer 在 DTCM,我们可以在编译完之后查看 *.map 文件,如下:$ S4 z" }5 z! G9 U: ] r1 ~. d
4 {4 y6 S/ d7 G3 _9 h1 K0 K* [- O+ H& V: D
8 ~$ O+ _# y Z) W% ?, v5 X图3.4 检查常量和变量分配情况 " n9 u2 a* Q" `/ o5 V( s" Z
5 J! l( u& r5 @6 x5 b; A, `! a$ y( d下载到 STM32F769I-DISCO 板子上,显然,由于此时开启了 D-Cache,会出现数据不一致的现象,执行结果如下所示:8 { t7 j+ u& m) V# o2 x4 m2 ^* H
6 r$ Z; N2 W' l/ L1 \3 I7 ]+ x& s$ g' v- ~* c" ~
' C+ u X! k- b+ j( x! f6 |
图3.5 Cache 数据不一致 * G ^* l; b: @4 u5 u- Y; y: j
" G( o/ f+ {. y1 K5 V0 @2 }# D
3.3.3 解决方案$ e. o. o7 U7 N% R# g8 c
(1)不启动 D-Cache
4 T+ i% G& k6 ]2 ^' m# u* O N
7 g- P) ^" ]7 @% z! N注释掉 SCB_EnableDCache();
3 J* x% L9 {4 M+ ]! M9 U
, N! p x# }" `6 c4 Z- J" n不启动 D-Cache,当然也就没有了 Cache 数据不一致的问题啦~
+ n2 L- G9 ]9 m, A0 @
' a5 f8 `) P9 t# l2 [: p(2)将 SRAM1 相应区域设置为 shareable3 {/ f* s' ^3 i+ x* y, k
9 P. G) i$ I# V4 @1 i4 v9 `0 I/ ~! E, q通过 MPU 将 SRAM1 相应区域设置为 shareable,MPU_Config() 函数处理如下:
# h# z$ q" X# a7 C- static void MPU_Config(void)' A! E/ g$ K% K% H+ |! ]3 I
- {; u. j' k, x1 w1 g5 | n
- /* Disable MPU */: Y1 Z5 I. P3 t) S1 C! Q) G" t
- MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;. R2 @. v8 P# H( I/ F
- . ^' W% F. Z, E5 b. J0 i0 D
- /* Configure RAM region as Region N°0, 256kB of size and R/W region */
! a0 q6 y z9 @& p% J: ~. U3 N - MPU->RNR = SRAM1_REGION_NUMBER;
, K/ _# Q0 Q- j( u; p - MPU->RBAR = SRAM1_ADDRESS_START;
3 b5 W- q m2 ^+ W) _4 B - 5 G3 D" e' n, R6 d
- /* Shareable */
0 T$ {$ J" J& K4 _ y - MPU->RASR = SRAM1_SIZE | MPU_RASR_S_Msk | SRAM1_ACCESS_PERMISSION;0 K& g- S' E3 S2 [
- 5 V: g- ~8 Z& g- ?: D2 v' A# f
- /* Enable MPU *// l( j8 ~, G5 l# j Z2 t
- MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
\' t8 Q# Z$ s) ~, m C3 c2 P4 } - }
复制代码
* V! O# m+ D! d& V; _) r(3)DMA 访问 SRAM1 前先 Clean cache
- [4 u4 i$ A4 P6 b! C, @ E+ P; n% G' b2 d: Z' ]; N+ |( M
在启动 DMA 访问之前,程序员需要在合适的地方将 D-Cache 数据回写到主内存中,也就是 Clean 的操作。
9 _) }% K9 `8 e
: S) N$ R% q) Z2 z9 z* g( |: A) n在本示例中,可以在 DMA_Config(); 前调用:8 N( l* x! { h D
或者
4 q8 M) s6 J" ~9 s" n2 `- SCB_CleanDCache_by_Addr((uint32_t*)SRAM1_ADDRESS_START, sizeof(aSRC_Const_Buffer));
复制代码
7 K3 H' K6 N% E L' b5 B9 a2 s; D(4)将 SRAM1 相应区域设置为 Write-through 策略7 O3 T$ q' ]* O* N8 A% c, \
- T: \% \/ N3 F v8 }8 V9 s6 ?通过 MPU 将 SRAM1 相应区域设置为透写模式(Write-through),MPU_Config() 函数处理如下:' T3 D% U7 o; z- J' w& e- i
6 A0 P6 [7 s. a7 }
- static void MPU_Config(void)! q& m5 _7 Y, w
- {( c" d/ B _7 z$ a7 t6 C! c- O6 D: j" ]
- /* Disable MPU */
5 l H9 f6 l! ]# w/ y3 }. d4 Q - MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;; T0 G; o3 B$ @' c
- , q# z ? Q3 K" Z5 d) E1 ]
- /* Configure RAM region as Region N°0, 256kB of size and R/W region */
. x7 K! ?/ H, @, `5 ~; R - MPU->RNR = SRAM1_REGION_NUMBER;
, G, X) S% I3 x4 E - MPU->RBAR = SRAM1_ADDRESS_START;
: E; y! {& t# d |2 F& _- W
! `* P- T- q. L- /*Write Through policy*/
( U2 O/ l2 I% W - MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | SRAM1_ACCESS_PERMISSION;) }/ H( I7 M) S
- {3 D" b; m% h. X" [- /* Enable MPU */
* K' D8 ~4 J1 N0 d( d+ K9 ] - MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
% a3 N" E. k, C6 w7 a; G g - }
复制代码
, K7 A( C# l$ c2 m* R P(5)将所有 cacheable 的空间全部强制 Write-though7 @* M" W& |- X0 y2 g
( J: R4 d) W& e0 o
通过 cache 控制寄存器,将所有 cacheable 的空间全部强制 Write-though 模式。2 l1 F8 s) V$ T# ]. K/ u
# `& _ @- X; R/ j2 j3 d. {4 g
2 u& M* J" y2 [- o U
- a4 ^4 ^% I% r% L4 M图3.6 CACR 寄存器(来自 PM0253)
7 S9 l; r' N8 i' s/ d" m7 ^
' |' t1 b% X. Z5 ^在初始化的时候进行设置:
* O! ]/ v: q! Z! s+ |* P
# ]% I" H0 n: v宏定义为:" _1 W2 |% H V/ o! P/ ?& a
- #define __FORCE_WRITE_THROUGH() *(__IO uint32_t *)0xE000EF9C = 1UL<<2
复制代码 ( g5 h* T! w3 ~0 j- ^
以上这是都是较为常用的方法,在实际的开发过程中,为了提高性能,一般都会开启 cache,同时将其配置为 WB 策略,这就需要开发者在使用时特别小心!8 x& A! S( v+ C7 b6 c" M, }5 l
" X/ I# S. H2 W2 V4 x6 i) \9 a# x值得一提的是:对于第二种情况(图3.2),就不是 clean 操作了,而是 invalidate。需要先调用 SCB_InvalidateDCache() 或 SCB_InvalidateDCache_by_Addr() 去 invalidate 相应的 cache-line, 这样当 CPU 在读取时,会忽略 D-cache 中的内容,去真实的物理地址读取对应的数据。/ g, }/ f, N! F: Z$ \! l. c+ x
+ ^& ~5 o f$ }5 O; w5 H& K7 [
7 ?: i/ r R) F$ e7 t" v
4 ^5 m5 A K i5 ?
图3.7 Cache 数据一致 # J# }) Z, _! b$ u, |
7 \( [% W! K5 Q6 m! x4 d8 E; u
好啦,通过上述几种方法,就可以解决 cache 数据一致性问题。当然,除了我这里提供的,还有其他方案,各种方案各有利弊,要根据实际应用场景去衡量,这就是嵌入式程序员展示才华的时候啦~
3 x! B: Z" |& u8 h/ u( G/ w7 p3 W4 E1 Z; i
W6 m( V0 W @( t6 I ~& ^1 |$ P4 p9 k7 D
|