考虑在Windows XP上的32位地址空间内运行的复杂,内存饥饿,多线程应用程序。
某些操作需要n个固定大小的缓冲区,其中一次只需要访问一个缓冲区。
应用程序使用一种模式,其中一些缓冲区大小的地址空间提前保留,用于包含当前所需的缓冲区。
这遵循以下顺序: (初次运行)VirtualAlloc - > VirtualFree - > MapViewOfFileEx (缓冲区更改)UnMapViewOfFile - > MapViewOfFileEx
这里指向缓冲区位置的指针是通过调用VirtualAlloc提供的,然后在每次调用MapViewOfFileEx时使用相同的位置。
问题是Windows(据我所知)没有提供任何握手类型操作来传递不同用户之间的内存空间。
因此,存在一个小机会(在我的上述序列中的每个 - >;),其中内存未被锁定,另一个线程可以跳入并在缓冲区内执行分配。
对MapViewOfFileEx的下一次调用被破坏,系统无法再保证缓冲区的地址空间中有足够大的空间。
显然,重构使用较小的缓冲区会降低重新分配空间的失败率。
HeapLock的一些使用取得了一些成功,但这仍然存在问题 - 仍有一些东西会在地址空间内窃取一些内存。 (我们尝试调用GetProcessHeaps,然后使用HeapLock锁定所有堆)
我想知道的是,无论如何要锁定与MapViewOfFileEx兼容的特定地址空间块?
编辑:我应该补充说,最终这段代码存在于一个由我无法控制的应用程序调用的库中
答案 0 :(得分:1)
你可以蛮力;暂停进程中的每个线程,而不是执行映射的线程,Unmap / Remap,取消悬挂的线程。它并不优雅,但这是我能想到的唯一方式来提供你需要的那种互斥。
答案 1 :(得分:0)
您是否考虑过通过HeapCreate
创建自己的私有堆?您可以将堆设置为所需的缓冲区大小。唯一剩下的问题是如何让MapViewOfFile
使用您的私有堆而不是默认堆。
我假设MapViewOfFile
在内部调用GetProcessHeap
来获取默认堆,然后它会请求一个连续的内存块。您可以绕道而行绕MapViewOfFile
的呼叫,即通过覆盖内存中的方法重新连接GetProcessHeap
调用,有效地将跳转插入到您自己的代码中,这可以返回您的私有堆。
Microsoft已发布我不熟悉的Detour Library。我知道绕道是非常常见的。安全软件,病毒扫描程序等都使用这样的框架。它不漂亮,但可能有效:
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(...);
}
}
这些也可能有所帮助:
答案 2 :(得分:0)
想象一下,如果我带着这样的代码来找你:
void *foo;
foo = malloc(n);
if (foo)
free(foo);
foo = malloc(n);
然后我来找你说帮忙! foo
在第二次分配时没有相同的地址!
我会发疯,对吗?
在我看来,你已经清楚地知道了为什么这不起作用。有一个原因是,任何需要显式地址映射到的API的文档都会让您知道该地址只是一个建议,并且无法保证。这也适用于POSIX上的mmap()
。
我建议你以这样一种方式编写程序,使地址的变化无关紧要。也就是说,不要在缓冲区内存储太多指针,或者如果你这样做,请在重新分配后修补它们。与您将要传递到realloc()
的缓冲区的方式类似。
即使是MapViewOfFileEx()
的文档明确暗示了这一点:
虽然可以指定现在安全的地址(操作系统不使用),但无法保证地址随时间保持安全。因此,最好让操作系统选择地址。在这种情况下,您不会将指针存储在内存映射文件中,您将存储来自文件映射基础的偏移量,以便可以在任何地址使用映射。
从您的评论中更新
在这种情况下,我想你可以:
不映射到连续的块。也许你可以用块映射并编写一些中间函数来决定从哪个读/写?
尝试移植到64位。
答案 3 :(得分:0)
正如之前的帖子所暗示的,您可以在更改内存映射时暂停进程中的每个线程。您可以使用SuspendThread()/ ResumeThread()。这样做的缺点是你的代码必须知道所有其他线程并为它们保存线程句柄。
另一种方法是使用Windows debug API暂停所有线程。如果进程附加了调试器,则每次进程发生故障时,Windows将挂起所有进程的线程,直到调试器处理故障并恢复进程。
另见这个问题非常相似,但措辞不同: Replacing memory mappings atomically on Windows