是否可以回收地址空间以多次调用 MapViewOfFileEx 而不会失败?

发布于 2024-08-12 16:15:06 字数 766 浏览 1 评论 0原文

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

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

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

这遵循以下顺序: (初始运行)VirtualAlloc ->虚拟免费 ->文件扩展视图 (缓冲区更改)UnMapViewOfFile -> MapViewOfFileEx

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

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

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

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

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

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

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

编辑:我应该补充一点,最终这段代码位于一个库中,该库由我控制之外的应用程序调用

Consider a complex, memory hungry, multi threaded application running within a 32bit address space on windows XP.

Certain operations require n large buffers of fixed size, where only one buffer needs to be accessed at a time.

The application uses a pattern where some address space the size of one buffer is reserved early and is used to contain the currently needed buffer.

This follows the sequence:
(initial run) VirtualAlloc -> VirtualFree -> MapViewOfFileEx
(buffer changes) UnMapViewOfFile -> MapViewOfFileEx

Here the pointer to the buffer location is provided by the call to VirtualAlloc and then that same location is used on each call to MapViewOfFileEx.

The problem is that windows does not (as far as I know) provide any handshake type operation for passing the memory space between the different users.

Therefore there is a small opportunity (at each -> in my above sequence) where the memory is not locked and another thread can jump in and perform an allocation within the buffer.

The next call to MapViewOfFileEx is broken and the system can no longer guarantee that there will be a big enough space in the address space for a buffer.

Obviously refactoring to use smaller buffers reduces the rate of failures to reallocate space.

Some use of HeapLock has had some success but this still has issues - something still manages to steal some memory from within the address space.
(We tried Calling GetProcessHeaps then using HeapLock to lock all of the heaps)

What I'd like to know is there anyway to lock a specific block of address space that is compatible with MapViewOfFileEx?

Edit: I should add that ultimately this code lives in a library that gets called by an application outside of my control

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

晨敛清荷 2024-08-19 16:15:06

你可以暴力破解它;挂起进程中不是执行映射的线程的每个线程,取消映射/重新映射,取消挂起挂起的线程。它并不优雅,但这是我能立即想到的提供您所需的互斥的唯一方法。

You could brute force it; suspend every thread in the process that isn't the one performing the mapping, Unmap/Remap, unsuspend the suspended threads. It ain't elegant, but it's the only way I can think of off-hand to provide the kind of mutual exclusion you need.

输什么也不输骨气 2024-08-19 16:15:06

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

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

Microsoft 发布了 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

Have you looked at creating your own private heap via HeapCreate? You could set the heap to your desired buffer size. The only remaining problem is then how to get MapViewOfFileto use your private heap instead of the default heap.

I'd assume that MapViewOfFile internally calls GetProcessHeap to get the default heap and then it requests a contiguous block of memory. You can surround the call to MapViewOfFile with a detour, i.e., you rewire the GetProcessHeap call by overwriting the method in memory effectively inserting a jump to your own code which can return your private heap.

Microsoft has published the Detour Library that I'm not directly familiar with however. I know that detouring is surprisingly common. Security software, virus scanners etc all use such frameworks. It's not pretty, but may work:

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(...);
    }
}

These may also help:

How to replace WinAPI functions calls in the MS VC++ project with my own implementation (name and parameters set are the same)?

How can I hook Windows functions in C/C++?

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

蓝眼睛不忧郁 2024-08-19 16:15:06

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

void *foo;

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

然后我来找你说,救命! foo 在第二次分配时没有相同的地址!

我会发疯的,对吧?

在我看来,您已经清楚地了解了为什么这不起作用。任何采用显式地址进行映射的 API 的文档都会让您知道该地址只是一个建议,并且无法得到保证,这是有原因的。这也适用于 POSIX 上的 mmap()

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

甚至 MapViewOfFileEx() 明确建议:

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

根据您的评论进行更新

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

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

  • 尝试移植到 64 位。

Imagine if I came to you with a piece of code like this:

void *foo;

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

Then I came to you and said, help! foo does not have the same address on the second allocation!

I'd be crazy, right?

It seems to me like you've already demonstrated clear knowledge of why this doesn't work. There's a reason that the documention for any API that takes an explicit address to map into lets you know that the address is just a suggestion, and it can't be guaranteed. This also goes for mmap() on POSIX.

I would suggest you write the program in such a way that a change in address doesn't matter. That is, don't store too many pointers to quantities inside the buffer, or if you do, patch them up after reallocation. Similar to the way you'd treat a buffer that you were going to pass into realloc().

Even the documentation for MapViewOfFileEx() explicitly suggests this:

While it is possible to specify an address that is safe now (not used by the operating system), there is no guarantee that the address will remain safe over time. Therefore, it is better to let the operating system choose the address. In this case, you would not store pointers in the memory mapped file, you would store offsets from the base of the file mapping so that the mapping can be used at any address.

Update from your comments

In that case, I suppose you could:

  • Not map into contiguous blocks. Perhaps you could map in chunks and write some intermediate function to decide which to read from/write to?

  • Try porting to 64 bit.

泪意 2024-08-19 16:15:06

正如之前的文章所建议的,您可以在更改内存映射时挂起进程中的每个线程。您可以使用 SuspendThread()/ResumeThread() 来实现。这样做的缺点是您的代码必须了解所有其他线程并保存它们的线程句柄。

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

另请参阅这个问题,它非常相似,但措辞不同:
在 Windows 上自动替换内存映射

As the earlier post suggests, you can suspend every thread in the process while you change the memory mappings. You can use SuspendThread()/ResumeThread() for that. This has the disadvantage that your code has to know about all the other threads and hold thread handles for them.

An alternative is to use the Windows debug API to suspend all threads. If a process has a debugger attached, then every time the process faults, Windows will suspend all of the process's threads until the debugger handles the fault and resumes the process.

Also see this question which is very similar, but phrased differently:
Replacing memory mappings atomically on Windows

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文