3. Cache 一致性问题- @% S4 m$ s! ]# e% [- g& o
3.1 什么是 cache 一致性问题/ ~! W) @4 y# Z& c A% k$ s2 h
所谓的 Cache 一致性问题, 主要指的是由于 D-cache 存在时,表现在有多个 Host(典型的如 MCU 的 Core, DMA 等)访问同一块内存时, 由于数据会缓存在 D-cache 中而没有更新实际的物理内存。
2 h* y9 c1 X$ c1 w; r( h5 K' s; A; {1 q9 T s0 `* q$ j
在实际应用中,有以下两种情况:
8 _1 H3 \- [: A) i& H, D
) U d& f {3 e: Q6 c3 f7 y# V9 r第一种情况是当有写物理内存的指令时,Core 会先去更新相应的 cache-line(Write-back 策略),在没有 clean 的情况下,会导致其对应的实际物理内存中的数据并没有被更新,如果这个时候有其它的 Host(如 DMA)访问这段内存时,就会出现问题(由于实际物理内存并未被更新,和 D-cache 中的不一致),这就是所谓的 cache 一致性的问题。
/ h4 r9 _, g' v. K! m$ W4 a
: A: s9 l% \* C
+ y5 H2 \$ J8 D' G6 N, f1 O) g$ P
图3.1 Cache 一致性问题 第1种情况 $ o% Z' G, j( _5 \- U) {
! H- u0 H: W# a9 v第二种情况是 DMA 更新了某段物理内存(DMA 和 cache 直接没有直接通道),而这个时候 Core 再读取这段内存的时候,由于相对应地址的 cache-line 没有被 invalidate,导致 Core 读到的是 cache-line 中的数据,而非被 DMA 更新过的实际物理内存的数据。6 i+ ?0 n, V! P. q! u
6 E# ~% |3 z! ^# g& W$ C/ H( G3 T; ^( E
5 z& v; i2 L, t# e图3.2 Cache 一致性问题 第2种情况
2 ^% V9 L( I9 e( [9 u. Q
. s2 X- l1 T0 l2 q) R# c3.2 如何处理 cache 一致性问题
9 d* L$ ]( X7 z8 b6 [7 d我们知道,Cache 机制是为了提高存储系统的平均读写性能而设计的,但是这种机制带来了数据一致性问题,然而,却没有对一致性的硬件支持。
3 d8 _0 w6 P( G4 x9 a4 C9 `4 z$ o+ n% R; a1 P
因此为了解决一致性问题,一个办法就是禁用 Cache(cache 都禁用了,肯定不会有 cache 一致性的问题啦~)。但是如果你选择使用 STM32F7 这样高性能的微控制器,又不使用其带来的高性能特性,那你为什么要用 F7 呢,用 F3、F4 不就得了么?所以为了提高性能,还是使能 cache,并积极解决 cache 一致性问题吧。4 s4 s `9 p U, e
* V7 p) N3 h0 K1 q. U好吧,解决 STM32F7 的 cache 一致性问题,有两种可选方案:
; {; j3 \5 e& o0 F$ v. V. A7 g3 B Q' G L5 t8 L7 `
所有的共享存储器都定义为共享属性
V3 D& J h+ O+ O+ o- Q) b. t# J% W% K, u
这些区域将默认不被缓存到 D-Cache。
: {1 a& {; k4 S+ Z3 J+ l8 r* q所有的操作都直接针对二级存储器(内部Flash,外部存储器),性能降低。+ r( h4 }( m& V/ q4 D* B
因为缓存对这些区域是透明的,写软件更容易。
" k* A6 a; |$ \; F0 i+ {% a
, c# s4 _; g# l, ?2 R; L' j通过软件进行cache的维护
* V( H9 G/ _5 P+ A1 b1 Q' C(1)Cortex-M7 的写操作要是全局可见的6 F; V2 z2 z' t; s! P) }3 Q8 {
6 Q) A2 }8 Z+ Y0 z' _使用透写属性(通过 MPU 设置)。
% s! G4 W& F: v% E! n% |% [使用 SIWT@CACR(Shared = Write Through)。
" z9 t1 X7 N" _: \通过指令清 D-cache,然后所有更新位置禁止 D-Cache操作。
, P4 X+ b# v. R. ]" _(2)其他主设备的写操作要对 Cortex-M7 可见
' C* f% z# M9 P/ d
- w- x' @+ a! [8 \) W+ Y i比如作废 Cortex-M7 Dache 中数据。5 f* j6 F7 N3 b4 a% D
3.3 示例2 ~+ B$ l8 g+ q/ s- F2 \
3.3.1 程序描述 x: h; S- C; V" c- w
(1)首先将地址 0x20020000(SRAM1)处开始的 128 字节初始化为 0x55。2 o2 K$ g8 l- L
(2)将 Flash 中的 128 字节的常量数组 aSRC_Const_Buffer 拷贝到 SRAM1 地址 0x20020000(pBuffer)。- M9 {* ~/ C- S ~
(3)配置并使能 DMA,通过 DMA 将数据从 SRAM1 的地址 0x20020000 处拷贝到 DTCM RAM 中的数组 aDST_Buffer 中。
2 L( b' y. E5 q(4)将 Flash 中的数组 aSRC_Const_Buffer 与 DMA 读出的数组 aDST_Buffer 进行比较。
9 f; h) ?/ w. R" g% H' S
- v3 Y2 u8 k- k1 _" Z! ?, x, [显然,这个例子中的 cache 一致性问题, 展示的是上面(图3.1)的第一种情况。也就是在 Write-back 策略下,CPU 先去更新相应的 cache-line,然后 DMA 去访问对应的内存,从而导致数据不一致的现象。
* `& a) y3 ?( W2 Z3 E5 ]4 \9 z& B+ |( y+ a0 a
程数据的传输流程和路径如下图所示:5 k6 ?$ J0 L4 N9 t/ q
Z. V9 m& f2 [
; T0 V. R0 W6 b% k3 v l, @3 R/ X8 I8 k
图3.3 Cache 示例数据传输框图
% K, j+ m- O X: C L
9 [3 {: d* E, D+ q6 w7 H# D7 z+ W3.3.2 复现 cache 一致性问题
# ^! v0 j* n! N) ^( c) N# a我们先来按照示例要求编写代码,复现 cache 一致性问题。有些人可能会疑惑,变量数据怎么放到 Flash、SRAM1、DTCM?实际上,可以通过一些相关的配置文件进行设置,比如 icf 文件、scatter 文件等,当然,这跟所使用开发环境和编译工具链有关。
, M N* B4 E! ~: d! W, Y# W
% G0 _+ i# V- X) H( l本文所使用的环境是 IAR,其链接文件 *.icf 如下:. \" v `) f- G& V3 @5 y
; T1 x" T$ e2 Z1 F4 W) A* P5 n( l. S7 {9 @& w5 j
/ @& Q" l; J$ d! _" d
然后将 aSRC_Const_Buffer 数组定义为常量,即可分配到 RO 区域,aDST_Buffer 定义为普通的全局变量或静态变量即可,因为内存区域从 0x20000000 开始,也就是 DTCM RAM。6 u0 p9 P, O- }' ^
. E& Q( b' i) v8 o/ Q2 n3 q/ Q7 ]3 ?好了,代码主体部分如下:' L2 X7 J. L! j4 e8 r9 g- J5 i
7 G3 Z+ F1 `' ^7 X. `' Z- #define SRAM1_ADDRESS_START (0x20020000UL)7 X1 Z0 y! K% Z4 ~! |! W r1 `
- Z0 y3 i5 \2 [5 m6 P' t" m3 {
- static const uint32_t aSRC_Const_Buffer[BUFFER_SIZE] =
: t' I- G1 q0 H, m- g - {
9 b$ g; s( P" U; _ - 0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,. N- y# q3 ?6 V' w3 n/ j" e: H
- 0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,
7 h; k' { A; t: @ - 0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30," U$ W5 [; x: B% }
- 0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,8 M1 F; N7 t4 z
- 0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50,
! K2 a( _) r0 c6 {; b+ M, A - 0x51525354, 0x55565758, 0x595A5B5C, 0x5D5E5F60,
7 O6 T5 R1 Y6 _' b4 c- g! |9 R - 0x61626364, 0x65666768, 0x696A6B6C, 0x6D6E6F70,
7 X8 @0 n0 k- }! ?, l! T) B, `2 l - 0x71727374, 0x75767778, 0x797A7B7C, 0x7D7E7F80+ _1 F9 m8 T E8 G/ O# g
- };% `# p* d/ u4 C! T
: Y: ]+ F7 {4 e1 S/ s m @- static uint32_t aDST_Buffer[BUFFER_SIZE];
4 D0 @- K* X; `4 l3 Q5 }1 N
2 |1 T/ S {- |% W' x" O- int main(void)
/ e I! L' i5 ] - {
- m: e9 L# \% v' V% n* B: R; q - uint32_t counter = 0;
$ a4 i" U9 {7 ^- i+ v$ F2 r - uint32_t *pBuffer = (uint32_t*)SRAM1_ADDRESS_START;" R t$ n' J$ r$ _7 `- h0 Q
- 2 O: b, c5 C% D0 ?) {
- if (HAL_Init() != HAL_OK)
4 z! c! n; J8 W' w - {
! E+ e' x" }- `+ Z: O& e; O - Error_Handler();
; y O# n7 i4 Q0 L+ s( f - }, L9 w# W2 [! Y2 p. Z; L
- ! k) W/ Y- U% a [+ }( O1 t
- /* Initialize LEDs */
& i- I, @ T' V/ ]/ O - BSP_LED_Init(LED1);* S$ a; L% }5 D! |
- % E5 E7 G1 S1 ?$ J% g
- /* Configure the system clock to 216 MHz */
8 E& T, E% [" o* [4 `8 g - SystemClock_Config();; N9 F9 z7 O! I5 T( X
- BSP_LCD_Config();
$ S6 v" N1 m) e9 E$ @. B% F2 s - + _' ^( { m( k; G, a# j
- /* Set to 1 if an transfer error is detected */
0 l2 M. A& t$ W0 P W6 H6 a - transferErrorDetected = 0;/ ? J h& ?5 P0 L
- 6 }+ L/ q6 A/ Z& V7 W
- /* Fill 128 bytes with 0x55 pattern */3 _' [' @2 s/ x6 N; [8 V" {3 Z
- memset((uint8_t*)SRAM1_ADDRESS_START, 0x55, sizeof(aSRC_Const_Buffer));
2 j! j( {1 p2 X j: k
- Z+ m+ S1 W# b' `+ h0 \" v& g6 m- /* TODO:Enable MPU and change SRAM region attribute
, ?5 x4 }1 x, w" Q2 _; E0 K) z - * set write-back policy on SRAM */0 S8 |2 F C4 ]' B
- MPU_Config();+ a6 E% `4 k+ @5 R+ K6 j5 C6 d* |
- ) `+ a1 Z; t$ q$ O# p* h
- /* Enable Data cache */# X h6 S A7 d* W* K' P
- SCB_EnableDCache();( T6 g1 a/ ^+ ]& `- a% `
- : B# i N$ P# G5 l: o! b
- /* Copy data from Flash to SRAM by CPU *// J, y( v: _1 X; u% R
- for (counter = 0; counter < (sizeof(aSRC_Const_Buffer)/4); counter++)) l" e4 }6 [0 D: i" y
- {. y6 b, x9 u% h. c4 D
- *pBuffer++ = aSRC_Const_Buffer[counter];/ Z8 D a; ^# e% s4 M& J/ F
- }, K) A1 Q1 y, o8 h3 U) g
- 5 ?# N a: h" ~4 k/ X! V3 J% y% X+ A
- //* Configure and enable the DMA stream for Memory to Memory transfer */
. }' L. w5 p+ [1 T u" j - DMA_Config();
: T5 O1 [& ]* H: X+ y* O - " s, R3 Z8 Q' u' H) P
- /* Wait for DMA end-of-transfer */
6 A ^1 J/ l% T
* s9 O$ H3 _! c) ]! u% `1 o8 ]- l- while(TransferCompleteFlag == RESET)7 S# {; k' R! t- T, U
- {
+ p( v$ m7 @# z1 A+ x - }& q. x- }) j9 }% _) H- s, E
- * @' L6 }; P [1 m8 t
- /* Check data integrity*/' }& ?. U) `8 d* ]3 J
- pBuffer = (uint32_t*)&aDST_Buffer;
" y* v/ D% t; M2 X, `$ ?) c6 G - for(counter = 0; counter <(sizeof(aSRC_Const_Buffer)/4); counter++)2 _& S. ]1 Q$ e4 X* O
- {
. C0 _- m" `* _7 @1 z1 q& q; q' u -
' s/ l; e& ]! T7 } { - if(aSRC_Const_Buffer[counter] != *pBuffer)! P: O" Q- S1 `& M+ Q
- {
$ i. a0 x a' K5 ~- K* ?& U- \% t - compareErrorDetected++;
$ I t! U" h- a; | - }0 y+ _( h9 p3 r" Q8 \
- pBuffer++;+ I! A. Z- j8 W% u d$ U
- }' } m, Z6 x3 L: p j+ w( J( m
-
/ I( r0 u& p0 O2 u - if (compareErrorDetected != 0)5 G# @8 O; F/ B X
- {
! B& |( _% Q# t# T4 x( P% Z6 v - /* Toggle LED1 */9 d: ^9 \+ ]. Q, w7 g9 ~
- BSP_LED_Off(LED1);% ~7 o7 X3 r7 A% G! m2 c& L
- compareErrorDetected = 0;
8 Z4 b$ e, T- ?/ S7 s - BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation failed! ");! t. C6 D& C, n4 Q9 K
- }
. y# D5 K1 D( K+ [ - else& `7 }8 J) @2 G! P: @. m- P
- {& R1 t& I2 K; a
- /* Turn LED1 on */
3 c) [) [$ f2 ?, ]8 s6 n - BSP_LED_On(LED1);8 d- h# P$ {9 S I3 V$ O
- BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation success! ");# P! C/ ~% P' J4 t9 x
- }
7 X2 R, X+ J: m# u: A' [- h
# | D8 K5 u3 b$ T+ `2 j- while (1)
* n3 t9 ^ z: V3 [7 ~: n/ B - {) N$ h5 K% j% M) e+ n' W0 t
: K) k% M G- ~: n3 M- }, c. z! {/ u9 i! s- j3 k
- }( H( ]* h! {2 `- j2 [' D2 p
- : ?) x7 O% j! ]) T9 Z! T
- static void MPU_Config(void)& s1 p3 t# A3 D: O
- { m. w& A, ?$ r- N( {/ H: k
- /* Disable MPU */* x0 d" f5 }4 O- j. f$ j
- MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;7 U; q$ w9 I. h- f, N
- " j, c) _" O2 [- Z- P! }6 I
- /* Configure RAM region as Region N°0, 256kB of size and R/W region */9 X* E% h3 m0 _9 b0 }0 C
- MPU->RNR = SRAM1_REGION_NUMBER;* |# i% l3 Q. p
- MPU->RBAR = SRAM1_ADDRESS_START;/ {" s# k- A0 b8 _9 Q8 \$ l
-
% {8 i @ O+ K - /* Write-Back policy */! y* P7 Z( O! p* y
- MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | MPU_RASR_B_Msk | SRAM1_ACCESS_PERMISSION | 1<<MPU_RASR_TEX_Pos;. {0 L: h4 C6 q$ ]; _2 X( K
/ X T' v% o% X( `, X/ \$ ?- /* Enable MPU */
- F8 U5 M* ]4 e" f6 e* w0 d - MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
5 N O7 U& n5 w - }
: E1 J: V+ `' o$ p1 b1 g. f
复制代码 4 b# ^4 t( H2 f! Z
为了确保 aSRC_Const_Buffer 在 Flash,aDST_Buffer 在 DTCM,我们可以在编译完之后查看 *.map 文件,如下:: p- c: W8 k8 {& K* b0 w) U# `/ i t
4 N' M J! d# G+ a/ M I" ?' n- d
( {$ p3 A3 E. o
- b. [& B; D9 r- y, G图3.4 检查常量和变量分配情况
1 C- R& e2 m6 w: u2 R V7 z; }) Q3 q+ V e( ?7 q
下载到 STM32F769I-DISCO 板子上,显然,由于此时开启了 D-Cache,会出现数据不一致的现象,执行结果如下所示:2 x& L- p* t5 s( W3 p
~) Z2 G) w/ X
7 \, d0 M, }4 ]7 v9 t8 z% x4 \* r" T) F6 Q+ g7 P8 {
图3.5 Cache 数据不一致 ) z E! L2 I* V* p1 v& N# A% G+ c
: |$ N4 e+ E( W' p2 A
3.3.3 解决方案: J3 u: {' i( {
(1)不启动 D-Cache0 e% p) Y+ q2 n8 j# P3 X5 `
4 S5 g4 k% C( o4 E" n. e注释掉 SCB_EnableDCache();* ]- w6 p3 `7 c9 H5 j
5 _/ q2 D0 u! h" `% s+ b# t
不启动 D-Cache,当然也就没有了 Cache 数据不一致的问题啦~; W( i0 t/ ~8 B
4 F* ] ^+ f% l% u
(2)将 SRAM1 相应区域设置为 shareable( T( G+ D4 |7 L6 I3 [3 H- u) N& @
. v2 |8 i+ }& r( c; |6 `, f
通过 MPU 将 SRAM1 相应区域设置为 shareable,MPU_Config() 函数处理如下:
8 J% s- q* }, @4 `$ W- static void MPU_Config(void)
& g9 x% Y7 b* K3 Z, `2 a" k$ M: O, O - {4 B& @% k" x- u. j m' o
- /* Disable MPU */
) F0 ` @4 b( e& b. o% p9 r& y - MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
4 O o. A d4 l u N
( y0 n5 j+ M# V6 K$ C- T/ i" K- /* Configure RAM region as Region N°0, 256kB of size and R/W region */$ l! F/ p' V: v+ @5 j
- MPU->RNR = SRAM1_REGION_NUMBER;
' h. `- N8 O5 z+ c# R - MPU->RBAR = SRAM1_ADDRESS_START;
3 @" [) K) h4 @. C
9 b# W7 f4 L" X8 ]( _8 F7 o- /* Shareable */
! {5 ~# ^4 b* z. n8 m - MPU->RASR = SRAM1_SIZE | MPU_RASR_S_Msk | SRAM1_ACCESS_PERMISSION;
! P/ }7 f% q1 r& ^ - + t1 [2 Z* R* t' n9 w
- /* Enable MPU */- c4 `/ U( {0 X* T* K, G
- MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;2 j8 T! E5 v# e
- }
复制代码
% C0 ]7 A% E2 f1 V$ \; u(3)DMA 访问 SRAM1 前先 Clean cache5 h* T+ E0 {* p! P8 E
9 g. g; `: _* h, R; H `0 J0 o2 q
在启动 DMA 访问之前,程序员需要在合适的地方将 D-Cache 数据回写到主内存中,也就是 Clean 的操作。
5 b7 x3 o) }) x! S! l; L/ T/ K2 P: Z
在本示例中,可以在 DMA_Config(); 前调用:8 ^2 \- H& d$ \! k) v
或者/ u6 g A- E# X$ J4 l' l6 n3 W6 r1 g
- SCB_CleanDCache_by_Addr((uint32_t*)SRAM1_ADDRESS_START, sizeof(aSRC_Const_Buffer));
复制代码 1 W* `' x: e( d9 P& k/ ]6 S/ {
(4)将 SRAM1 相应区域设置为 Write-through 策略
; O6 f" n7 o4 y1 d- k
2 w4 z% u3 y% @/ s4 i+ `/ ?通过 MPU 将 SRAM1 相应区域设置为透写模式(Write-through),MPU_Config() 函数处理如下:, ~" a1 P" `0 O- B: {
6 [. n: @6 R' t% _+ E4 h- static void MPU_Config(void)
( i; S8 ~1 G( I# X& D* v5 y - {
( p1 r8 ^4 Y! k# ` - /* Disable MPU */4 R3 H4 {! v! n1 u* J5 d3 W
- MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;- C: @& H$ u$ v- \: l
' o' T) s4 A# h7 @+ }! O" f- /* Configure RAM region as Region N°0, 256kB of size and R/W region */' |& g5 w- p) g* N
- MPU->RNR = SRAM1_REGION_NUMBER;" `0 w% D2 v4 n% l
- MPU->RBAR = SRAM1_ADDRESS_START;9 q7 r5 U* d( j+ g! K; w1 t. o: M: a
+ t7 L9 A' w& }7 @- /*Write Through policy*/8 z4 H' Y. n( E1 D9 V
- MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | SRAM1_ACCESS_PERMISSION;
3 `+ _2 u0 z* F- n6 D5 E( o6 c
$ H0 s1 j: _! n. P$ @2 a' ^- /* Enable MPU */) ^9 G0 ]; d* z) H
- MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
: | `5 S- v( d2 J! W - }
复制代码
* j9 a% I$ B, a6 D7 W' h) L2 c(5)将所有 cacheable 的空间全部强制 Write-though
& j0 m: s# O; b9 T# G9 [$ E& J( E
通过 cache 控制寄存器,将所有 cacheable 的空间全部强制 Write-though 模式。
1 K( Y7 `. v- p' Y" l
7 I0 T [: N* l8 m# C5 z8 b1 U; l0 l8 M- E, |
; n3 u8 O9 L K: q7 l( g# c! a: g
图3.6 CACR 寄存器(来自 PM0253)
. N! b3 W( f% \) x6 f/ }; y/ t( G0 J4 q
在初始化的时候进行设置:9 H4 m. `5 T) y: V3 o1 T
! `' i# V% {8 m# t7 ]宏定义为:
. I* t% g. ~/ L. D0 e. }+ [5 o- #define __FORCE_WRITE_THROUGH() *(__IO uint32_t *)0xE000EF9C = 1UL<<2
复制代码
" h3 i+ s4 X1 R- N以上这是都是较为常用的方法,在实际的开发过程中,为了提高性能,一般都会开启 cache,同时将其配置为 WB 策略,这就需要开发者在使用时特别小心!
# J" N# j: A* C0 Q; T. m7 L5 e3 m4 B( O. _6 C
值得一提的是:对于第二种情况(图3.2),就不是 clean 操作了,而是 invalidate。需要先调用 SCB_InvalidateDCache() 或 SCB_InvalidateDCache_by_Addr() 去 invalidate 相应的 cache-line, 这样当 CPU 在读取时,会忽略 D-cache 中的内容,去真实的物理地址读取对应的数据。1 Y) j0 K! o, Y& S) U0 w- ^7 |
; U8 f% M6 s, G7 ]- d& |
8 k# u5 l2 X; U# x0 C! r. R( T: w2 N% z* f- z0 L% n
图3.7 Cache 数据一致
: f' A2 i H5 C- D3 P5 L0 w+ Y! Q) {8 f* `$ U) z
好啦,通过上述几种方法,就可以解决 cache 数据一致性问题。当然,除了我这里提供的,还有其他方案,各种方案各有利弊,要根据实际应用场景去衡量,这就是嵌入式程序员展示才华的时候啦~
+ {% r& T+ W! @- G; n4 S% r$ j; e, j. a9 d: v
& Y; p, c: U% y$ g
$ {9 _# s+ p) e3 L1 ^2 G/ K% i7 c
|