x86内存模型下存储转发语义及部分重叠读写规则问询
通过实验不难弄清当前CPU的语义,但我想了解的是架构内存模型提供了哪些语义保障?我阅读了Intel SDM卷3第8章,认为其表述模糊,是否有遗漏之处?
手册明确指出:
- 程序顺序中写操作之后的不同地址读操作,可能被重排至写操作之前。
- 程序顺序中写操作之后的相同地址读操作,会在写操作之后执行。
同时手册提到:
在多处理器系统中,内存顺序遵循因果性原则。
关于存储转发仅提及一句:
本节描述的处理器顺序模型与Pentium和Intel486处理器所用模型基本一致。Pentium 4、Intel Xeon和P6系列处理器的唯一增强是:当读操作与同一内存地址的写操作重叠时,支持存储缓冲区转发。
手册还有示例(§8.2.3.5),通常示例不具规范性,但暂时按字面理解,该示例基本将第二条规则修正为:
- 若程序顺序中写操作之后的读操作指向同一地址,则读操作必须“晚于”写操作,即要么观测到该写操作,要么观测到其他核心对同一地址的后续写操作;但第一种情况下,读操作可能在写操作对其他处理器可见前执行。
但手册未提及读操作与之前写操作部分重叠的情况,语义如何?以下是两个具体问题:
问题一
初始状态执行mov word [r1], 0,之后:
核心1
mov byte [_x], 0x01 mov r1, word [_x]
核心2
mov eax, 0x0000 mov r2, 0x0100 lock cmpxchg word [_x], r3
是否必然仅存在以下两种情况之一?
- 写操作先于CAS执行,CAS失败,读操作得到0x0001。
- 写操作晚于CAS执行,CAS成功,读操作得到0x0101。
不存在第三种情况: - 写操作晚于CAS执行,CAS成功,但读操作因在写操作对其他核心可见前执行,仍得到0x0001。
问题二
更简单的场景:核心2不在_x附近写操作(可在其他地址写),仅核心1执行:
mov byte [_x], 0x01 mov r1, word [_x]
该读操作是否必须等待写操作对其他核心可见后才能执行?
这两个问题本质相同:若读操作与存储队列中的写操作部分重叠,能否转发存储队列中的重叠部分,其余部分按常规读操作处理?还是必须等待写操作完成后再执行读操作?手册仅提及读操作“指向同一地址”的情况,但此场景下读操作既包含写操作的地址,也包含其他地址。
有人可能这样推理:
- 字读操作理论上是两个原子执行的字节读操作。
- 将读操作拆分为两个读操作,每个读操作可独立重排至写操作对其他核心可见前;一个与写操作不重叠,一个完全匹配。
- 将两个字节读操作重排至写操作后,再合并为一个原子读操作。
但我认为这种推理不成立:不能将原子操作拆分,对各部分应用非原子推理后再合并为原子操作。而且若该推理成立,问题一的答案是否定的,这是否违反因果性?
我的观点:
- 若问题一答案为否,则违反因果性,存在矛盾。
- 若问题一答案为是、问题二答案为否,则读操作可部分从存储队列转发、部分从缓存读取,但只能推测性执行,若在存储转发完成前其他核心独占缓存行,需回退。
- 若两个问题答案均为是,则读操作可部分从存储队列转发、部分从缓存读取,但仅当执行写操作的核心已独占对应缓存行,且推测在写操作完成前无其他核心申请时,才能推测性执行。
实际中,问题一为是与两个问题均为是的情况差异极小,且我所知的处理器均不支持向部分重叠的读操作转发存储。因此两个问题的答案应均为是,但手册未明确说明。
内容的提问来源于stack exchange,提问作者Moonchild

