11

我有一个简单的测试程序,它使用 movdqu 指令加载一个 xmm 寄存器,该指令在页面边界(OS = Linux)上访问数据。

如果映射了以下页面,则可以正常工作。如果它没有被映射,那么我会得到一个 SIGSEGV,这可能是预期的。

然而,这大大降低了未对齐负载的有用性。此外,允许未对齐内存引用的 SSE4.2 指令(如 pcmpistri)似乎也表现出这种行为。

这一切都很好——除了我发现有许多使用 pcmpistri 的 strcmp 实现似乎根本没有解决这个问题——而且我已经能够设计出会导致这些实现失败的琐碎测试用例,而一次一个字节的琐碎 strcmp 实现将在相同的数据布局下正常工作。

还有一点需要注意——64 位 Linux 的 GNU C 库实现似乎有一个 __strcmp_sse42 变体,它似乎以更安全的方式使用 pcmpistri 指令。这个 strcmp 的实现相当复杂,但它似乎在小心翼翼地试图避免页面边界问题。我不确定这是由于我上面描述的问题,还是只是试图通过对齐数据来获得更好的性能的副作用。

无论如何,我的问题主要是——我在哪里可以找到更多关于这个问题的信息?我已经输入了“movdqu 跨越页面边界”以及我能想到的谷歌的每一个变体,但没有遇到任何特别有用的东西。如果有人可以向我指出有关这方面的更多信息,将不胜感激。

4

2 回答 2

8

首先,任何试图访问未映射地址的算法都会导致 SegFault。如果非 AVX 代码流使用 4 字节加载来访问页面的最后一个字节和碰巧没有被映射的“下一页”的前 3 个字节,那么它也会导致 SegFault。不?我相信“问题”是 AVX(1/2/3) 寄存器比“典型”大得多,以至于如果将不安全的算法(但逃脱了它)简单地扩展到更大的寄存器,它们就会被捕获.

对齐负载 (MOVDQA) 永远不会有这个问题,因为它们不会跨越任何自身大小或更大的边界。未对齐的负载可能会出现这个问题(正如您所指出的)并且“经常”会出现。原因是该指令被定义为加载目标寄存器的完整大小。您需要非常仔细地查看指令定义中的操作数类型。您对多少数据感兴趣并不重要。重要的是指令被定义为做什么。

然而...

AVX1 (Sandybridge) 添加了“屏蔽移动”功能,它比 movdqa 或 movdqu 慢,但不会(在架构上)访问未映射的页面,只要没有为该页面中的访问部分启用掩码. 这是为了解决这个问题。一般来说,向前看,加载/存储的屏蔽部分(参见 AVX512)似乎也不会导致 IA 上的访问冲突。

(PCMPxSTRx 的行为真是令人沮丧。也许您可以在“字符串”对象中添加 15 个字节的填充?)

于 2014-06-20T23:34:15.743 回答
2

面对我正在编写的一个库的类似问题,我从一位非常有帮助的贡献者那里得到了一些信息。

该思想的核心是将 16 字节读取对齐到字符串的末尾,然后在开头处理剩余的字节。这是有效的,因为字符串的结尾必须位于可访问的页面中,并且您可以保证 16 字节截断的起始地址也必须位于可访问的页面中。

由于我们从未读过字符串,因此我们不可能误入受保护的页面。

为了处理初始字节集,我选择使用PCMPxSTRM函数,它返回匹配字节的位掩码。然后只需移动结果以忽略字符串真正开头之前出现的任何掩码位。

于 2015-05-17T14:41:12.493 回答