3. Cache 一致性问题! I2 g6 W: a* U+ L) x# u$ I
3.1 什么是 cache 一致性问题( {5 l# C1 G. r+ |' A
所谓的 Cache 一致性问题, 主要指的是由于 D-cache 存在时,表现在有多个 Host(典型的如 MCU 的 Core, DMA 等)访问同一块内存时, 由于数据会缓存在 D-cache 中而没有更新实际的物理内存。
, T. P; e9 R2 ]/ e9 V& h/ D4 D" u- u3 Y& v- U* Z a
在实际应用中,有以下两种情况:
0 m& _9 a9 b, Q; v( t# F8 b
# X- [4 k1 q6 O0 `& o3 a第一种情况是当有写物理内存的指令时,Core 会先去更新相应的 cache-line(Write-back 策略),在没有 clean 的情况下,会导致其对应的实际物理内存中的数据并没有被更新,如果这个时候有其它的 Host(如 DMA)访问这段内存时,就会出现问题(由于实际物理内存并未被更新,和 D-cache 中的不一致),这就是所谓的 cache 一致性的问题。( B+ B; I/ Y# J
$ A: L2 H6 c9 H* Y: o, a |$ c' N
2 r; G! e8 d- ~! @
! y/ l" H# s8 J! b' C0 Q4 h* W5 f! u图3.1 Cache 一致性问题 第1种情况
+ x8 Q+ Z4 s0 n4 u7 O8 s9 r6 b2 E7 A- Y1 v' M( r0 w
第二种情况是 DMA 更新了某段物理内存(DMA 和 cache 直接没有直接通道),而这个时候 Core 再读取这段内存的时候,由于相对应地址的 cache-line 没有被 invalidate,导致 Core 读到的是 cache-line 中的数据,而非被 DMA 更新过的实际物理内存的数据。
! `! o) I# n% p9 R$ E( x, x: B
( _6 S( k9 G7 ^! Q
- J7 m% I" p% }' b5 K5 v! `9 L1 z/ V+ t. n" V0 E+ D/ z5 O# {) N# n
图3.2 Cache 一致性问题 第2种情况
/ X% i# t6 z3 m- C9 ?5 O
7 ?6 R, n# E; T0 v7 Z3.2 如何处理 cache 一致性问题: s; u6 a9 T/ S! Y3 c2 z
我们知道,Cache 机制是为了提高存储系统的平均读写性能而设计的,但是这种机制带来了数据一致性问题,然而,却没有对一致性的硬件支持。
/ w+ J& ~6 h4 `1 J* z; _7 w: M$ i
" l; E6 Q" [& x因此为了解决一致性问题,一个办法就是禁用 Cache(cache 都禁用了,肯定不会有 cache 一致性的问题啦~)。但是如果你选择使用 STM32F7 这样高性能的微控制器,又不使用其带来的高性能特性,那你为什么要用 F7 呢,用 F3、F4 不就得了么?所以为了提高性能,还是使能 cache,并积极解决 cache 一致性问题吧。. F4 x* v/ h) N) f& _& G
! R% O" s. p' S" {
好吧,解决 STM32F7 的 cache 一致性问题,有两种可选方案:4 f7 G7 k" N7 _
7 A3 U, t" ~" H( L+ u/ ]( h所有的共享存储器都定义为共享属性
0 ^4 K9 L$ j8 j& \0 W
' Q X$ N) d2 ^ B这些区域将默认不被缓存到 D-Cache。
" u4 G: u3 P7 [9 w& T所有的操作都直接针对二级存储器(内部Flash,外部存储器),性能降低。7 S4 y( W7 b; `' S
因为缓存对这些区域是透明的,写软件更容易。/ W' l, p$ Y3 G4 f, S7 K
) i5 O6 X$ v, l通过软件进行cache的维护4 Y! Z$ y0 t0 ~ p& Z
(1)Cortex-M7 的写操作要是全局可见的( }( d' w; e& T- s! \# G
6 _6 F* M6 y; C: M C1 O# n
使用透写属性(通过 MPU 设置)。
, `) z2 `; I+ i0 Y( a4 k+ D使用 SIWT@CACR(Shared = Write Through)。4 ~* l5 k2 m- `. V
通过指令清 D-cache,然后所有更新位置禁止 D-Cache操作。
% H- r% x4 D0 T6 ~6 W(2)其他主设备的写操作要对 Cortex-M7 可见
D$ S2 Z5 U* {& P5 L3 v. t
( {, ~/ N, m" G比如作废 Cortex-M7 Dache 中数据。
- w' M- m" Q1 A. S& P8 j3.3 示例5 ^ D/ y1 R, y. f$ i
3.3.1 程序描述
( \; W3 |9 ^$ @(1)首先将地址 0x20020000(SRAM1)处开始的 128 字节初始化为 0x55。0 }* C9 K8 ?) Z! P. B
(2)将 Flash 中的 128 字节的常量数组 aSRC_Const_Buffer 拷贝到 SRAM1 地址 0x20020000(pBuffer)。
- h2 X: n- h* P(3)配置并使能 DMA,通过 DMA 将数据从 SRAM1 的地址 0x20020000 处拷贝到 DTCM RAM 中的数组 aDST_Buffer 中。$ `% j5 n( G2 [2 U
(4)将 Flash 中的数组 aSRC_Const_Buffer 与 DMA 读出的数组 aDST_Buffer 进行比较。
- p; S e+ N6 ]/ V" w& w
$ E0 g0 T* h1 Z# N1 } s显然,这个例子中的 cache 一致性问题, 展示的是上面(图3.1)的第一种情况。也就是在 Write-back 策略下,CPU 先去更新相应的 cache-line,然后 DMA 去访问对应的内存,从而导致数据不一致的现象。1 T+ @9 e4 \( `' L3 }" x
0 f' g' r v. u: d3 R程数据的传输流程和路径如下图所示:
. J" g1 ^! l5 Q( [0 G9 }( g& p: _# Z3 f2 C a
4 c" e9 D! C' `8 ~
+ u; g' ^# k+ ]2 }图3.3 Cache 示例数据传输框图
+ A7 @/ f9 J8 V+ O5 i9 L- F' e
: p Q% A0 `3 ^3.3.2 复现 cache 一致性问题
/ F: r' g$ \4 x) V9 Z我们先来按照示例要求编写代码,复现 cache 一致性问题。有些人可能会疑惑,变量数据怎么放到 Flash、SRAM1、DTCM?实际上,可以通过一些相关的配置文件进行设置,比如 icf 文件、scatter 文件等,当然,这跟所使用开发环境和编译工具链有关。6 ^4 m i4 [' o4 N/ K3 \
T2 @) \( ^% Q) K8 \) y1 _
本文所使用的环境是 IAR,其链接文件 *.icf 如下:9 f( {1 h m0 t
$ ~2 U8 h m& Z$ q
6 x8 v e/ e ?8 J
3 d5 u4 K8 i& V2 J4 ~
然后将 aSRC_Const_Buffer 数组定义为常量,即可分配到 RO 区域,aDST_Buffer 定义为普通的全局变量或静态变量即可,因为内存区域从 0x20000000 开始,也就是 DTCM RAM。
M; @6 W6 ?' I6 Q& E, D% R1 I |- b% s
# y# T' U, m, t好了,代码主体部分如下:. x" ^0 t1 Z; d
& z# I, }9 j6 `% F$ r" @+ [- #define SRAM1_ADDRESS_START (0x20020000UL)
" S9 W6 K% S0 ]; N - 0 q- l/ t/ Z3 j% B6 e
- static const uint32_t aSRC_Const_Buffer[BUFFER_SIZE] = _( t" v# T- W4 v% V. f; p @
- {& a: K& L( Y+ _( h
- 0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
" _+ j1 o$ _" p+ E3 H - 0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,
: `" |! J. {1 {& ^' G ~, e! n - 0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30,$ o M2 O! q0 @, }, n' ^
- 0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40," F' b ~- \1 p0 Z. Q
- 0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50,
- t5 P$ } l- s; J) c0 } - 0x51525354, 0x55565758, 0x595A5B5C, 0x5D5E5F60,
" _9 \9 V! G, L; d - 0x61626364, 0x65666768, 0x696A6B6C, 0x6D6E6F70,
5 [, u+ G# L( W) U& {. v; ^, F1 a2 ^ - 0x71727374, 0x75767778, 0x797A7B7C, 0x7D7E7F80- p5 h% q5 E* m2 E9 j9 J8 h- U, s' @
- };! B I- k+ z7 [/ n2 w" i
7 E/ X3 l2 x2 U- o+ g6 `- static uint32_t aDST_Buffer[BUFFER_SIZE];
. ]3 _; ?& C$ O# J& S; | ~4 o* N - / u. F. m+ k7 T8 O, D' g
- int main(void)
2 n& ~' w* P" s! e- o* i% c2 @ - {8 A& O1 b3 c7 ]4 H2 O9 S* ~
- uint32_t counter = 0;6 N5 T7 u5 ?7 c) O- l5 q7 Z8 T
- uint32_t *pBuffer = (uint32_t*)SRAM1_ADDRESS_START;
& z( d. k1 ~/ B( e8 x/ s% } - $ i b9 f+ |% k+ H6 c9 _
- if (HAL_Init() != HAL_OK)! K- ?7 O U8 x. K
- {
6 o; U% L9 q; v" {1 Y - Error_Handler();
7 L% W. r x9 E% j5 {9 f - }
: G& u$ J2 z4 k& D+ _; K3 N& E - 7 j, ~- |3 Q8 s7 `, f
- /* Initialize LEDs */9 R6 Y9 S( a8 W/ d
- BSP_LED_Init(LED1);0 [& M A+ D6 k" C e$ l3 Q
+ a O" X. S9 E% V& S- /* Configure the system clock to 216 MHz */
. h. Q# W1 n. z: [2 U9 n' c0 \7 M - SystemClock_Config();6 m0 R( S6 B u& [ L6 U" \! P' v
- BSP_LCD_Config();
- p6 h: \. r# Z* C8 p0 d
f0 a: A7 g. Z! O1 o$ r- /* Set to 1 if an transfer error is detected */
m0 a3 v2 x6 r/ }, y - transferErrorDetected = 0;
& ]$ n+ Y/ ^, [# a4 {/ j -
- K5 B. ]6 r8 ]# Y8 @* { Y - /* Fill 128 bytes with 0x55 pattern */3 V$ G% C& g, U/ N2 [
- memset((uint8_t*)SRAM1_ADDRESS_START, 0x55, sizeof(aSRC_Const_Buffer));5 X n; W% P2 c4 L9 N, U, A" \
- + M7 C# e) E+ V3 S9 `: l3 U& [
- /* TODO:Enable MPU and change SRAM region attribute 3 h! H( {& N/ b3 ^# e
- * set write-back policy on SRAM */
5 [. t1 }! [+ K5 u. Q& I3 X - MPU_Config();
# _" r' m! ~! F/ S - - D# R2 i8 B* u6 V: ^
- /* Enable Data cache */
# D0 y% [& P% Q X" k s \ - SCB_EnableDCache();
0 C9 Q: |6 }& K. \+ K# o7 _
$ I, v4 a0 ~) K5 K, [, @% Z- /* Copy data from Flash to SRAM by CPU */% E" e5 t! S1 h3 o+ P9 S" x( j
- for (counter = 0; counter < (sizeof(aSRC_Const_Buffer)/4); counter++)
9 c7 k7 q7 X% Z2 ?9 J - {+ Y5 M, i Q/ v" Z* a+ u4 L4 K
- *pBuffer++ = aSRC_Const_Buffer[counter];* {( n0 u, a* B; N7 o9 y
- }
/ W9 j# e" d; t -
3 O ~3 c& K+ n - //* Configure and enable the DMA stream for Memory to Memory transfer */0 T& e' @+ F+ U: Z( O: Z0 Q' o5 K% |& N
- DMA_Config();
' A' L2 V4 N! u
' j% R3 A$ k: j8 U& B- /* Wait for DMA end-of-transfer */
9 U" v% v9 w1 }6 _% \5 ~4 E - ! A: o7 y1 `% A5 K8 p4 p- K
- while(TransferCompleteFlag == RESET)
, }6 [+ |+ M, L- U- W0 j4 `1 D2 v* p - {
6 o9 M5 W' i: [: ~/ O) G - }! ]/ C: l7 ^" R( `0 c
( R5 C J7 h. d$ |6 ~3 [. M- /* Check data integrity*/6 u7 `9 @+ p! L# J* U
- pBuffer = (uint32_t*)&aDST_Buffer;. Z, y: @. ^& Z8 H
- for(counter = 0; counter <(sizeof(aSRC_Const_Buffer)/4); counter++)* {( Z8 ~5 ~; e. K" ]
- {2 s; f/ L6 T$ _9 }$ l, M
- 7 C4 g0 \0 }$ v/ G" h
- if(aSRC_Const_Buffer[counter] != *pBuffer)
' m2 l; @4 N \& N% ]0 @ - {
8 S! `6 F; e2 ~+ B) P - compareErrorDetected++;
5 x$ X R0 G$ Z4 y* k3 L - }8 I+ G+ A. o& t
- pBuffer++;
- x) r6 {9 F4 d. H6 M0 t* k. L - }
& C( d& i p4 r" { -
7 k" M- a- H' X5 J$ |( v - if (compareErrorDetected != 0)8 \* F0 B! j3 p+ L; f+ s* f
- {, A3 s% j( N6 i: X& y6 I
- /* Toggle LED1 */
j! F1 T; a+ ?: Q- K - BSP_LED_Off(LED1);7 `) K9 s7 D, X7 b, G
- compareErrorDetected = 0; m8 K E9 ~7 Q
- BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation failed! ");
/ Q0 |3 x5 X( ]# |- L1 l - }& Z- O9 J" H2 o
- else6 G" D8 H& N5 k2 Z5 F
- {7 Z7 ~( q, T% z( P1 [/ C/ V: I, A
- /* Turn LED1 on */1 j4 }) k) r+ E# F+ m+ b
- BSP_LED_On(LED1);
# A q' F, C Z2 @' H( T6 @ - BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation success! ");
2 z1 W, t0 O+ d1 z, @8 u2 A0 f8 O1 \ - }
' }6 ^( w* A" U% Y: k2 W" u - : [) R/ i# \, u8 Q
- while (1)/ |" K2 R4 `! z8 l2 v4 R
- {4 |5 N- i. P7 d# r( r
! \ {9 s3 X; M* n. \/ V/ _- }* M. [9 f; v/ Y* g `
- }" D8 r! N- J0 x
2 M5 g: |1 j! P- p8 ]4 f" A% d0 |- static void MPU_Config(void)
1 j+ m: c" {9 J& Y3 r4 {- X, b - {
; E/ S% c' v- c/ F) }; ] - /* Disable MPU */8 h5 C; P/ h; |/ `) g
- MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
5 @! ]3 G p: q" G4 P( H. [: {( ` - 4 p# B3 E& |. }
- /* Configure RAM region as Region N°0, 256kB of size and R/W region */4 D3 y" f7 Y% q2 v0 ~
- MPU->RNR = SRAM1_REGION_NUMBER;' i3 C4 T) @# b3 ?! E' t0 |2 C
- MPU->RBAR = SRAM1_ADDRESS_START;
: g2 o5 q) P$ [& J$ f -
! g* p k& n! E, }' y9 L - /* Write-Back policy */$ j" S3 E* X1 c! S2 B l
- MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | MPU_RASR_B_Msk | SRAM1_ACCESS_PERMISSION | 1<<MPU_RASR_TEX_Pos;
% y2 p) |2 D, c; h4 {- `
6 A0 N2 `0 p; r+ S9 {- /* Enable MPU */ F# K7 ?, S. U5 E, B. y1 ?# \+ S0 W
- MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
$ P( L G. d, F& n - }% V6 P. r) R8 S1 J, H& ?
复制代码 - @- @( L; O$ r+ m- @8 O9 [/ t
为了确保 aSRC_Const_Buffer 在 Flash,aDST_Buffer 在 DTCM,我们可以在编译完之后查看 *.map 文件,如下:/ b7 A& f2 D$ X3 |' c, N! l
+ G D) n+ l y4 H/ i
( K% q; \' n0 _/ g! ^) P# j- U- J- @1 I( Y" K; u9 R
图3.4 检查常量和变量分配情况
/ E! ]# X4 W. @! X7 @, V2 a, i! ~- B- C% {+ }' q) [! V
下载到 STM32F769I-DISCO 板子上,显然,由于此时开启了 D-Cache,会出现数据不一致的现象,执行结果如下所示:: X& {. D5 k; m2 S. Y$ z6 I
0 @7 e- r/ V: p, r# Q) i
2 u7 f5 \" Z9 L, h5 q6 ?- P% @' Y9 |6 Q; a
图3.5 Cache 数据不一致 0 d' U3 \* B1 U# Q3 [% I
% _( F* z5 ]* K) y8 R3 `3.3.3 解决方案3 F7 C+ {( h" w! K; S) U
(1)不启动 D-Cache
. x& b8 k+ e5 u3 M: l5 M% R3 b5 Q8 r( u U1 }# B
注释掉 SCB_EnableDCache();
2 \# q9 F( ~9 r/ Y4 a
$ j. O" q! }' S+ p5 z' n/ K不启动 D-Cache,当然也就没有了 Cache 数据不一致的问题啦~8 T. M# q) E6 s5 m
6 ~4 f% H" x: Y, {1 v- H' n(2)将 SRAM1 相应区域设置为 shareable
; E" |% Y2 l5 ~) P2 ^- s1 @
# U' A) M* x7 h! O8 [7 `6 G通过 MPU 将 SRAM1 相应区域设置为 shareable,MPU_Config() 函数处理如下:, A' H, L* q; g) S
- static void MPU_Config(void)
3 g' Z( H& C3 a - {
( J5 |6 H& y2 g) N1 {* C - /* Disable MPU */% i9 ]7 f) X. i0 w3 y/ B
- MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
8 a9 o' _$ z" G! S" @ - 8 [& U' C1 g3 n1 X7 B7 R* \" C( j2 d
- /* Configure RAM region as Region N°0, 256kB of size and R/W region */
$ R" U9 U7 r5 G1 u - MPU->RNR = SRAM1_REGION_NUMBER;
0 R/ r) l7 c" j3 t) I - MPU->RBAR = SRAM1_ADDRESS_START;
8 P: h; A8 F" d& P) c4 V - 0 _/ C) E, }' W" h
- /* Shareable */
. {1 G( M/ x+ {3 p$ p - MPU->RASR = SRAM1_SIZE | MPU_RASR_S_Msk | SRAM1_ACCESS_PERMISSION;
" Q, e7 i+ J' p5 q& }( F - & @9 y' Z/ x& M" I: T$ }) \
- /* Enable MPU */
8 u* m! Y! O* _' b, e - MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
1 p! _; w5 _4 H' T - }
复制代码
' |% y" d1 V0 o( ?9 h2 i(3)DMA 访问 SRAM1 前先 Clean cache
) b( A6 P6 Y" F0 @. b
2 Y/ D# q0 t/ P0 _8 c p在启动 DMA 访问之前,程序员需要在合适的地方将 D-Cache 数据回写到主内存中,也就是 Clean 的操作。3 M0 n4 H7 \2 k
) q0 h" m+ @& R/ S$ j8 Z在本示例中,可以在 DMA_Config(); 前调用:0 V9 f6 F9 v3 H- I4 G5 T5 Q% C. k
或者2 Q; J! M* i( S6 f# G' s
- SCB_CleanDCache_by_Addr((uint32_t*)SRAM1_ADDRESS_START, sizeof(aSRC_Const_Buffer));
复制代码 + F% ^: Z8 P" _( i/ ^4 w1 L6 c
(4)将 SRAM1 相应区域设置为 Write-through 策略
. o0 C: g0 V* q5 u# y0 n/ ?
+ X) Y8 o" S' q, x# C" e4 ^! r$ x通过 MPU 将 SRAM1 相应区域设置为透写模式(Write-through),MPU_Config() 函数处理如下:
: [4 @- d; a: K+ s2 _ }0 b
7 t5 U$ d* N7 G# q- static void MPU_Config(void)4 f' D v+ u$ k. Y
- {
, t: L, a/ p& \; W ?; {' ` - /* Disable MPU */9 e" W% \) I4 l7 a* J+ E: R
- MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;! O5 C$ r1 z" z! L- @2 |
- 3 x5 V6 e$ t, y3 K% x Z
- /* Configure RAM region as Region N°0, 256kB of size and R/W region */) W7 |, A% R1 S0 [( ~
- MPU->RNR = SRAM1_REGION_NUMBER;
I5 O/ Y" `( m - MPU->RBAR = SRAM1_ADDRESS_START;2 H* ~- `$ ~& a0 g
- & m2 y: K; T, ?+ A0 F" j) {
- /*Write Through policy*/ o# G. w8 D9 K& z; e0 I; U& Y
- MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | SRAM1_ACCESS_PERMISSION;
4 V1 s2 {( w6 T, U
6 f% p8 G2 h3 z. b2 P- /* Enable MPU */" A) T6 ^# z" ?/ B# a/ P& i
- MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;0 z: |$ h( _& ~, M; b' n4 d# w
- }
复制代码
! f' ~$ R+ [% e' h X(5)将所有 cacheable 的空间全部强制 Write-though8 x1 `; T. y7 [: {; x
3 A4 L! c" e; `% Q2 z/ V# u
通过 cache 控制寄存器,将所有 cacheable 的空间全部强制 Write-though 模式。
V* p ^( x" S. a* `" }* @4 H3 {( ]9 D6 D8 ?0 ~7 Z8 ~
0 c9 \) X1 m8 R& Z' h: r3 s) X: G; b
图3.6 CACR 寄存器(来自 PM0253) ( V9 }, Z4 ?: h( S' G
8 _7 G) l# v Q7 j4 w9 y
在初始化的时候进行设置:* z; H: o: r1 i% G" T9 E& r( Z
6 R+ h2 K% {# R0 Z" t宏定义为:, B3 g6 }: ]: _- ^ |- n
- #define __FORCE_WRITE_THROUGH() *(__IO uint32_t *)0xE000EF9C = 1UL<<2
复制代码 + M4 z- U5 l# I' a0 h4 Y+ }
以上这是都是较为常用的方法,在实际的开发过程中,为了提高性能,一般都会开启 cache,同时将其配置为 WB 策略,这就需要开发者在使用时特别小心!
0 N5 v7 W: ]# c4 C! x6 t& o$ A9 b( l
值得一提的是:对于第二种情况(图3.2),就不是 clean 操作了,而是 invalidate。需要先调用 SCB_InvalidateDCache() 或 SCB_InvalidateDCache_by_Addr() 去 invalidate 相应的 cache-line, 这样当 CPU 在读取时,会忽略 D-cache 中的内容,去真实的物理地址读取对应的数据。
4 X+ v! Y3 G) {8 c: M7 Y1 X0 r, G$ l: W) T+ T2 s; p
0 L/ f2 G7 p& ^* K
8 o6 ]" U; a; ]0 Z图3.7 Cache 数据一致
) `+ Q W: u2 B9 @5 s9 Y2 e9 D: s2 _' [ W) Y
好啦,通过上述几种方法,就可以解决 cache 数据一致性问题。当然,除了我这里提供的,还有其他方案,各种方案各有利弊,要根据实际应用场景去衡量,这就是嵌入式程序员展示才华的时候啦~8 ~- e* A! C7 X
n! k. F4 [! o9 N. b+ A* D" R `% { D4 n4 D# d% W# d9 Y1 e
. S, k, `4 y( O1 ^) [
|