1. ARM CHI协议中的独占访问机制解析
在ARM多核处理器架构中,独占访问(Exclusive Access)是实现原子操作的核心机制。这种机制允许一个处理器核心以独占方式访问特定内存区域,确保在执行"读-修改-写"操作序列时不会被其他核心干扰。这种特性对于实现锁、信号量等同步原语至关重要。
1.1 独占事务类型与通信节点
ARM CHI协议定义了多种支持独占访问的事务类型,根据访问目标的不同可分为两大类:
Snoopable位置独占事务:
- 独占加载(Exclusive Load):
- ReadClean
- ReadNotSharedDirty
- ReadShared
- ReadPreferUnique
- 独占存储(Exclusive Store):
- CleanUnique
- MakeReadUnique
Non-snoopable位置独占事务:
这些事务通过Excl标志位表明其独占属性。通信节点对根据访问类型有所不同:
- 对于Snoopable位置:RN-F ↔ ICN(HN-F)
- 对于Non-snoopable位置:RN-F/RN-D/RN-I ↔ ICN(HN-F/HN-I)或ICN ↔ SN-F/SN-I
关键提示:所有独占事务必须使用正确的LPID(Logical Processor Identifier)值,这是确保多核间正确协调的基础。
1.2 独占请求的响应机制
独占事务的响应与常规读写响应类似,但有以下关键区别:
-
对于ReadClean、ReadNotSharedDirty、ReadShared和ReadNoSnp独占事务:
- 禁止使用分离的Comp和data响应
- 成功的请求不得使用DMT或DCT
-
对于WriteNoSnpFull和WriteNoSnpPtl事务:
- 如果独占监控器位于Home节点且检查失败,则不得使用DWT
响应中的RespErr字段专门用于指示独占操作状态:
- 0b01(Exclusive Okay):独占操作成功
- 0b00(Normal Okay):独占访问失败
值得注意的是,并非所有内存位置都支持独占访问。对于不支持独占访问的位置,独占加载事务绝不能返回Exclusive Okay响应。而对于独占存储事务,是否更新不支持独占访问的位置则由具体实现定义(IMPLEMENTATION DEFINED)。
2. 独占访问的详细工作流程
2.1 Snoopable位置的独占访问
对于Snoopable内存位置的独占访问,ARM CHI协议规定了严格的操作序列:
2.1.1 独占加载阶段
LP(Logical Processor)通过以下方式启动独占序列:
- 发起带有Excl标志的加载事务(如ReadClean、ReadShared等)
- 设置LP独占监控器(LP exclusive monitor)
当LP已经缓存了目标cache line时:
- 如果处于Unique状态:允许但不推荐执行独占加载事务
- 如果处于Shared状态:允许但不要求执行独占加载事务
- 如果没有缓存副本:推荐使用独占加载事务获取cache line
2.1.2 独占存储阶段
在独占加载后,LP通常会计算新值并尝试独占存储。关键行为包括:
- 禁止同时存在多个进行中的独占事务
- 如果LP独占监控器被重置,必须使独占存储失败并重启序列
- 如果cache line处于Unique状态且监控器设置,可直接更新位置
- 如果cache line处于Shared状态且监控器设置,必须发起独占存储事务(CleanUnique或MakeReadUnique)
MakeReadUnique的特殊处理:
MakeReadUnique(Excl)是CleanUnique(Excl)的优选替代方案。其响应处理较为特殊:
- 不允许在响应中使用Exclusive Okay
- 请求者必须结合本地监控器状态和响应缓存状态判断操作结果:
- 响应缓存状态为Shared → 独占访问失败
- 响应缓存状态为Unique → 根据本地监控器判断
2.2 Non-snoopable位置的独占访问
对于Non-snoopable内存位置的独占访问有以下限制:
- 地址必须按事务总字节数对齐
- 传输字节数必须是合法数据大小(1/2/4/8/16/32/64字节)
- 独占读写的以下字段必须一致,否则可能导致失败:
- Addr
- MemAttr
- SnpAttr
- Size
- LPID
系统监控器(System monitor)的最小监控范围由事务大小决定,但可以扩展到最大64字节。这可能导致"误报"失败——即因相邻字节被修改而导致独占访问被错误判定为失败。
3. 缓存存储(Cache Stashing)机制详解
3.1 缓存存储概述
缓存存储是一种性能优化机制,它允许数据在写入时被直接安装到特定缓存中,从而减少后续访问延迟。CHI协议支持两种主要形式:
带Stash提示的写入(Write with stash hint):
- WriteUniqueFullStash
- WriteUniquePtlStash
- 在写入数据时即知道目标缓存的情况下使用
独立Stash请求(Independent Stash request):
- StashOnceUnique/StashOnceSepUnique
- StashOnceShared/StashOnceSepShared
- 当Stash请求与数据写入时间分离时使用
缓存存储只能用于Snoopable内存,且本质上是一种性能提示(hint),接收方可以选择忽略。
3.2 缓存存储的工作流程
3.2.1 带Stash提示的写入
WriteUniqueFullStash和WriteUniquePtlStash的处理规则:
请求者(Requester)必须:
- 根据写入完整或部分cache line选择对应事务类型
- 请求中应包含Stash目标信息
Home节点必须:
- 向指定Stash目标发送SnpUniqueStash
- 向其他共享cache line的请求者发送SnpUnique
- 对于WriteUniqueFullStash,可用SnpMakeInvalidStash替代SnpUniqueStash
- 完成一致性操作后向请求者发送Comp响应
Stash目标必须:
- 支持包含Data Pull的响应类型:
- SnpResp_I_Read
- SnpRespData_I_Read
- SnpRespData_I_PD_Read
- SnpRespDataPtl_I_PD_Read
- 在特定条件下禁止请求Data Pull
3.2.2 独立Stash请求
StashOnce和StashOnceSep请求的处理规则:
请求者必须:
- 根据后续使用场景选择Unique或Shared类型的Stash请求
- 提供或不提供Stash目标信息
- 对于StashOnceSep,必须能处理StashDone响应
Home节点必须:
- 对于StashOnceUnique/StashOnceSepUnique,向目标RN-F发送SnpStashUnique
- 对于StashOnceShared/StashOnceSepShared,向目标RN-F发送SnpStashShared
- 即使放弃Stash请求也必须发送Comp响应
Stash目标必须:
- 不改变本地cache line状态
- 在特定条件下禁止请求Data Pull
- 确保Read数据能被无死锁风险地接受
3.3 Stash目标标识与消息类型
Stash目标标识:
Stash目标可以通过以下字段指定:
- StashNID:目标节点ID
- StashLPID:目标逻辑处理器ID
- StashNIDValid/StashLPIDValid:标识字段有效性
当不指定Stash目标时,Home节点会根据cache line状态决定如何处理:
- 如果cache line以Unique状态缓存在某请求节点,可将该节点视为Stash目标
- 否则只发送必要的SnpUnique而不发送SnpUniqueStash
支持的Stash消息类型:
-
写入请求:
- WriteUniqueFullStash
- WriteUniquePtlStash
-
无数据请求:
- StashOnceUnique
- StashOnceSepUnique
- StashOnceShared
- StashOnceSepShared
-
Snoop请求:
- SnpUniqueStash
- SnpMakeInvalidStash
- SnpStashUnique
- SnpStashShared
-
Stash响应:
- Comp
- StashDone
- CompStashDone
4. 实现考量与性能优化
4.1 独占访问的系统要求
实现CHI协议的系统必须:
- 为所有独占请求提供防饥饿机制
- 建议每个LP配备独占监控器以提高处理效率
- 确保来自不同PAS的独占请求能独立进展
4.2 缓存存储的性能影响
缓存存储通过以下方式提升系统性能:
- 数据局部性:将数据预取到使用点附近的缓存
- 减少延迟:避免后续访问时从远端或主存获取数据
- 带宽优化:通过选择性缓存减少不必要的传输
实际应用中的权衡:
- 对于立即使用的数据:采用带Stash提示的写入
- 对于延迟使用的数据:采用独立Stash请求
- 对于调度不确定的场景:使用StashOnceSep分离请求与数据写入
4.3 监控器设计与避坑指南
独占监控器实现要点:
- 必须能同时跟踪系统中每个LP的独占线程
- 建议使用精确的Snoop Filter判断独占访问成败
- 缺乏精确缓存信息时可使用SnpQuery探测请求者状态
常见问题与解决方案:
-
独占访问活锁:
- 实现完善的防饥饿机制
- 确保监控器能处理所有LP的并行请求
-
Stash目标缓存污染:
- 合理选择Stash时机
- 对非立即使用的数据采用独立Stash请求
-
监控范围过大导致的假失败:
- 根据实际需求调整监控粒度
- 在软件层面对大数据结构进行适当分块
-
独占序列中断:
在性能敏感场景中,建议通过微架构性能计数器监控独占访问和缓存存储的成功率,据此调整实现策略。例如,高频失败的独占访问可能表明锁竞争激烈,需要优化同步算法。