ARM 多核处理器架构
本文从 Cache 一致性协议底层原理出发,剖析 ARMv7-A 架构下基于 LDREX/STREX 的独占访存与同步原语。
1. 单核性能瓶颈:功耗墙与内存墙
单核处理器主要依靠提升主频和挖掘指令级并行(Instruction-Level Parallelism,ILP,如超标量、乱序执行和更深的流水线)来提高性能。然而,这条路线最终会受到两大物理瓶颈的限制:
- 功耗墙(Power Wall):CMOS 动态功耗近似满足 P≈CV²f。随着主频不断提升,为保证时序收敛,通常需要提高供电电压,因此功耗增长速度往往远快于频率增长速度。
- 内存墙(Memory Wall):CPU 的计算能力提升速度远快于主存访问延迟的改善速度,处理器越来越多的时间消耗在等待数据返回,而非真正执行指令。
面对这两大瓶颈,ARM 在 Cortex-A 系列中的解决思路,是在一个 Cluster 中集成多个处理核心(早期 Cortex-A 通常为 2~4 个核心),利用线程级并行(Thread-Level Parallelism,TLP)提升整体吞吐量,而不是继续单纯依赖提高单核性能。
多核处理器带来的系统性优势如下:
| 维度 | 效果 |
|---|---|
| 并行性能 | 多个任务能够真正同时执行,而非依赖单核时间片轮转实现的”假并行”。 |
| 能效比 | 以较低的工作频率和供电电压即可获得更高的总体吞吐量,因此整体能效通常优于单核高频运行。 |
| 可扩展性 | 同一 SoC 架构可以根据产品定位配置不同核心数量,而无需重新设计整个系统。 |
| 响应性 | 中断和后台任务可分布到不同核心执行,降低响应延迟,提升系统实时性和用户体验。 |
| 内存系统利用率 | 多个核心能够同时发起内存访问请求,现代内存控制器可对这些请求进行调度和重排序,提高整体内存吞吐率。 |
当然,多核并非只有收益,也带来了新的挑战。当多个核心频繁访问和修改共享数据时,需要维护 Cache 一致性(Cache Coherency),这会增加总线通信和同步开销;如果核心数量增加而内存带宽没有同步提升,各核心还可能因竞争内存资源而导致整体性能下降。共享 L2 Cache 能够减少多个核心访问共享数据(如文件缓存、共享库和内核代码)时对主存的访问次数,从而在一定程度上缓解这些问题。
ARM 官方文档还特别强调了一个容易被忽视的事实:
Migrating multi-threaded software from a single core system to a multi-core one can trigger problems with incorrect programs that could not be exposed by running the same program time-sliced on a single core. It can also cause very infrequent bugs to become very frequently triggered. What it cannot do is to cause correctly written multi-threaded programs to misbehave — only expose previously unnoticed errors.
换句话说,多核并不会制造新的并发 Bug,而更像一面放大镜:那些由于单核时间片调度而难以暴露的竞态条件、同步错误和内存可见性问题,在真正并行执行时更容易被触发。
既然多核系统既带来了显著的性能收益,也引入了全新的并发挑战,那么接下来,我们首先需要了解 ARM 多核处理器的基本硬件组成,以及多个核心之间是如何协同工作的。
1.1 ARM 多核处理器硬件基础
ARM 于 2004 年推出 ARM11 MPCore,率先将多核处理器引入嵌入式 SoC 市场。随后,多核逐渐成为 Cortex-A 系列的主流设计(Cortex-A8 为单核架构,其余早期 Cortex-A 处理器均提供 MPCore 多核版本)。
一个典型的 Cortex-A MPCore 处理器通常具有以下硬件特性:
- 1~4 个处理核心(设计时可配置,具体取决于处理器型号)
- 私有 L1 Cache,簇(Cluster)内各核心的数据 Cache 由 SCU(Snoop Control Unit) 自动维护一致性
- 集成 GIC(Generic Interrupt Controller),支持中断在多个核心之间分发和路由
- 每个核心拥有独立的私有定时器(Private Timer)和看门狗(Watchdog)
- 可选 ACP(Accelerator Coherency Port)接口(部分处理器如 Cortex-A5、A9、A12、A15 支持),允许外部硬件加速器直接参与 Cache 一致性
- 每个核心可独立加入或退出一致性域(Snoop Domain),以适应不同的功耗和性能需求
相比 ARM11 MPCore,后续 Cortex-A 多核处理器的另一项重要改进是 Cache 和 TLB 维护操作的硬件加速。对于失效(Invalidate)、清理(Clean)等维护指令,硬件能够在一致性域内自动广播到其他核心,而无需软件逐个核心执行,从而降低多核系统的软件开销。
除了同构多核(Homogeneous Multi-Core)之外,ARM 处理器长期以来也广泛应用于异构系统。例如 TI OMAP、Freescale i.MX 等 SoC 往往将 ARM 处理器与 DSP、GPU 或其他专用处理器集成在一起,不同处理器既可以拥有各自独立的存储空间,也可以共享部分内存,并运行不同的软件栈。
了解了 Cortex-A 多核处理器的硬件组成之后,下面将介绍三种常见的软件运行模型:SMP(Symmetric Multi-Processing)、AMP(Asymmetric Multi-Processing) 和 HMP(Heterogeneous Multi-Processing)。
2. 多处理软件架构模型
ARM 多核系统支持三种不同的软件架构模型,每种适用于不同的场景。
2.1 对称多处理(SMP)
SMP(Symmetric Multi-Processing,对称多处理)的核心理念是:所有处理核心拥有一致的内存视图,并运行同一个操作系统实例。每个核心地位完全平等,都可以执行内核代码、处理中断以及运行用户任务。操作系统调度器可以根据系统负载,动态地将任务分配或迁移到任意核心,实现资源利用率和性能的最优化。
SMP 的主要特点包括:
- 真正的并行执行:多个任务能够在不同核心上同时运行,而非依赖单核时间片轮转实现的”假并行”。
- 调度器负责负载均衡:调度器可根据系统负载、Cache 拓扑、CPU 利用率以及能耗策略,在多个核心之间动态迁移任务。例如,可以将任务集中到少数核心以便其他核心进入低功耗状态,也可以将任务分散到多个核心以提高整体吞吐量。
- 中断可在多个核心之间分配:GIC 支持将中断路由到指定核心或多个核心。Linux 内核提供了 IRQ Affinity 机制,用户或
irqbalance等工具可以根据系统拓扑和负载动态调整中断与 CPU 的绑定关系,以减少热点核心并提高整体性能。 - 共享内存一致性:硬件负责维护 Cache 一致性,确保所有核心观察到一致的内存视图,软件无需主动同步 Cache(但仍需使用锁、原子操作和内存屏障保证并发正确性)。
调度器在进行负载均衡时通常会综合考虑多方面因素。例如,为了降低功耗,可以将任务集中到少数核心,使其余核心进入空闲状态;为了提高性能,可以将计算密集型任务分散到不同核心,减少资源竞争;在支持 DVFS(Dynamic Voltage and Frequency Scaling)的平台上,还可以通过”更多核心、较低频率”或”较少核心、较高频率”等不同组合,在性能与能耗之间取得平衡。此外,调度器还会结合任务的 CPU 使用率、唤醒频率、Cache 局部性等信息,尽量减少不必要的任务迁移,以避免 Cache 命中率下降带来的性能损失。
以一个典型的四核 Cortex-A9 MPCore SoC(如 TI OMAP4 系列)为例,四个核心共享同一 L2 Cache,并通过一致性互连访问共享内存和外设。Linux 启动后会识别所有 CPU,在 /proc/cpuinfo 中可以看到 processor: 0 至 processor: 3。运行过程中,调度器可能将 ksoftirqd、音频解码线程、图形渲染线程等分别安排到不同核心执行;当某个核心负载过高时,也可能将部分任务迁移到其他核心。整个过程完全由操作系统负责,对用户态应用透明,绝大多数多线程程序无需重新编译即可运行在 SMP 系统上。
2.2 非对称多处理(AMP)
AMP 是功能分发架构:静态地为每个核心分配独立角色,通常每个核心运行各自的操作系统。整个系统看起来像是多个独立的单核处理器。
与 SMP 的关键区别:
| 特性 | SMP | AMP |
|---|---|---|
| 角色分配 | 动态,由调度器决定 | 静态,设计时确定 |
| 内存视图 | 所有核心相同 | 各核心可有不同视图 |
| 负载转移 | 支持 | 不支持 |
| 硬件 Cache 一致 | 必需 | 不需要 |
| 核间通信 | 共享内存(硬件一致) | 共享内存 + 门铃中断(MCAPI) |
AMP 的典型应用场景包括:
- 安全隔离:将敏感任务隔离到特定核心
- 实时保证:一个核心处理硬实时需求,另一个处理高性能应用
- 网络系统:控制面(AMP)+ 数据面(SMP)混合架构
- Multi-OS 系统:不同核心运行不同的操作系统(如 Linux + RTOS),这在 AMP 设计中是常见用法
实际上,许多系统是 SMP + AMP 混合体:两个或更多核心运行 SMP OS,同时系统中还有不属于该 SMP 子系统的其他元素。Cache 一致性在 SMP 核心间实现,但不一定延伸到 AMP 部分。
2.3 异构多处理(HMP)与 big.LITTLE
与 SMP 中所有核心性能相同不同,HMP(Heterogeneous Multi-Processing,异构多处理)允许系统中存在性能和能效不同的处理核心。
ARM 对 HMP 的定义是:多个处理核心具有相同的指令集架构(ISA),但内部微架构不同,因此具有不同的性能和功耗特性。同时,这些核心处于同一个 Cache 一致性域内,可以共享内存数据。
big.LITTLE 是 ARM 实现 HMP 的典型硬件架构。
它将两类核心组合在同一个 SoC 中:
| 类型 | 特点 | 示例 |
|---|---|---|
| big 核心 | 高性能、高功耗,适合计算密集型任务 | Cortex-A15、Cortex-A57 |
| LITTLE 核心 | 高能效、低功耗,适合轻负载任务 | Cortex-A7、Cortex-A53 |
例如:
1 | big Cluster: |
由于 big 和 LITTLE 核心实现相同的 ARM ISA,应用程序无需修改即可运行在任意核心上。操作系统调度器会根据任务负载,在不同类型核心之间迁移任务:
- 计算密集型任务倾向运行在 big 核心,以获得更高性能;
- 后台任务和轻负载任务倾向运行在 LITTLE 核心,以降低功耗。
早期 ARM big.LITTLE 系统采用 HMP Scheduler 实现异构调度;现代 Linux/Android 更多采用 EAS(Energy Aware Scheduling),综合考虑 CPU 性能容量、DVFS 状态、能耗模型和温度限制,在性能与功耗之间进行优化。
无论采用 SMP、AMP 还是 HMP,只要多个核心共享同一块物理内存,就必须解决数据一致性问题:当一个核心修改数据后,其他核心如何看到最新值。这正是 Cache 一致性(Cache Coherency)机制需要解决的问题。
简单来说:
- SMP 是多个相同能力的核心并行工作,HMP 是多个性能和功耗不同的核心协同工作,需要根据任务特点选择合适的核心。
- SMP 和 HMP 通常都是一个操作系统实例管理所有核心;AMP 通常是多个核心分别运行独立的软件系统。
- big.LITTLE 是硬件设计,HMP 是管理这些不同核心的软件模型。
3. Cache 一致性
SMP 系统中的一个核心硬件挑战是 Cache 一致性(Cache Coherency)。
由于每个处理核心通常拥有自己的 L1 Cache,当多个核心访问同一块共享内存时,可能出现数据副本不一致的问题。
例如:
1 | Core0: |
如果 Core1 使用自己的 Cache 副本,而没有及时获得 Core0 修改后的数据,就会导致程序错误。
因此,多核系统必须提供机制,使多个核心对于共享数据保持一致的视图。
需要注意的是:
- Cache 一致性主要解决 CPU 核心之间的数据同步问题;
- DMA 等外部设备的数据一致性属于 I/O Coherency 范畴,需要额外硬件支持。
3.1 三种一致性方案
| 方案 | 机制 | 优缺点 |
|---|---|---|
| 禁用 Cache | 将共享数据区域设置为 Non-cacheable | 实现简单,但访问速度慢,内存带宽压力大 |
| 软件维护一致性 | OS 或驱动主动执行 Cache Clean / Invalidate 操作 | 灵活,但增加软件复杂度,占用 CPU 时间和总线带宽 |
| 硬件维护一致性 | 通过 SCU、CCI、CHI 等互连以及 MESI/MOESI 等协议自动维护 | 硬件复杂度增加,但显著降低软件负担 |
软件维护 vs 硬件维护
以软件管理方式为例:
1 | Core0 写数据 |
每次共享数据交互都需要软件显式维护 Cache 状态。
如果共享数据访问频繁,例如:多核通信缓冲区、音视频处理流水线、网络数据包处理等场景,大量 Cache 维护操作会:消耗 CPU 周期、增加内存总线访问、降低系统吞吐量。
而硬件一致性机制:
1 | Core0 修改 Cache |
整个过程由硬件自动完成,软件只需要按照正常内存访问方式编程。
因此,ARM Cortex-A 多核处理器通常采用硬件 Cache 一致性方案:
- Cortex-A9:通过 SCU(Snoop Control Unit) 实现 Cluster 内一致性;
- Cortex-A15/A57 等:通过 ACE/CCI 等一致性互连 支持多 Cluster 一致性;
- 新一代 ARMv8/v9 系统:通过 CHI 等协议 实现更大规模的一致性扩展。
硬件一致性是现代 ARM 多核处理器能够高效运行 SMP 操作系统(如 Linux、Android)的重要基础。
3.2 MESI 与 MOESI 协议
ARM 多核处理器通常采用基于 MESI/MOESI 思想的 Cache 一致性协议,通过记录每个 Cache Line 的状态,判断数据是否有效、是否被修改以及是否允许写入。
需要注意的是,不同 Cortex-A 处理器和一致性互连的具体实现有所不同。例如,Cortex-A9 通过 SCU(Snoop Control Unit) 实现 Cluster 内的 L1 Cache 一致性,而后续 Cortex-A 系列通常通过 ACE、CCI、CHI 等一致性协议和互连机制扩展到更复杂的多核系统。因此,不能简单认为所有 Cortex-A 都固定采用某一种协议。
典型 MESI/MOESI 协议包含以下五种状态:
1 | M (Modified) -- “我改过,而且只有我有” |
简单来说:
| 状态 | 数据是否最新 | 是否可以直接写入 |
|---|---|---|
| M | 最新,但主存可能旧 | 可以 |
| O | 最新,允许共享 | 可以升级为 M |
| E | 最新,且唯一副本 | 可以 |
| S | 最新,但存在共享 | 需要升级 |
| I | 无效 | 不可以 |
其中:
- M/E/O 状态表示当前 Cache 具有写权限或者可以快速获得写权限;
- S 状态只能读取,写入前必须使其他 Cache 副本失效;
- I 状态表示当前 Cache 中没有有效数据。
3.2.1 MOESI 状态转换示例
假设四个核心:
1 | Core0、Core1、Core2、Core3 |
共同访问地址:
1 | 0x8000_1000 |
初始状态:
1 | 数据仅存在 DDR 中, |
Step 1:Core0 首次读取数据
Core0 执行:
1 | LDR [0x8000_1000] |
由于其他核心没有该数据:
1 | Core0: |
此时:
- Core0 拥有唯一副本;
- 数据与 DDR 保持一致。
Step 2:Core0 修改数据
Core0 执行:
1 | STR [0x8000_1000] |
由于 Core0 当前处于 E 状态,可以直接写入:
1 | Core0: |
此时:
- Core0 L1 Cache 中保存最新数据;
- DDR 中仍然是旧数据。
Step 3:Core1 读取该数据
Core1 执行:
1 | LDR [0x8000_1000] |
一致性硬件发现:
1 | Core0: |
说明 Core0 持有最新数据。
因此数据可以直接从 Core0 的 Cache 提供给 Core1:
1 | Core0: |
此时:
1 | Core0: |
整个过程无需访问外部 DDR。
这正是 MOESI 相比 MESI 的优化:
允许一个 Cache 保留脏数据,同时让其他 Cache 保存共享副本,避免为了共享数据而提前写回主存。
Step 4:Core1 修改数据
此时:
1 | Core1: |
Core1 不能直接写入,因为其他 Cache 可能仍保存该数据。
因此 Core1 必须发送升级请求:
1 | Upgrade |
一致性硬件使其他副本失效:
1 | Core0: |
随后 Core1 可以继续执行写操作。
3.2.2 Cache 间直接数据迁移
硬件一致性机制的一个重要优化是:
共享数据不一定需要先写回外部 DDR,再由其他核心读取,而可以直接在不同核心的 Cache 之间传递。
例如:
1 | Core0 L1 Cache |
根据 Cache Line 的状态不同,一致性硬件可以执行不同操作:
- 干净 Cache Line(Clean Line):
当数据与 DDR 中的数据保持一致时,可以直接将 Cache Line 复制到其他核心的 Cache 中,无需访问外部内存。 - 脏 Cache Line(Dirty Line):
当某个核心修改了数据但尚未写回 DDR 时,最新数据保存在该核心的 Cache 中。一致性硬件可以直接将该数据传递给其他核心,并更新 Cache 状态,无需提前写回 DDR。
因此,数据传递路径可以从:
1 | Cache |
优化为:
1 | Cache |
避免了额外的 DDR 访问,降低了访问延迟,同时减少了内存带宽消耗。
在 Cortex-A9 中,Cluster 内的 L1 Cache 一致性由 SCU(Snoop Control Unit)负责;在更复杂的多 Cluster 系统中,则通过 CCI、CMN、CHI 等一致性互连协议实现更大规模的 Cache 一致性管理。
需要注意:
Cache 一致性和 LDREX/STREX 独占访问机制是两个不同的概念。
Cache 一致性解决的是:
多个核心看到的数据是否一致。
LDREX/STREX 解决的是:
多个核心同时修改共享数据时,如何保证操作具有原子性。
二者通常共同用于实现多核同步,但属于不同层面的硬件机制。
3.3 Snoop Control Unit (SCU)
SCU(Snoop Control Unit)是 Cortex-A9 MPCore 中负责 Cluster 内 Cache 一致性管理的核心硬件模块。
它主要维护同一 Cluster 内各核心 L1 数据 Cache(D-Cache)之间的一致性。
需要注意:
- L1 数据 Cache 参与一致性管理;
- L1 指令 Cache 通常不参与一致性协议,因为代码区域通常只读;
- 如果程序运行过程中动态修改代码,需要软件主动执行 Cache 维护操作,保证 I-Cache 与内存中的代码一致。
SCU 启用一致性管理的四个条件缺一不可:
- SCU 使能——通过 Private Memory Region 中的 SCU 控制寄存器启用
- 核心参与 Inner Shareable 域——通过设置 CP15 ACTLR 寄存器的 SMP 位(bit[6]). 尽管名字叫 “SMP bit”,它实际上是标记核心是否参与一致性域
- MMU 已使能——ARM 的 Cache 属性(如 Shareable、Cacheable、Normal/Device 类型)由页表描述符定义,而 MMU 负责将这些属性应用到实际内存访问中;MMU 未开启时,无法正确建立共享缓存属性。
- 被访问的页面标记为 Normal Shareable,Cache 策略为 Write-Back Write-Allocate(Device 和 Strongly-ordered 内存不可缓存,Write-Through 缓存对核心而言行为等同于 uncached)
设置 SMP 位的汇编代码:
1 | MRC p15, 0, r0, c1, c0, 1 ; 读取 ACTLR |
SCU 的一个基本限制是:它只能维护同一个 Cortex-A cluster 内核心之间的 Cache 一致性。例如 Cortex-A9 四核簇内部可以通过 SCU 实现 L1 Cache 一致性,但跨 cluster 或其他总线主设备并不由 SCU 直接管理,需要依靠更高层的一致性互连(如 CCI)或硬件一致性接口(如 ACE/ACP)实现协同。
3.4 Accelerator Coherency Port (ACP)
ACP(Accelerator Coherency Port)是 Cortex-A5、Cortex-A9、Cortex-A12 和 Cortex-A15 的可选硬件特性。它本质上是一个 AXI Slave 接口,连接到 SCU,用于将外部 AXI Master(如 DMA、加速器)接入 CPU 的 Cache 一致性系统。
在 Cortex-A 多核系统中的位置如下:
1 | Cortex-A Cluster |
普通 DMA 访问内存时绕过 CPU Cache,需要软件通过 cache clean/invalidate 保证数据一致性:
- CPU 写入的数据可能仍停留在 L1 Cache 中,DMA 读取前需要 clean cache 将数据写回内存;
- DMA 写入的数据可能导致 CPU Cache 中保存的副本失效,CPU 使用前需要 invalidate cache。
ACP 通过硬件一致性机制消除了这些额外的软件维护操作:
- ACP 读操作:DMA 通过 ACP 访问内存时,SCU 会 snoop(窥探)各核心 L1 Cache。如果数据在某个核心 Cache 中,则可以直接获取最新数据,无需提前 clean。
- ACP 写操作:DMA 写入数据时,SCU 会使其他核心 Cache 中对应 Cache Line 失效,避免 CPU 后续读取旧数据,无需手动 invalidate。
因此,在支持 ACP 的系统中,CPU 与 DMA 之间可以通过硬件机制保持 Cache 一致性。
示例:
无 ACP(软件维护一致性)
1 | CPU: |
有 ACP(硬件维护一致性)
1 | CPU: |
需要注意:
- ACP 使用的是物理地址,因为 DMA 不经过 CPU MMU 地址转换;
- ACP 只能扩展 SCU 的一致性范围,仍然受 SCU 能力限制,主要用于单个 Cortex-A cluster 内 CPU 与外部 Master 的一致性;
- ACP 解决的是 Cache 数据一致性问题,但不能保证访问顺序。如果需要约束内存访问顺序,仍然需要使用
DMB/DSB等内存屏障。
3.5 Cache Coherent Interface (CCI-400)
SCU 只能维护单个 Cortex-A cluster 内部的 Cache 一致性。当系统包含多个 cluster 时(例如 big.LITTLE 架构中的 Cortex-A7 与 Cortex-A15),需要借助更高层的一致性互连实现跨 cluster 的 Cache 协同。
ARM 在 AMBA 4 中引入 ACE(AXI Coherency Extensions),在 AXI 基础上增加 Cache 一致性支持,使互连能够传递 snoop 请求和 Cache 状态信息。
- ACE:用于具有 Cache 的处理器 cluster,例如连接 Cortex-A7/A15 等 CPU 簇。
- ACE-Lite:ACE 的简化版本,用于无 Cache 的外部 Master(如 DMA、GPU、加速器),使其能够访问 CPU 共享数据而无需软件维护 Cache 一致性。
CoreLink CCI-400 是 ARM 第一代 Cache Coherent Interconnect,实现多个 ACE 主设备之间的一致性连接,典型结构如下:
跨 cluster 读取数据时,CCI-400 会将请求转发到目标 cluster,并触发 snoop 检查:
1 | Step 1: 请求 cluster 发起 Cache Read 请求 |
因此,CCI-400 并不保存数据副本,而是负责协调不同 cluster 的 Cache 状态。对于软件而言,该过程完全透明,CPU 执行普通 LDR/STR 指令即可,由硬件自动完成一致性维护。
需要注意:
- CCI-400 解决的是 Cache 数据一致性问题;
- 操作系统修改共享页表后,还需要通过 TLB Shootdown 机制同步各核心 TLB;
- Cache 一致性保证“数据一致”,TLB 同步保证“地址映射一致”。
4. TLB 和 Cache 维护操作的广播
前面介绍的 SCU、ACP 和 CCI 解决的是数据 Cache 在多个核心或外部 Master 之间的一致性问题。但多核系统中还存在另一类同步需求:当操作系统修改共享页表或主动维护 Cache 时,其他核心也必须同步执行对应的维护操作,否则可能继续使用旧的地址映射或旧的缓存数据。
ARM 提供了维护操作广播机制,可以将 TLB 和 Cache maintenance 操作广播到 Inner Shareable 域内的其他核心。
该功能依赖于以下控制位:
- SMP 位(ACTLR bit[6]):使核心加入 Inner Shareable 一致性域;
- FW 位(ACTLR bit):控制维护操作广播功能(Cortex-A9)。
支持广播的操作包括:
- 按虚拟地址 invalidate TLB entry;
- 按虚拟地址 clean / invalidate Data Cache Line;
- 按虚拟地址 invalidate Instruction Cache Line。
例如,Linux 修改共享页表后,需要通知其他 CPU:
1 | CPU0: |
这样可以避免不同 CPU 使用不同的地址映射。
4.1 L2 Cache 下的维护顺序问题
在带有 L2C-310 L2 Cache 的系统中,Cache maintenance 操作并不是 L1 和 L2 的单步原子操作,而是需要分别维护不同 Cache 层级。
因此,执行 clean 和 invalidate 时必须遵循正确顺序:
| 操作 | 顺序 |
|---|---|
| Clean Cache | L1 → L2 |
| Invalidate Cache | L2 → L1 |
原因如下:
Clean:先 L1,再 L2
L1 Cache 离 CPU 最近,可能保存最新的脏数据:
1 | CPU |
如果先 clean L2:
1 | L2 → DDR |
写回的数据可能仍然是旧值。
因此必须:
1 | Clean L1 |
Invalidate:先 L2,再 L1
Invalidate 的目标是删除旧数据副本。
如果先 invalidate L1:
1 | Invalidate L1 |
但 L2 仍保留旧数据:
1 | L2 Cache = 旧数据 |
后续访问可能重新从 L2 加载旧 Cache Line。
因此需要:
1 | Invalidate L2 |
保证所有缓存层级中的旧副本都被清除。
Cache/TLB 维护广播解决的是数据和地址映射的一致性问题:
- Cache maintenance:保证多个核心看到的数据一致;
- TLB maintenance:保证多个核心使用的虚拟地址映射一致。
但它们并不能解决多个核心同时修改共享变量的问题。
当多个核心需要安全访问临界区时,还需要依靠 ARM 的独占访问机制(LDREX/STREX)实现同步。
5. 多核同步原语:LDREX/STREX
5.1 从关中断到 Spinlock
在单核系统中,保护临界区通常可以通过禁用中断实现——只要当前核心不会被抢占,就不会发生并发访问。但在多核系统中,禁用一个核心的中断并不能阻止其他核心同时访问同一临界区,因此无法保证互斥。
多核系统通常使用 spinlock(自旋锁) 实现互斥,其底层依赖 CPU 提供的原子读-改-写(Read-Modify-Write)机制。ARM 采用 LDREX/STREX 这一组 LL/SC(Load-Linked / Store-Conditional)指令来实现原子操作。
ARM 架构为此提供了三条专用指令:
| 指令 | 功能 |
|---|---|
LDREX |
Load Exclusive —— 从内存读取数据,同时在当前核心的 Exclusive Monitor 中建立独占访问状态 |
STREX |
Store Exclusive —— 仅当独占状态仍然有效时完成写入,并返回 0(成功)或非 0(失败) |
CLREX |
Clear Exclusive —— 清除当前核心的 Exclusive Monitor 状态 |
LDREX/STREX 并非多核系统专用,它们提供的是 CPU 级别的原子读-改-写能力,因此在单核系统中同样广泛用于实现互斥锁、信号量和原子计数等同步原语。
此外,在支持独占访问的系统中,操作系统在上下文切换时必须执行 CLREX。否则,如果线程 A 在执行 LDREX 后被切换出去,而遗留的独占状态未被清除,线程 B 后续执行 STREX 时可能继承这一状态,从而导致未定义行为。因此,ARM 要求内核在上下文切换过程中清除所有残留的独占访问状态。
5.2 Shareable vs Non-Shareable 内存的行为差异
LDREX/STREX 的行为取决于目标内存的 Shareable 属性:
- Shareable 内存:
LDREX建立独占访问状态。如果在STREX之前,任何核心对该地址发生写入(无论是普通STR还是成功的STREX),独占状态都会失效,随后STREX返回失败。 - Non-Shareable 内存:独占访问仅在当前核心内部维护,不会监测其他核心的访问,因此只有本核心的写操作或其他使独占状态失效的事件会导致
STREX失败。
此外,每个核心同时只能维护一个独占访问状态。如果同一核心再次执行 LDREX(即使访问的是另一个地址),之前建立的独占状态将自动失效。
下面以一个典型的 spinlock 为例,说明 Shareable 内存上的 LDREX/STREX 如何实现互斥:
1 | Spinlock 地址:0xB000_0000 |
这个例子说明了三个关键点:
- 每个核心维护自己的独占访问状态。 一个核心再次执行
LDREX会覆盖自己之前建立的独占状态,但不会因为执行LDREX而清除其他核心的独占状态。 - Shareable 内存上的任何写操作(包括普通
STR和成功的STREX)都会使其他核心针对该地址建立的独占状态失效,因此后续STREX将返回失败。 - spinlock 正是利用这一特性实现互斥:只有当
LDREX与STREX之间没有其他核心修改锁变量时,STREX才能成功完成写入;否则立即失败并进入下一轮重试。
5.3 Local Monitor 与 Global Monitor
为了实现 LDREX/STREX 的独占访问,ARM 在硬件中引入了两级 Monitor,它们分别负责核心内部和多核之间的协调。
- Local Monitor:位于每个 CPU 核心内部,负责维护当前核心的独占访问状态。执行
LDREX时建立独占状态,执行STREX时检查该状态是否仍然有效;无论成功还是失败,STREX都会清除这次独占状态。ARM 并未规定 Local Monitor 必须记录完整的地址信息,因此其内部实现可能因处理器而异。 - Global Monitor:用于监测 Shareable 内存上的独占访问。当多个核心共享同一块内存时,仅靠 Local Monitor 已无法判断共享数据是否被其他核心修改,因此需要 Global Monitor 参与协调。如果其他核心对同一 Shareable 地址发生写入,Global Monitor 会使相关核心的独占状态失效,从而导致后续
STREX返回失败。
两级 Monitor 的职责可以简单理解为:
- Local Monitor:负责”我是否还拥有独占访问资格”;
- Global Monitor:负责”其他核心是否破坏了这次独占访问”。
两者共同决定 STREX 是否能够成功完成写入。
需要注意的是,并非所有内存区域都支持独占访问。如果目标地址没有对应的 Monitor(例如某些外设寄存器,或 SoC 未实现独占访问支持的内存区域),那么 STREX 将始终返回 1(失败),软件也就无法利用 LDREX/STREX 在该区域实现原子操作。
6. 多核中断处理
6.1 GIC 架构与中断分类
ARM 多核处理器集成了遵循 GIC 架构规范的中断控制器,中断被分为三类:
| 类型 | 缩写 | 数量 | 说明 |
|---|---|---|---|
| Software Generated | SGI | 每核 16 个 (ID 0-15) | 只能通过软件操作生成,用于核间通信 |
| Private Peripheral | PPI | 每核 16 个 (ID 16-31) | 每个核心私有的外设中断 |
| Shared Peripheral | SPI | 最多 224 个 | 所有核心共享的外设中断,可路由到任意核心 |
GIC 控制寄存器是内存映射的,位于 Private Memory Region 内。
6.2 中断路由
Interrupt Processor Targets 寄存器控制每个 SPI 中断被路由到哪个(或哪些)核心。对于私有中断(SGI 和 PPI),该寄存器被忽略——私有中断永远只作用于所属核心。
私有中断的配置寄存器(包括优先级、使能/禁能)对每个核心是banked的——即每个核心有自己的独立副本。
6.3 软件生成中断(SGI)
SGI 是多核核间通信的关键机制:
- 可以向任何核心或核心组发送 SGI
- 接收方的中断优先级由其自己的优先级配置决定,而非发送方的配置
- 中断确认寄存器(IAR)的 bits[12:10] 提供发送核心的 ID
- 目标列表过滤器(Target List Filter)提供三种简写方式:
- 发送给所有处理器
- 发送给除自己外的所有处理器
- 发送给指定的目标列表
在实际代码中,向 Core1 发送 SGI ID 0 的典型写法如下:
1 | ; 假设 GIC Distributor 基地址已加载到 r0 |
当 Core1 收到该 SGI 后,在中断服务例程中读取中断确认寄存器(GICC_IAR):
1 | ; GICC_IAR 寄存器偏移 0x0C (CPU Interface 基地址在 Private Memory Region 0x0100) |
需要注意的是,即便 Core0 和 Core1 都把 SGI ID 0 配置为不同的优先级,Core1 实际响应该中断时使用的是 Core1 自己对该 SGI ID 的优先级配置,而不是 Core0 发送时的配置——这给了每个接收核心独立的优先级控制权。
SGI 广泛用于两类场景:
- SMP 内核同步(softirq):触发调度、RCU 回调、任务唤醒
- AMP 核间通信:通过 SGI 作为”门铃”信号 + 共享内存缓冲区传递消息数据
至此,我们已经覆盖了多核系统运行时的三大支柱——Cache 一致性保证数据可见、LDREX/STREX 保护临界区、GIC/SGI 提供核间信号。现在回到一切开始的起点:一个多核系统如何从 Reset 状态启动到所有核心都能独立运行?
7. SMP 系统启动流程
SMP 系统的启动遵循”主从模式”:只有一个核心(主核/Primary Core)负责初始化内存系统和外设,其余核心(从核/Secondary Core)等待唤醒。
7.1 两种硬件启动策略
策略一:所有核心同时从 Reset 向量启动
主核读取 Cluster ID(MPIDR 寄存器)确认自己身份后执行初始化,初始化完成后向从核发出信号。从核在启动代码中检测到自己不是主核后进入等待状态。
策略二:硬件保持从核在 Reset 状态
需要硬件支持来协调各核心的 Reset 状态,主核单独初始化完成后释放从核。这在 AMP 系统中尤为重要——每种应用可能需要不同的启动顺序。
这两种策略是SoC厂商决定的,是SoC硬件设计的一部分。
7.2 MPIDR 寄存器
CP15 的 MPIDR(Multiprocessor Affinity Register) 用于标识当前处理器在多核系统中的位置,是启动代码识别主核和操作系统管理 CPU 拓扑的重要依据。
MPIDR 提供最多三级 Affinity(亲和性) 标识,每级 8 位:
- Affinity Level 0(Aff0):通常表示核心(Core)编号。
- Affinity Level 1(Aff1):通常表示核心簇(Cluster)编号。
- Affinity Level 2(Aff2):用于标识更高层级的处理器拓扑(如多 Cluster 或多 Socket 系统)。
此外,ARMv7-A 多处理扩展还定义了 U(Uniprocessor)位,用于指示当前处理器是否属于单核实现:U=1 表示系统中不存在其他可寻址的处理器,U=0 则表示支持多处理器。
MPIDR 的主要用途包括:
- 启动阶段:Bootloader 读取 MPIDR,判断当前核心是否为主核(Primary Core),决定继续执行系统初始化还是进入等待状态。
- 操作系统:内核利用 MPIDR 构建 CPU 拓扑,识别核心之间的层级关系,从而优化任务调度、负载均衡以及 Cache 局部性。
7.3 Linux SMP 启动流程
Linux 的 SMP 从核启动流程如下:
- 主核完成常规启动流程(引导加载器 → 内核初始化),建立页表、使能 MMU 和 Cache,并完成 GIC 等关键硬件初始化。
- 从核进入 Holding Pen,通常执行 WFE(Wait For Event)或 WFI(Wait For Interrupt)等待主核唤醒。
- 主核将全局变量
pen_release设置为待启动处理器的 MPIDR 值,指定本次允许启动的目标核心。 - 主核通过 GIC 发送 IPI(Inter-Processor Interrupt,一种 SGI) 唤醒从核。
- 从核被唤醒后读取自身 MPIDR,并与
pen_release比较:- 匹配 → 继续启动,加载内核页表、使能 MMU 和 Cache,并完成处理器相关初始化。
- 不匹配 → 返回 Holding Pen,继续执行 WFE/WFI 等待下一次唤醒。
- 从核完成初始化后,在
cpu_online_map中标记自己已上线,并进入空闲循环(Idle Loop),等待调度器分配任务。 - 主核检测到
cpu_online_map更新后,修改pen_release为下一个待启动核心的 MPIDR,重复上述过程,直至所有从核完成启动。
下图以时序图展示了 Linux 下主核与从核的典型交互:
1 | Primary Core (Core0) Secondary Core (Core1) |
Linux 的从核启动机制可以概括为 **Holding Pen + IPI + pen_release**:
- Holding Pen:从核进入等待循环(通常执行
WFE或WFI),避免空转消耗处理器资源。 - IPI(Inter-Processor Interrupt):主核通过 GIC 发送软件生成中断(SGI),通知从核检查是否可以启动。
- **
pen_release**:提供”点名”机制。从核被唤醒后立即将pen_release的值与自身 MPIDR 比较,只有匹配的核心继续启动,其余核心重新进入等待状态。
这种”点名 + 唤醒”机制使主核能够按顺序启动各个从核,并确认每个从核完成初始化后,再继续启动下一个核心,从而保证整个 SMP 启动过程简单、可靠且易于管理。
7.4 AMP 启动的额外考量
在 AMP 系统中,引导加载器必须根据各核心的 Cluster ID 确定各自的起始地址(因为不同核心运行不同的代码)。当不同核心上的应用存在启动依赖关系时,需要额外小心确保正确的启动顺序。
启动完成后,每个核心需要一个私有的硬件资源空间来存放自己的外设控制寄存器、定时器和看门狗。这个空间就是 ARM 多核处理器中的 Private Memory Region。
8. Private Memory Region
在 Cortex-A5 和 Cortex-A9 MPCore 处理器中,多个仅供处理器集群内部(Cluster内部)使用的硬件模块被统一映射到 Private Memory Region——一个 8 KB 的专用地址空间。其基地址由 SoC 硬件决定,可通过 CP15 的 Peripheral Base Address Register(PBAR) 获取。
8.1 寄存器布局
| 基地址偏移 | 功能 |
|---|---|
0x0000 |
Snoop Control Unit(SCU)控制寄存器 |
0x0100 |
GIC CPU Interface(中断控制器 CPU 接口) |
0x0200 |
Global Timer |
0x0600 |
Private Timer / Watchdog |
0x1000 |
GIC Distributor(中断控制器分配器) |
Private Memory Region 具有以下特点:
- 所有寄存器均采用 Little-Endian 字节序访问,与处理器当前的 Endianness 设置无关,因此当处理器运行于 Big-Endian 模式时需要特别注意。
- 部分寄存器采用 banked 设计,即每个核心拥有独立的一份寄存器副本(例如 Private Timer 和 Watchdog)。
- 该区域仅供处理器集群内部访问,不能通过 ACP(Accelerator Coherency Port)访问。
8.2 定时器与看门狗
每个核心都拥有一个 Private Timer 和一个 Watchdog。Private Timer 支持一次性(One-shot)和自动重载(Auto-reload)两种工作模式,通过 32 位计数器和 8 位预分频器配置定时时间;处理器进入调试状态时,定时器可暂停计数。
此外,Cortex-A5 和 Cortex-A9 MPCore 还提供了一个 Global Timer。它是一个所有核心共享的 64 位递增计数器(寄存器通过两个 32 位寄存器访问),所有核心看到的是同一个计数值。
虽然计数器是共享的,但每个核心都拥有独立的 Comparator 和 Auto-increment 寄存器。当 Global Timer 的计数值达到本核心设置的比较值时,即向该核心产生定时中断;若启用了 Auto-increment,比较值会在每次触发后自动递增,从而周期性地产生中断,而无需软件重新编程。
这种设计非常适合操作系统实现每 CPU 定时事件(Per-CPU Timer Event)。例如,Linux 可以为每个核心设置不同的比较值,使各核心的调度 Tick 错开触发,避免所有核心在同一时刻进入调度器,从而降低锁竞争和总线压力。
9. 关键要点
三种多处理模型各有分工:SMP 追求负载均衡和透明性,AMP 提供确定性和隔离,HMP/big.LITTLE 在性能和能效间取得动态平衡。
硬件 Cache 一致性是 SMP 的基础:MESI/MOESI 协议配合 SCU 实现了无需软件介入的自动一致。SCU 还可以在 L1 Cache 之间直接迁移数据,避免外部内存访问。
ACP 和 CCI-400 扩展了一致性边界:ACP 将一致性延伸到无 Cache 的 DMA 引擎等外部 Master;CCI-400 通过 AMBA 4 ACE 协议实现跨簇一致性。
SMP、FW、MMU、Normal Shareable 四者必须同时满足,SCU 才能对一次内存访问执行一致性管理。
LDREX/STREX 是 ARM 多核同步的基础指令:与 Local/Global Monitor 协同,为 spinlock 等同步原语提供硬件支持。STREX 返回 0 表示成功、1 表示失败。
GIC 的分层中断体系覆盖了从核间信号(SGI)到私有外设(PPI)再到共享外设(SPI)的全部场景。SGI 的目标列表过滤器极大简化了核间中断的编程模型。
SMP 启动遵循主从串行模式:Linux 用
pen_release+ WFI + IPI 实现了稳妥的从核唤醒机制。Cache/TLB 维护操作需要严格顺序:clean 从内到外,invalidate 从外到内,以避免 L1/L2 两步操作间的一致性窗口。