banner
yono

yono

哈喽~欢迎光临
follow
github

arm cache 属性的影响

前言#

最近希望将 ADC 性能跑到尽可能的极限,所以研究了一下相关的外设功能。

其中 cache 和 MPU 属性适配的部分我以前一直没搞太懂,这次总算弄得比较清楚了。在具有 cache 功能的 arm 芯片中,一定要配合 MPU 外设功能配置才可以使用 cache 功能。

  • cache:缓冲区主要是为了处理器内核可以更连续地操作内存,毕竟一些内存操作涉及到跨总线,cache 可以在读取时对命中的内存附近进行预取,以及写入时一定程度的延迟凑连续,最主要的是可以减少内部总线的冲突以加速数据的处理。
  • MPU(Memory Protection Unit):内存保护单元是 arm 芯片的内存保护机制,主要用于配置某段内存区的 cache 属性。
    当然可以有其他用途但是恕我直言没有其他用途,例如在不同的任务线程加载中,对内存区的配置可以精细化不同,实现类似 MMU 的功能但是正常人不会做这么复杂的应用的。
  • DMA(Direct Memory Access):直接寄存器访问,可以以没有处理器算力参与的形式,将外设寄存器中的数值拷贝到内存中,在拷贝一定数量 (例如 1024 个) 后产生中断通知,供我们对这段数据进行处理。

MPU 与 MMU 的主要区别

MPUMMU
缓冲命中周期固定 1 指令周期1~20 周期不等
管理精细度有限的区域 (十数个) 管理可以 RAM 逐页精细化
多任务支持划分物理区域进行有限隔离虚拟地址进程完全隔离
说白了做实时性应用的搞 linux 的

其他很多芯片当然都有类似的 cache 和 MPU 功能,但是这里主要讨论的 arm 芯片,他们的功能都是类似的,只是管理粒度、划区数量、或者不可管理固定属性,其中的属性和对应功能都是类似的。

总线的理解#

MCU 芯片的外设分布在不同总线上,而同总线的外设和内存间读写相对会快。我们仅就 ADC 、DMA、以及内存的配合,理解总线。

在我的这个应用中,因为 DMA 只能操作同总线上的外设和内存,所以使用到了 ADC1/2/3、DMA1/2、BDMA1。

结合以下的总线示意图理解。ADC1 结合 DMA1 采样同时占用部分 D2 域内存、ADC3 结合 BDMA 采样同时占用部分 D3 域内存,并最终将数据初期处理后将采样值放在 D1 域内存供 CPU 后续计算。这样分配尽可能减少跨域的性能损耗。

用到的总线

cache 属性的配置#

MPU 配置控制 cache 属性,有最重要的 3 个配置项如下,我有一些自己的简单总结

  • IsCacheable 决定是否开启 cache。
  • IsBufferable 决定是否将写入指令也使用 cache 缓冲后统一写入内存。如果不开启 IsCache 那么没有意义
  • IsShareable 决定是否采用严格的时序控制,这在外设的外设,例如 FMC 访问外部内存时非常重要,因为资源跨芯片了,需要开启严格时序。

以及对应的 chat GPT 解释,比我说的清楚详细。

  • IsCacheable 控制是否使用 D-Cache,以及在 M7 上配合 TEX/C/B 决定缓存策略。
  • IsBufferable 允许写缓冲(write buffering/combining),可能延后对外可见时间,以换取更高吞吐。
  • IsShareable 标记该区 “可被多个主机共享”,在 M7 上还会改变缓存策略:Cacheable+Shareable 会强制为 Write-Through/No-Write-Allocate。

详细说明

  • IsCacheable
    • 关心点:是否走 D-Cache;配合 TEX/C/B 决定是 Write-Back/Write-Allocate 还是 Write-Through/No-Allocate。
    • 影响:CPU 访问延迟与带宽、总线流量;不直接改变外设采样速率,但影响 CPU 处理速度。
  • IsBufferable
    • 含义:允许将写入先进入写缓冲并可能合并,再异步刷到内存 / 总线,减少总线事务、提高吞吐。
    • 影响:
      • 性能:连续写(memcpy、流式写)更快。
      • 可见性 / 顺序:写入对其他主机(DMA / 外设)的 “可见时刻” 可能延后;必要时用屏障指令保证顺序。
    • 典型用法:Normal 内存常设为 BUFFERABLE;Device/MMIO 通常不设以避免重排 / 合并。
  • IsShareable
    • 含义:该区域可能被多个主机共享(CPU、DMA 等)。在 Cortex-M7 上对 Cacheable 的 Normal 区有特别影响:
      • Cacheable + Shareable = 写直达(Write-Through)、不写分配(No-Write-Allocate),减少其他主机看到旧数据的窗口,但 CPU 侧性能较 WB/WA 略降。
      • Cacheable + Non-shareable = 通常为 Write-Back/Write-Allocate(配合 TEX=1,C=1,B=1),CPU 性能最佳。
    • 影响:
      • 顺序与可见性语义更保守,有利于与其他主机协作。
      • 不等于 “硬件缓存一致性”,但改变了缓存写策略与内存屏障语义范围。

常见组合速览(Normal 内存)

  • B=1, C=1, Shareable=0, TEX=1 → Write-Back, Write-Allocate(CPU 最快,适合大多数计算缓冲)
  • B=1, C=1, Shareable=1, TEX=1 → Write-Through, No-Write-Allocate(更保守的共享语义)
  • B=0/1, C=0 → Non-cacheable(可选择是否允许写缓冲;B=0 更严格时序)

见此前的总线示意,由于 cache 实际属于 CPU 的一部分,所以 DMA 获得的内存数据 (D2/D3 域内存区) 是必然跨域的。在跨域的应用中,必须解决内存一致性问题,这需要一个内存区大小的 while 循环来强制命中解决一致性问题,反而引起不必要的消耗。

所以在 MPU 配置中,D2、D3 域对应的地址范围不开启 Cache,对应上述的第三个组合。D1 域的常用内存区则可以开启 IsCache、开启 IsBuffer、关闭 IsShare 以获得最大的性能,对应上述的第一个组合。

而上述的第二个组合,我暂时没有遇到过使用场景,也想象不出来。

以下是上述配置的匹配代码,可供参考以及我自己回档。

/**
  * @brief MPU配置
  * @param None
  * @retval None
*/
static void MPUInit(void)
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable( );

    /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate
        最佳性能,用于CPU处理计算 */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /* 配置D2域MPU 
        D2域外设的DMA使用 禁止cache */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x30000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_256KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_DISABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /* 配置以太网收发描述符部分为Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x30040000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_32KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.SubRegionDisable = 0x0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_DISABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /* 配置D3域MPU 
        D3域外设的DMA使用 禁止cache */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER3;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_DISABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /* 配置FMC 片选3 的支持*/
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x68000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_256B;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE; // 此外设需配置为无cache,否则会重复片选和读写使能
    MPU_InitStruct.IsShareable      = MPU_ACCESS_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER4;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

此文由 Mix Space 同步更新至 xLog
原始链接为 https://www.yono233.cn/posts/novel/25_10_13_arm-cache_mpu


加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。