指令获取访问传递锁定的指令

时间:2015-01-17 05:26:34

标签: assembly concurrency x86 thread-safety x86-64

英特尔软件开发人员手册提到"指令获取和页表访问可以传递锁定的指令"。这是什么意思,为什么重要?

有一篇帖子说许多Windows函数都以MOV EDI, EDI指令开头,因为它对安全代码挂钩非常有用:它可以用两个字节的相对跳转原子替换。但是,如果获取对内存的访问可以"传递锁定的指令",是否可能发生以下情况?

  • cpu 0原子地用相对跳转替换MOV EDI, EDI指令
  • cpu 1"传递锁定的指令",获取并执行陈旧的MOV EDI, EDI

这样的事情也有可能发生吗?

  • cpu 0原子地用相对跳转替换MOV EDI, EDI指令
  • 因为指令提取可以通过锁定指令",从指令提取的上下文中可以认为指令的替换是非原子的,所以cpu 1从陈旧指令中取出1个字节,并且1个字节来自新指令

来自Intel 64和IA-32架构软件开发人员手册,第3卷:"系统编程指南"

  

锁定操作相对于所有其他内存都是原子操作   操作和所有外部可见事件。 仅提取指令   和页表访问可以传递锁定的指令。锁定   指令可用于同步一个处理器写入的数据   并由另一个处理器读取。

     

对于P6系列处理器,锁定操作序列化所有   出色的加载和存储操作(即等待它们   完成)。对于Pentium 4和Intel Xeon,此规则也适用   处理器,但有一个例外。加载弱引用的操作   有序的内存类型(例如WC内存类型)可能不是   序列

链接:Why do Windows functions all begin with a pointless MOV EDI, EDI instruction?

1 个答案:

答案 0 :(得分:1)

关于第二种情况 - “传递锁定的指令”并不意味着它打破了原子性。如果存储器以原子方式写入这两个指令字节,则在任何时候都看不到它们中的一个(存储将只在完整的高速缓存行上运行 - 请注意,如果2个字节被分成2行,则它不会是原子的)。它的意思是你为了尝试同步而放置的任何锁定指令都不会阻止代码获取,所以在内存排序方面 - 它可以在它之前或之后发生。

现在,关于第一个场景和一般问题 - 请注意您的描述中没有锁定。您描述的情况完全有效,即使它是数据读取而不是代码读取 - 除了您自己执行的操作之外,两个核心之间没有固有的顺序。为了强制执行这样的命令,你可以开始使用障碍和信号量,或任何其他方法,它最终会归结为阻塞cpu 1的某些锁,直到cpu 0表示写完成。

在这种情况下,数据读取可能会被锁定停止,但是尽管您试图保护它,但较年轻的代码读取实际上可能会获取旧数据。但是,这里有一个机制x86核心通常实现称为SMC(自修改代码)flush - 来自cpu 0的存储窥探cpu 1中的指令缓存,检测那里的陈旧代码,并且因为它无法分辨出这个代码到底在哪里沿着管道,或者可能已经产生了什么影响(因为我们知道那里可能有一个暂停指令,或者更糟) - 它只是冲洗整个管道。不同产品的具体细节可能不同,但概念很老。

页面遍历情况稍微复杂一些,但是这里还有一种机制可以检测大多数使用过程中的修改情况 - 查找“TLB击落”。请注意,在某些情况下,运行期间SMC和TLB都会进行修改完全有效并且有用处(SMC经常用于JITting,页面移动是一种在流程之间传递数据而不必复制它的廉价方法。