3. Cache 一致性问题
d5 J& r b4 l7 H! n3.1 什么是 cache 一致性问题
8 m7 k+ j6 i* h8 ~: D; z2 [4 v所谓的 Cache 一致性问题, 主要指的是由于 D-cache 存在时,表现在有多个 Host(典型的如 MCU 的 Core, DMA 等)访问同一块内存时, 由于数据会缓存在 D-cache 中而没有更新实际的物理内存。
$ f) k- |6 p+ D( A, U% h1 C5 i: }/ |7 j% I+ f3 b7 Z5 F6 t
在实际应用中,有以下两种情况:) D" o# g6 t1 C! p- e) v( }' R1 z
7 B3 t% G8 W( X$ u( N$ @: R6 x; s第一种情况是当有写物理内存的指令时,Core 会先去更新相应的 cache-line(Write-back 策略),在没有 clean 的情况下,会导致其对应的实际物理内存中的数据并没有被更新,如果这个时候有其它的 Host(如 DMA)访问这段内存时,就会出现问题(由于实际物理内存并未被更新,和 D-cache 中的不一致),这就是所谓的 cache 一致性的问题。( y& P7 V" R( i( c
* D# G5 [4 C; ^0 ?4 L7 |. e1 z
. M# J) r2 `. x: t( y7 D) a
, `& \" N+ e4 V% k, Z# M图3.1 Cache 一致性问题 第1种情况
2 @* L% {* ] L4 j/ o; \1 k1 ]1 k) b$ H. r1 G: x" f
第二种情况是 DMA 更新了某段物理内存(DMA 和 cache 直接没有直接通道),而这个时候 Core 再读取这段内存的时候,由于相对应地址的 cache-line 没有被 invalidate,导致 Core 读到的是 cache-line 中的数据,而非被 DMA 更新过的实际物理内存的数据。6 M0 b5 r g* ^& R; D9 ~
% f# m! X0 _; h2 ^( D4 J0 a ~6 O8 X. H, R; G; W I5 g) |
6 e7 m% W% E8 p o5 ]2 S
图3.2 Cache 一致性问题 第2种情况
+ H/ k' O' h. V+ ^' y. a2 N0 X: ^0 U- \- B3 R4 H" J1 k" C
3.2 如何处理 cache 一致性问题
. a6 g5 V/ A; m: i" C我们知道,Cache 机制是为了提高存储系统的平均读写性能而设计的,但是这种机制带来了数据一致性问题,然而,却没有对一致性的硬件支持。' P$ _# w" L# S% B
+ t/ f- D: K* l j' O' g$ w因此为了解决一致性问题,一个办法就是禁用 Cache(cache 都禁用了,肯定不会有 cache 一致性的问题啦~)。但是如果你选择使用 STM32F7 这样高性能的微控制器,又不使用其带来的高性能特性,那你为什么要用 F7 呢,用 F3、F4 不就得了么?所以为了提高性能,还是使能 cache,并积极解决 cache 一致性问题吧。. E1 ]: m, B9 v3 C4 I
! F4 l4 h3 @' r* {3 p- t好吧,解决 STM32F7 的 cache 一致性问题,有两种可选方案:4 P9 P9 x/ G5 Z5 Y, Z8 t6 R2 o% I
' ~& J Q5 E9 |3 I6 f所有的共享存储器都定义为共享属性) z# X/ S0 a7 @; q
' _- R- N0 K8 Y. A. ]这些区域将默认不被缓存到 D-Cache。) i% r' u7 ?6 F
所有的操作都直接针对二级存储器(内部Flash,外部存储器),性能降低。
' A7 ^7 j) S( G- ^因为缓存对这些区域是透明的,写软件更容易。
9 n# E0 y4 I8 K$ s% N8 f$ a( @5 S: B: g) g: d
通过软件进行cache的维护
2 n. ]; p0 x0 j(1)Cortex-M7 的写操作要是全局可见的9 V8 G/ p8 n8 s+ x
9 {7 } l# j" U' ?2 l8 S5 h使用透写属性(通过 MPU 设置)。" v/ X1 g: \% N( G9 A: j/ R/ j* t
使用 SIWT@CACR(Shared = Write Through)。
* H" k. }& | b0 s. h通过指令清 D-cache,然后所有更新位置禁止 D-Cache操作。
& I/ u$ k" \' H. n0 Y(2)其他主设备的写操作要对 Cortex-M7 可见8 B; D- ?* q: Z# G. ^/ A" M+ W
1 V- B- K+ Q- i1 L0 x7 ]6 `# e比如作废 Cortex-M7 Dache 中数据。
4 Z) F$ x3 H8 g: s+ C2 z, B3.3 示例( e4 u- M: x! F: Q4 \6 x/ `
3.3.1 程序描述2 t0 i- {: K& Q! _ n' `
(1)首先将地址 0x20020000(SRAM1)处开始的 128 字节初始化为 0x55。5 ~7 r$ f# |" B) n. o% C
(2)将 Flash 中的 128 字节的常量数组 aSRC_Const_Buffer 拷贝到 SRAM1 地址 0x20020000(pBuffer)。; a) T* q: t; b8 h, X
(3)配置并使能 DMA,通过 DMA 将数据从 SRAM1 的地址 0x20020000 处拷贝到 DTCM RAM 中的数组 aDST_Buffer 中。# C, ~. r- I% z4 [) H
(4)将 Flash 中的数组 aSRC_Const_Buffer 与 DMA 读出的数组 aDST_Buffer 进行比较。
' i" ?' \$ }0 [+ V o) l/ i; F
: p4 D$ a; B1 ^1 }' b% ` N) u; i; q; W显然,这个例子中的 cache 一致性问题, 展示的是上面(图3.1)的第一种情况。也就是在 Write-back 策略下,CPU 先去更新相应的 cache-line,然后 DMA 去访问对应的内存,从而导致数据不一致的现象。
8 X7 N- D0 H& m3 s* J! T! `1 }
2 b% T' e3 A3 B2 ~' G程数据的传输流程和路径如下图所示:- n3 B. K) o; x6 ]6 y- N
0 E" q9 E1 r) x% {' C4 Z1 u
" ^5 E2 v: D" K) `& q3 o$ s
* X: b: C4 j ?2 Z7 [% o
图3.3 Cache 示例数据传输框图
" E$ Q1 j/ n+ J& Y( k+ Y9 O& [! F3 n, K7 N5 h- ]
3.3.2 复现 cache 一致性问题
# U' P/ R' `! Y( K; t' v我们先来按照示例要求编写代码,复现 cache 一致性问题。有些人可能会疑惑,变量数据怎么放到 Flash、SRAM1、DTCM?实际上,可以通过一些相关的配置文件进行设置,比如 icf 文件、scatter 文件等,当然,这跟所使用开发环境和编译工具链有关。9 c+ W/ k3 }1 @8 e2 ^
- e& J3 |5 L1 Q% o
本文所使用的环境是 IAR,其链接文件 *.icf 如下:
# P+ Q6 u; j; j# W* c+ B. l9 j" x! F1 b
+ Y+ l+ H& I* [9 ^7 V; |7 u' n
: j6 K" R5 k! ^6 u5 D; g然后将 aSRC_Const_Buffer 数组定义为常量,即可分配到 RO 区域,aDST_Buffer 定义为普通的全局变量或静态变量即可,因为内存区域从 0x20000000 开始,也就是 DTCM RAM。
: I' U& `5 d# z, n+ g' \. z; u9 B) f& z# q( r- N* f- u7 j9 _
好了,代码主体部分如下:
" g5 @ S# J( ^: }0 i f6 O& a( R/ P6 m: [$ Z9 Q
- #define SRAM1_ADDRESS_START (0x20020000UL)
3 h8 ]: s2 A( U5 I3 ^$ w: u1 ] - ! \# e' Y1 `% U% h
- static const uint32_t aSRC_Const_Buffer[BUFFER_SIZE] =
) j4 }9 H$ m+ ^$ H2 J - {
) K; ]0 Z, N8 Z& @. U* o/ [2 B - 0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,: X% `4 z( p8 \+ J6 x& o- _: d
- 0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,1 e3 X. m( z6 ^4 r# O3 V1 c7 B
- 0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30,
4 Z9 `% w! j6 O: F, d7 C: F - 0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,' V% L/ x5 C% V7 J! N+ F
- 0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50,2 O% I2 o0 y. O- o$ i+ j: B
- 0x51525354, 0x55565758, 0x595A5B5C, 0x5D5E5F60,0 a6 M, l5 u! C+ G* a9 A
- 0x61626364, 0x65666768, 0x696A6B6C, 0x6D6E6F70,' ~7 N3 U! u, m/ w* z( [5 v9 H ~
- 0x71727374, 0x75767778, 0x797A7B7C, 0x7D7E7F80; S& W" \4 N5 z; Y4 Y3 `3 o" R3 a
- };
: N' s# z, ]+ m) g( x
5 i* e; \' G8 N% {9 Y- static uint32_t aDST_Buffer[BUFFER_SIZE];; o" S8 p" ^2 Y0 E
- $ u, f. E/ b# o
- int main(void)% _' M5 ^. j: R
- {
: Q+ t) Q8 `& _4 `( t - uint32_t counter = 0;
- S( @& H* S. u! H$ C - uint32_t *pBuffer = (uint32_t*)SRAM1_ADDRESS_START;! n& E! ^' C- f/ J5 I
2 Y3 l" w9 d. I/ j/ k/ [- if (HAL_Init() != HAL_OK)
9 C4 k1 q" D9 H4 A( W H% z4 t - {
+ R I% h$ `6 C, i# g- A+ X3 S$ _ - Error_Handler();
' f& R- u8 e. y - }! D3 D3 A+ ^0 r- _" m8 ?
/ v; z1 l" E/ Y) \- /* Initialize LEDs */
, b( E7 K w" \0 p - BSP_LED_Init(LED1);1 ~/ Z2 ^! K8 }, Z* J
3 G* X! k5 P0 C6 R2 U1 E5 F- /* Configure the system clock to 216 MHz */$ F$ L4 S% w R5 S
- SystemClock_Config();
9 C m' N- G9 ~+ L: G5 U: x) i - BSP_LCD_Config();- ?1 l# x+ p) T# |: G
- % o* T. r& O' h! I- k7 A
- /* Set to 1 if an transfer error is detected */6 I6 u# I& |- S
- transferErrorDetected = 0;! _( v8 W4 M5 V1 Z4 \
-
1 c4 l y- a& K4 C - /* Fill 128 bytes with 0x55 pattern */
4 Z' n, R* ?: k0 s6 F - memset((uint8_t*)SRAM1_ADDRESS_START, 0x55, sizeof(aSRC_Const_Buffer));
$ z* |4 a# X: y; [4 Q c - 4 ^3 q8 M( m1 G- v4 A4 b* y
- /* TODO:Enable MPU and change SRAM region attribute . q3 r2 p. @ j5 t L- Z$ w
- * set write-back policy on SRAM */
* h) |/ P4 C ?) i6 Q! j8 y8 a - MPU_Config();
' a6 I( J1 I# y
2 `6 R6 x2 I# C; v2 {: o9 a& {- /* Enable Data cache */
' e* r5 F; `$ b" U - SCB_EnableDCache();
: R5 o+ J! |* E% n F - 9 h% j, G/ O# ^4 k# P3 `
- /* Copy data from Flash to SRAM by CPU */
$ q: |" ^5 b j" M - for (counter = 0; counter < (sizeof(aSRC_Const_Buffer)/4); counter++)' m/ r: I% t% Q8 f$ |
- {
1 j+ F% U+ L$ W$ X4 V( P - *pBuffer++ = aSRC_Const_Buffer[counter];& M+ F. F- ` x% t6 U8 G# U+ I
- }
" P1 m' T4 G, v0 D( U& \6 c. m( [ - 1 d: Q3 }# ~& S% y
- //* Configure and enable the DMA stream for Memory to Memory transfer */
$ v" `, [4 V2 n3 M - DMA_Config();; O: Z5 X7 V4 p1 e# q
/ g# u7 D% j7 j9 o- /* Wait for DMA end-of-transfer */
+ |0 z6 T( S/ M. k
0 i7 L1 L- |+ k) r6 Y- while(TransferCompleteFlag == RESET)& f( @, w8 X3 H5 V
- {* j& p5 a. j% ?9 x# [
- }
. G+ `) ~+ F) M7 s - 1 `0 v" ~; P2 E
- /* Check data integrity*/6 l0 e' j3 M; h+ P4 M% Y& Y8 n" L
- pBuffer = (uint32_t*)&aDST_Buffer;
. V" C- E! s5 s - for(counter = 0; counter <(sizeof(aSRC_Const_Buffer)/4); counter++)7 t0 [9 H! ?6 ?% P
- {5 t0 l+ I0 H5 L7 j4 l% j
- , q# B$ y0 d! O9 j- m0 o
- if(aSRC_Const_Buffer[counter] != *pBuffer)$ X% _, s3 m0 [( \* J! J
- {
0 m. e4 u# a% q( [# }4 D - compareErrorDetected++;
~3 T3 X5 m+ | - }
5 Q% x: {% {3 H$ \ - pBuffer++;' o, U3 g' ^9 M K9 A. v
- }
& I; A# ]7 L4 t, A! i1 K* P - " t8 X5 ~. ?- |. _* @+ Q
- if (compareErrorDetected != 0)+ T# K' t' t- ?" t6 \4 N
- {
+ N% z6 Z5 L8 A) ^- F - /* Toggle LED1 */: b" `. d7 S% ~1 N9 B: w. \
- BSP_LED_Off(LED1);( M) Q/ V4 F: F; w, F7 p6 a8 ^
- compareErrorDetected = 0; 8 @& S I) P. X/ e
- BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation failed! ");/ w/ I7 g1 J f$ Q: k# p
- }
7 Y/ `$ V! j- | J# u - else
+ L! y0 n2 J1 g - {$ q, J# E" Y& B! X! o( o
- /* Turn LED1 on */$ n3 N2 T( L: s4 D* L+ ^
- BSP_LED_On(LED1);
% Q% l$ i4 `( V: I2 V - BSP_LCD_DisplayStringAtLine(10, (uint8_t *)" Data comparation success! ");
7 l, N- O( B9 l" I+ |! }0 J - }
; h7 u6 v8 T6 b; }/ @9 G
/ D9 O- l: H/ Y& ~ r- while (1)7 S3 [1 ?& w# d# }, b
- {# Z+ e8 }5 |1 X
3 g/ L! s. a3 P8 u, Y0 K! f- }2 o& |% A( }: H9 w( d; S' }
- }
5 d# g4 J E' [0 l - % q9 A9 M; m6 L) c Z- H7 F
- static void MPU_Config(void)% p! E M9 ]# {
- {
9 ^! x9 J6 o/ h5 I - /* Disable MPU */& {6 H! Q4 f. ]$ F' a6 ~" U
- MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
. M! Q7 N% R: I: X$ x
3 V: J P |9 @; c- /* Configure RAM region as Region N°0, 256kB of size and R/W region */
7 i3 [: W! R8 _ \- x( j4 p - MPU->RNR = SRAM1_REGION_NUMBER;
! R8 A% Y1 I) [8 ?; v - MPU->RBAR = SRAM1_ADDRESS_START;7 T2 C# ~9 v' C3 o# q/ F" `
- ' M1 g( s# [% W! c/ O; p" f
- /* Write-Back policy */, X" E* B0 j/ b9 G, h7 s' T
- MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | MPU_RASR_B_Msk | SRAM1_ACCESS_PERMISSION | 1<<MPU_RASR_TEX_Pos;+ v$ b3 K/ V; i
2 |; n Y0 t& S5 T1 a1 m- /* Enable MPU */3 z3 D( }/ ~- k, y& s
- MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;* h0 a& k' h7 H6 m
- }- P: O+ \( g* t2 P) o
复制代码 : Q8 |( q- Y V
为了确保 aSRC_Const_Buffer 在 Flash,aDST_Buffer 在 DTCM,我们可以在编译完之后查看 *.map 文件,如下:2 _9 e. d& W. t
/ l- `6 k8 T9 Y5 B* B. L. m2 e1 F, F, p; o
' C6 M+ |* T p# |( k
图3.4 检查常量和变量分配情况
# H Y: k( y, u9 s' P- U, d6 ?
4 G4 N& w# w1 @9 k2 {$ a( b! X下载到 STM32F769I-DISCO 板子上,显然,由于此时开启了 D-Cache,会出现数据不一致的现象,执行结果如下所示:' u+ g. |3 M2 k8 m* t5 {* w: g
' f4 o( _& I: Y9 `
" r- Z" A; P1 U. |+ N' L$ \# {
k1 s: I2 r* i$ H& _9 q
图3.5 Cache 数据不一致
* e- Z+ E2 @3 i( U0 q8 S7 z& T* S. R6 ^9 h' a9 Q5 |1 a
3.3.3 解决方案) ~- T/ V) h" S. z' \/ ~$ q/ c
(1)不启动 D-Cache9 b$ K* I5 h" m) Y& U0 ^9 p
) B4 Q' F3 k: A8 v5 Z/ u注释掉 SCB_EnableDCache();6 Q8 r# c# F! w2 ~2 A9 }. G
6 l( U* z* W' t4 \( R不启动 D-Cache,当然也就没有了 Cache 数据不一致的问题啦~
) K$ W! S% _) @5 e6 A i- \1 C2 K7 c
(2)将 SRAM1 相应区域设置为 shareable' G, m5 V5 Q q
4 i4 `7 x; _: s通过 MPU 将 SRAM1 相应区域设置为 shareable,MPU_Config() 函数处理如下:, I5 d6 J" K( n& L8 H& @
- static void MPU_Config(void)- F; f' |" p+ L& @
- {
- x- l- i. B0 ]8 I9 S- R - /* Disable MPU */
8 e' a C7 V0 j& u( U. o/ O - MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
8 c8 j4 k& Z, r' e1 [
7 \) p% @' x3 Z+ R, L5 a- /* Configure RAM region as Region N°0, 256kB of size and R/W region */
# |, n: F2 U: X1 F9 F! S: U5 f - MPU->RNR = SRAM1_REGION_NUMBER;( C" s1 x) v3 Y2 W p
- MPU->RBAR = SRAM1_ADDRESS_START;8 |% T2 h1 E9 ~# k0 R+ O' u9 T
- 2 n* ?$ _( L) J, N& L/ L
- /* Shareable */
5 |& Z% Q, I, S8 U4 q6 w I: [ - MPU->RASR = SRAM1_SIZE | MPU_RASR_S_Msk | SRAM1_ACCESS_PERMISSION;; R* L8 |9 \0 Z& ?: }
- , x2 h$ [- s. o2 ^) {
- /* Enable MPU */6 e e/ B& X1 u6 p. f) ?9 b
- MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;( `4 K+ q' @' K
- }
复制代码
9 z n+ h% c6 `2 u) |! W% C: `(3)DMA 访问 SRAM1 前先 Clean cache
1 Y( |9 W3 @4 Y- a& n. Q( c# O+ C/ ]4 }$ b
在启动 DMA 访问之前,程序员需要在合适的地方将 D-Cache 数据回写到主内存中,也就是 Clean 的操作。
+ A) q/ W+ p+ j3 Y9 I+ A( b& @& S" @; T- r) |7 u; Z" P
在本示例中,可以在 DMA_Config(); 前调用:: n& Z ~ h, l M
或者
/ x, u {( E4 S% Z4 z- SCB_CleanDCache_by_Addr((uint32_t*)SRAM1_ADDRESS_START, sizeof(aSRC_Const_Buffer));
复制代码 : Z! m7 Z6 Y7 r6 I. L
(4)将 SRAM1 相应区域设置为 Write-through 策略5 X) N! c" C+ u: {4 o; W
, N% [$ B) T( ^5 X A
通过 MPU 将 SRAM1 相应区域设置为透写模式(Write-through),MPU_Config() 函数处理如下:
' S: [, q! x9 w. k9 a. T1 |+ \+ E0 B/ l2 H" @
- static void MPU_Config(void)# s+ @4 K9 u! K
- {" P7 H4 i4 x2 Q% x
- /* Disable MPU */
& ~7 P( m$ Y' W- J - MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
: e" ]( v) G3 b
/ z6 t. U i# U" F7 i- /* Configure RAM region as Region N°0, 256kB of size and R/W region */
8 @# B3 i( G1 D% k9 X5 |5 i X" } - MPU->RNR = SRAM1_REGION_NUMBER;# P; e% A" Z; L: Q, G' O. L+ t
- MPU->RBAR = SRAM1_ADDRESS_START;( |+ X( `6 I$ O4 _' }0 d4 J& i
- * p/ |8 B0 m3 t
- /*Write Through policy*/3 Z& X1 E- K5 p' r: ^4 F
- MPU->RASR = SRAM1_SIZE | MPU_RASR_C_Msk | SRAM1_ACCESS_PERMISSION;- [4 Z' |! y. f& \3 O3 n) h0 b
- * b! G' }$ F0 i6 \
- /* Enable MPU */
1 { x! M. g9 k* K: ] - MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;6 _) B8 C7 @0 X# _, `
- }
复制代码 / K8 Y" v: h; |5 u: ^6 g
(5)将所有 cacheable 的空间全部强制 Write-though5 P' f- x; |+ {
V: q" B$ N& F- z/ w" }通过 cache 控制寄存器,将所有 cacheable 的空间全部强制 Write-though 模式。
$ \( f& E8 Z2 f* p* C/ Z# c
' G, q2 g( V7 R9 B! q4 z; r
0 e0 E* e( p3 \& t
2 s8 f' i5 p `7 X+ J8 f图3.6 CACR 寄存器(来自 PM0253) 2 p: j7 ]) [2 {/ q5 V6 a; ^4 Z
, M9 v! B. q Z [+ e在初始化的时候进行设置:5 P8 R( y1 X7 l8 e( k' G
+ b' ~8 a6 e8 a& S8 A' @5 ]0 _1 b
宏定义为:# S I7 h M' G) ]
- #define __FORCE_WRITE_THROUGH() *(__IO uint32_t *)0xE000EF9C = 1UL<<2
复制代码
; |- p+ W$ f9 i- A, |' R6 B以上这是都是较为常用的方法,在实际的开发过程中,为了提高性能,一般都会开启 cache,同时将其配置为 WB 策略,这就需要开发者在使用时特别小心!2 [5 n: t& e. |) ^' e! `5 b' q0 H& O
7 W: t3 o( N0 s9 a6 u" q
值得一提的是:对于第二种情况(图3.2),就不是 clean 操作了,而是 invalidate。需要先调用 SCB_InvalidateDCache() 或 SCB_InvalidateDCache_by_Addr() 去 invalidate 相应的 cache-line, 这样当 CPU 在读取时,会忽略 D-cache 中的内容,去真实的物理地址读取对应的数据。4 V6 I8 n& K% ~- y' M* D" F
6 A* \' M* V% N. G6 O0 _7 U+ u* M8 N* L( z' n& Y
, |) J! x0 i/ p" F# t8 k
图3.7 Cache 数据一致
+ H0 f9 k* d' [; c7 y- A6 a
8 W- g) |9 ^5 o. p Z好啦,通过上述几种方法,就可以解决 cache 数据一致性问题。当然,除了我这里提供的,还有其他方案,各种方案各有利弊,要根据实际应用场景去衡量,这就是嵌入式程序员展示才华的时候啦~" ?7 i' L7 _' F! U( B
5 |* y2 V7 z6 j! k7 _( n4 m
" t* X; m' _# W3 N; W
* V/ T. G0 \$ B% ~! l |