4

考虑一个在 Windows XP 上运行在 32 位地址空间内的复杂的、需要大量内存的多线程应用程序。

某些操作需要 n 个固定大小的大缓冲区,其中一次只需要访问一个缓冲区。

应用程序使用一种模式,其中一个缓冲区大小的一些地址空间被提前保留,并用于包含当前需要的缓冲区。

这遵循以下顺序:(初始运行)VirtualAlloc -> VirtualFree -> MapViewOfFileEx(缓冲区更改)UnMapViewOfFile -> MapViewOfFileEx

这里指向缓冲区位置的指针是通过调用 VirtualAlloc 提供的,然后在每次调用 MapViewOfFileEx 时使用相同的位置。

问题是windows(据我所知)不提供任何握手类型的操作来传递不同用户之间的内存空间。

因此,有一个小机会(在我上面的序列中的每个 -> 处)内存未锁定,另一个线程可以跳入并在缓冲区内执行分配。

对 MapViewOfFileEx 的下一次调用被中断,系统无法再保证地址空间中有足够大的空间用于缓冲区。

显然,重构以使用更小的缓冲区会降低重新分配空间的失败率。

HeapLock 的一些使用取得了一些成功,但这仍然存在问题 - 某些东西仍然设法从地址空间中窃取一些内存。(我们尝试调用 GetProcessHeaps 然后使用 HeapLock 锁定所有堆)

我想知道是否有锁定与 MapViewOfFileEx 兼容的特定地址空间块?

编辑:我应该补充一点,最终这段代码存在于一个库中,该库被我无法控制的应用程序调用

4

4 回答 4

1

你可以暴力破解它;挂起进程中不是执行映射(Unmap/Remap)的每个线程,取消挂起挂起的线程。它并不优雅,但这是我能想到的唯一方式来提供你需要的那种互斥。

于 2009-12-01T08:01:56.163 回答
0

您是否考虑过通过创建自己的私有堆HeapCreate?您可以将堆设置为所需的缓冲区大小。剩下的唯一问题是如何MapViewOfFile使用您的私有堆而不是默认堆。

我假设MapViewOfFile内部调用GetProcessHeap以获取默认堆,然后它请求一个连续的内存块。您可以MapViewOfFile绕道而行,即GetProcessHeap通过覆盖内存中的方法来重新连接调用,从而有效地将跳转插入到您自己的代码中,该代码可以返回您的私有堆。

微软已经发布了我不直接熟悉的Detour 库。我知道绕道是非常普遍的。安全软件、病毒扫描程序等都使用这样的框架。它不漂亮,但可能有效:

HANDLE g_hndPrivateHeap;

HANDLE WINAPI GetProcessHeapImpl() {
    return g_hndPrivateHeap;
}    


struct SDetourGetProcessHeap { // object for exception safety 
   SDetourGetProcessHeap() {
       // put detour in place
   }

   ~SDetourGetProcessHeap() {
       // remove detour again
   }
};


void MapFile() {
    g_hndPrivateHeap = HeapCreate( ... );

    {
        SDetourGetProcessHeap d;
        MapViewOfFile(...);
    }
}

这些也可能有帮助:

如何用我自己的实现替换MS VC++项目中的WinAPI函数调用(名称和参数设置相同)?

如何在 C/C++ 中挂钩 Windows 函数?

http://research.microsoft.com/pubs/68568/huntusenixnt99.pdf

于 2009-11-24T13:18:00.673 回答
0

正如之前的帖子所暗示的,您可以在更改内存映射时暂停进程中的每个线程。您可以为此使用 SuspendThread()/ResumeThread()。这样做的缺点是您的代码必须了解所有其他线程并为它们保存线程句柄。

另一种方法是使用Windows 调试 API挂起所有线程。如果一个进程附加了调试器,那么每次进程出现故障时,Windows 都会暂停该进程的所有线程,直到调试器处理故障并恢复该进程。

另请参阅此问题,该问题非常相似,但措辞不同: Replaceing memory mappings atomically on Windows

于 2012-12-13T04:54:07.200 回答
0

想象一下,如果我带着这样的一段代码来找你:

void *foo;

foo = malloc(n);
if (foo)
   free(foo);
foo = malloc(n);

然后我来找你说,救命! foo第二次分配的地址不一样!

我会疯的,对吧?

在我看来,您似乎已经清楚地知道为什么这不起作用。任何需要显式地址映射到的 API 的文档都让您知道该地址只是一个建议,并且不能保证是有原因的。这也适用mmap()于 POSIX。

我建议您以这样一种方式编写程序,即地址的更改无关紧要。也就是说,不要在缓冲区内存储太多指向数量的指针,或者如果这样做,请在重新分配后修补它们。类似于您处理要传入的缓冲区的方式realloc()

甚至文档也MapViewOfFileEx()明确暗示了这一点:

虽然可以指定一个现在安全的地址(操作系统不使用),但不能保证该地址会随着时间的推移保持安全。因此,最好让操作系统来选择地址。在这种情况下,您不会在内存映射文件中存储指针,而是存储文件映射基址的偏移量,以便可以在任何地址使用映射。

根据您的评论更新

在这种情况下,我想你可以:

  • 不映射到连续的块。也许您可以分块映射并编写一些中间函数来决定读取/写入哪个?

  • 尝试移植到 64 位。

于 2009-11-24T17:58:59.560 回答