STM32F7是第一款带有内部高速缓存的STM32微控制器,所以接下来我们会介绍一些和高速缓存和Cache相关的基本概念,会介绍一些如果是开发者在使用STM32F7的时候需要注意的事情。
STM32F7的内核中带有一级高速缓存,实现了4K的数据缓存和4K的指令缓存。
下面介绍一下什么是高速缓存和与高速缓存相关的一些基本概念。
高速缓存就是高速存储器块,包括地址信息和相关联的数据,它的目的主要是为了提高对存储器的平均访问速度。
执行代码的时候CPU每次都要去访问FLASH,而我们知道FLASH的读取速度是远远低于CPU的主频的,所以需要设置一个等待周期来保证能够正确地从FLASH中把数据读出来。有了Cache之后,第一次访问FLASH读取出需要的指令和数据之后,可以把指令和数据先放到Cache里,当下次再需要这部分内容的时候就不需要再去访问FLASH,而是直接从Cache中把这部分内容读出来,这样就可以提高存储器的平均访问速度和程序的执行速度。
高速缓存的这种应用是基于下面两个程序的局部性:
一个是空间局部性,如果一个存储器的位置被访问,那么将来它附近的位置也会被访问,比如顺序执行代码或者是使用一个数据结构。
另一种是时间局部性,被访问过一次的存储器位置,接下来会被多次引用。比如代码中的循环。
下面介绍一下高速缓存中的一些术语。
缓存行是指逻辑上的一组存储器位置,是内存交换数据的最小粒度。
缓存命中是指要访问的数据或者指令已经在缓存中。
缓存缺失是指要访问的数据或者指令不在缓存中。
处理器需要访问某个可缓存的寄存器位置时,会先检查缓存内是否已经存在该位置的内容。如果缓存命中,就直接从缓存读出;如果缓存缺失,就从存储器中读出,同时放入缓存。
缓存分配是指当出现缓存缺失时,需要在缓存中发现一个位置,并且把新的缓存数据存到这个位置。在缓存分配时有两种策略,一种是读分配,就是说在进行读操作发生缓存缺失的时候,进行缓存分配,所有可缓存的存储器都是读分配;另一种是写分配,是在进行写操作发生缓存缺失的时候进行缓存分配。
在应用高速缓存的时候会带来一致性问题,一个原因是程序员不能控制对存储器的访问时机,不知道什么时候会发生CPU访问存储器,可能在读数据写数据的时候都是从缓存中读从缓存中取和把数据写到缓存中,根本就没有真正地访问存储器,可能在某一个时刻缓存满了,但又需要存新的缓存内容,这个时候可能会把数据重新写到FLASH中,这个时机是程序员不能控制的。另一个问题是同一个数据被保存在多个物理位置,被保存在缓存里,又被保存在FLASH中,这两个物理位置中的数据并不都是一致的,所以这个时候就会出现一致性问题。
驱逐是指从缓存中移除一个缓存行,为新的数据腾位置的过程。它发生在一个标位“dirty”的缓存行被新的缓存行替代的时候,标位“dirty”是说现在数据还只是存在缓存里,需要更新到存储器中。
回写是指在对数据进行操作时,只更新缓存,然后将缓存行标记为“dirty”,当这个缓存行需要被替换的时候,再将数据写到存储器中。
透写是指在对数据进行操作的时候同时更新缓存和二级存储,这时候缓存行不被标记为“dirty”。
缓存策略
第一种:透写,就是说数据直接同时写到缓存和下一级存储器中。
第二种:回写,是说数据只写到缓存。
Cortex-M7高速缓存
Cortex-M7的高速缓存采用了哈佛结构,指令缓存和数据缓存都是分开的,而且是可选的,只在AXIM接口有缓存。对于M7的系统架构来说,增加了Cache之后它增加了Cache的维护操作,增加了新的相关寄存器,最重要的是使用Cache会对系统软件有影响。
Cortex-M7高速缓存全面支持下列的缓存属性:
透写,不支持写分配;
回写,不支持写分配;
回写,支持写分配。
回写的方式有利于优化性能,因为减少了对FLASH的访问次数,但是会带来一致性的问题,Cortex-M7没有对一致性的硬件支持,所以需要从软件的层面去保证数据的一致性。
对于一致性有两种可选方案
一种是把所有的共享存储器都定义为共享属性,定义为共享属性之后这些区域将默认不被缓存到D-Cache,由于所有的操作都是直接针对二级存储器进行的,性能就会降低。但是因为这种情况下缓存对于这些区域是透明的,所以写软件会更容易。
另一种是通过软件对cache进行维护,包括两个方面,一方面M7的写操作必须是全局可见的,另一方面其他主设备的写操作要对M7可见。
存储器的属性可以由MPU来进行设定,包含共享属性、分配策略和存储器类型。
下图是存储器默认的映射和属性
最后说一下初始化和使能以及缓存的时候需要注意的问题
上电复位时,在使能之前,cache必须全部被作废,作废就是告诉CPU现在缓存中的数据已经没有用了,如果想对这些数据进行操作的话必须重新从二级存储中把它读出来。如果不这样做,可能会引发程序不可预测的行为。如果是通过软复位并且确定复位前RAM中的值都是可靠的,可以不用做这一步。
为了保证数据的一致性,必须在除能D-cache之前对其进行清理,这个只在使用回写策略时需要,如果不这么做就可能会丢失数据。