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

时间:2017-12-09 02:48:51

标签: winapi x86 reverse-engineering disassembly

我正在研究GetSystemTimeAsFileTime函数的工作原理,以了解它在内部的优化程度,并偶然发现这个看似简单的指令集(在我的Windows 10系统中):

enter image description here

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

据我所知,需要pause指令才能在短时间内停止CPU以节省电量。这就是它的原因吗?

此外,如果是这种情况,为什么他们没有使用critical section来阻止与这3个全局变量的线程同步问题?

最后,那个循环的目的是什么?

编辑: 有趣的搜索结果。我查看了x64实现,它更简单。它只是这个:

enter image description here

2 个答案:

答案 0 :(得分:4)

正如Daniel answer所解释的那样,这只是一种在原子64位操作不可用或不希望使用的情况下使用两个32位读取实现原子64位读取的方法他们出于某种原因。

特别是关于pause指令,在极少数情况下,读取计数器的用户区代码碰巧发生在内核更新它们的确切时刻。此时,它希望“等待”直到内核完成更新,因为它在发生之前无法继续,但是立即再次读取值可能会适得其反,因为写入和读取代码将会在涉及缓存行。

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

代码可以在没有pause的情况下工作,所以这只是一个次要的性能优化。巨大的浩瀚,大部分时间都没有采取这条路径,所以整体效果可能是微观的 1

1 在评论中提到高位部分每7分钟发生一次变化,所以我们需要重试的比赛的机会非常非常小。让我们保守地假设两个写入内核大小之间的差距是10 ns(在典型的机器上大约30个循环),我计算任何给定读取在大约1到400亿的比赛中的概率。鉴于此,你可以提出一个合理的论点,即pause可能在这里是一个悲观 - 在这个缓慢的路径中的任何额外指令可能无法在代码大小与利益方面得到回报(尽管在这里他们似乎已经放弃了缓慢进入自己的缓存行的路径,因此额外的字节可以在那里“免费”。

答案 1 :(得分:2)

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

经典x86指令无法执行此操作。

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

low part:  0xffffffff
high part: 0x00000001

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

low part:  0x00001111
high part: 0x00000002

我们可能以错误的价值结束:

low part:  0x00001111
high part: 0x00000001 <- WRONG

看起来计时器处理程序将高位部分写入两个内存位置。这允许用户代码检测从低部分到高部分的溢出并启动重读时间。由于此代码,无需切换到内核模式,时间读取可以在用户模式下完成。

可以使用更高级的SSE指令来完成,但可能没有必要更改工作代码。

  

PAUSE-Spin Loop提示说明
改进   自旋等待循环的性能。执行“旋转等待”时   循环,“处理器将遭受严重的性能损失   退出循环,因为它检测到可能的内存顺序   违反。 PAUSE指令为处理器提供提示   代码序列是一个自旋等待循环。处理器使用它   提示在大多数情况下避免内存顺序违规   大大提高处理器性能。因此,它是   建议在所有旋转等待中放置PAUSE实例   循环。 PAUSE指令的附加功能是减少   执行自旋循环时处理器消耗的功率。

Intel® 64 and IA-32 Architectures Software Developer’s Manual