Winapi 暂停x86指令和GetSystemTimeAsFileTime函数反汇编中的循环的目的是什么?

Winapi 暂停x86指令和GetSystemTimeAsFileTime函数反汇编中的循环的目的是什么?,winapi,x86,reverse-engineering,disassembly,Winapi,X86,Reverse Engineering,Disassembly,我在研究函数的工作原理,看看它在内部有多优化,偶然发现了这组看似简单的指令(在我的Windows 10系统中): 如您所见,该函数只读取3个全局变量:2是系统时间(或FILETIME),然后将较低的DWORD与第3个变量进行比较,如果它们相同,则循环直到它们不相同 据我所知,需要使用pause指令短时间停止CPU以节省电源。这就是它在那里的原因吗 此外,如果是这样,为什么他们不使用关键部分来防止这3个全局变量的线程同步问题 最后,这个循环的目的是什么 编辑:有趣的发现。我研究了x64的实现,它

我在研究函数的工作原理,看看它在内部有多优化,偶然发现了这组看似简单的指令(在我的Windows 10系统中):

如您所见,该函数只读取3个全局变量:2是系统时间(或
FILETIME
),然后将较低的
DWORD
与第3个变量进行比较,如果它们相同,则循环直到它们不相同

据我所知,需要使用
pause
指令短时间停止CPU以节省电源。这就是它在那里的原因吗

此外,如果是这样,为什么他们不使用
关键部分
来防止这3个全局变量的线程同步问题

最后,这个循环的目的是什么

编辑:有趣的发现。我研究了x64的实现,它更简单。就是这个,


x64实现以原子方式读取64位值(如果正确对齐)

经典x86指令无法做到这一点

假设我们有值(这些是示例值,不是真实值):

因为两个32位值的读取可以通过定时器中断进行分割,所以读取部分旧值和部分新值的可能性很小。如果中断后,我们有:

low part:  0x00001111
high part: 0x00000002
我们可能会以错误的价值观结束:

low part:  0x00001111
high part: 0x00000001 <- WRONG
low部分:0x00001111
高部分:0x00000001正如Daniel的解释,这只是一种在64位原子操作不可用或出于某种原因不希望使用它们的情况下,使用两个32位读取实现64位原子读取的方法

具体地说,关于
pause
指令,读取计数器的用户land代码很少会恰好在内核更新计数器的时候出现。此时,它希望“等待”内核完成更新,因为在更新完成之前它无法继续,但立即再次读取值可能会适得其反,因为写入和读取代码将争夺相关的缓存线

pause
指令在这里很有用,因为它插入了一个小延迟,是一条单独的指令,并且还向CPU提示我们处于一种自旋等待循环中,不需要进一步推测内存读取。它还允许在同一内核上运行的另一个线程(在另一个“超线程”上)更多地使用内核的执行资源。在本例中,另一个线程很可能是试图完成写入的内核线程,因此这很有意义

代码将在没有暂停的情况下工作,因此这只是一个较小的性能优化。绝大多数情况下,这条路径甚至没有被采用,所以整体效果可能是显微镜下的



1在评论中提到,高音部分每7分钟改变一次,因此,我们需要重试的比赛中获胜的机会非常非常小。让我们保守地假设内核大小上的两次写入之间的间隔为10 ns(在典型机器上约为30个周期),我计算出任何给定读取命中竞争的概率约为400亿分之一。考虑到这一点,您可以提出合理的论点,即暂停可能是一种悲观的看法-在这个慢路径中的任何额外指令可能不会在代码大小和好处方面得到回报(尽管在这里,它们似乎已经将慢路径放入了自己的缓存线,因此额外的字节可能在那里是“免费的”).

@MichaelPetch:嗯,你在哪里看到的?在那些
mov
指令中没有任何
关键的
相互排斥的
。我找到了一些作品。您可能希望阅读这篇文章,尤其是Windows Times一节,该节专门定义了想要检索计时器值的函数必须做什么才能获得详细说明同步算法的时间值。如果您阅读了这篇文章并查看了您的代码-3个全局值(在共享用户数据区域中)是内核系统计时器结构的一部分。0x7ffe0014是
LowPart
,0x7ffe0018是
High1Time
,0x7FFE01C是
High2Time
技巧在于严格的顺序,其中3个时间字段必须写入和读取。
GetSystemTimeAsFileTime()
使用的计时器中断服务和循环遵循该顺序,以确保
GetSystemTimeAsFileTime()
获得了准确和一致的时间,而没有实际锁定任何东西,在x86上,与32位对齐(自然对齐)的内存位置之间的32位
mov
将是原子的。对于32位
mov
,对齐是至关重要的,因为如果它不是自然对齐的,则不能保证原子性。32位x86,但仅适用于MMX/SSE2
movq
(或SSE1
movlps
)或x87
fild
//code>fistp
gcc-m32
将其用于
std::atomic
。或者使用
锁cmpxchg8b
,这比进行三次32位存储或加载更昂贵。FWIW这种无锁读取技术的推广称为。这篇文章指出它在某种程度上是特定于Linux的,但事实并非如此(也许这就是这个名字最初的来源,但我甚至不确定是否如此)。它只在具有“足够强”内存顺序的平台上工作,特别是读不随读重新排序的平台上(对于读端:写端不需要存储重新排序就可以无锁,但这通常不那么重要,而且通常您会锁定写)。@BeeOnRope:非常有趣。谢谢分享。现在我们知道了这项技术的名称,也可能知道提出这项技术的工程师。我认为这句话适用于这里:
“第一个实现是在x86-64时间代码中,它需要与无法使用真正锁的用户空间同步”
。我是n
low part:  0x00001111
high part: 0x00000001 <- WRONG