Windows环形缓冲区无需复制

6

所讨论的示例并不消除对memcpy的需求,而是消除了在操作将超出缓冲区分配的末尾时以两个片段执行DMA操作的需要。它仅适用于虚拟地址空间,因为它依赖于将一个页面大小的缓冲区映射到虚拟内存中的两个位置。 - RBerteig
嗯,没错,就是那样。在我的情况下,我正在将指针传递给另一个(库)函数进行写入,因此该函数可以执行自己的memcpy。 - Robert Fraser
3个回答

11

我并没有完全理解维基百科示例的所有细节。考虑到这一点,你可以使用 CreateFileMappingMapViewOfFile 在 Windows 中映射内存,但是 MapViewOfFile 不允许您指定映射的基地址。 可以使用 MapViewOfFileEx 来指定基地址,因此您可以使用类似的技术。

我无法确定这是否真的有效:

// determine valid buffer size
SYSTEM_INFO info;
GetSystemInfo(&info);

// note that the base address must be a multiple of the allocation granularity
DWORD bufferSize=info.dwAllocationGranularity;

HANDLE hMapFile = CreateFileMapping(
             INVALID_HANDLE_VALUE,
             NULL,
             PAGE_READWRITE,
             0,
             bufferSize*2,
             L"Mapping");

BYTE *pBuf = (BYTE*)MapViewOfFile(hMapFile,
                    FILE_MAP_ALL_ACCESS,
                    0,                   
                    0,                   
                    bufferSize);
MapViewOfFileEx(hMapFile,
                    FILE_MAP_ALL_ACCESS,
                    0,                   
                    0,                   
                    bufferSize,
                    pBuf+bufferSize);

1
抱歉,我的最初反应是:我真的没想到那会有效。 - 1800 INFORMATION
1
其实,我认为CreateFileMapping只需要分配bufferSize的空间,而不是bufferSize*2...反正我没有收到访问冲突的错误。;-P - Robert Fraser

5

嘿,这是最近一直困扰我的话题。我需要在Windows上使用posix优化的环形缓冲区,主要是因为其随机访问接口,但我从来没有想过如何实现它。现在,@1800 INFORMATION提出的代码有时有效,有时无效,但该想法仍然很好。

问题在于MapViewOfFileEx有时会失败并显示ERROR_INVALID_ADDRESS,这意味着它无法将视图映射到pBuf+bufferSize。这是因为之前调用的MapViewOfFile选择了一个长度为bufferSize的自由地址空间(从pBuf开始),但它并不保证这个地址空间也有bufferSize*2那么长。我们为什么需要bufferSize*2虚拟内存?因为我们的环形缓冲区需要回绕。这就是第二个映射视图的作用。当读写指针离开第一个视图时,它进入第二个视图(因为它们在内存中是连续的),但实际上它重新从同一个映射开始。

UINT_PTR addr;
HANDLE hMapFile;
LPVOID address, address2;

hMapFile = CreateFileMapping (    // create a mapping backed by a pagefile
    INVALID_HANDLE_VALUE,
    NULL,
    PAGE_EXECUTE_READWRITE,
    0,
    bufferSize*2,
    "Local\\mapping" );
if(hMapFile == NULL) 
    FAIL(CreateFileMapping);

address = MapViewOfFile (    // find a free bufferSize*2 address space
    hMapFile,
    FILE_MAP_ALL_ACCESS,
    0,                   
    0,                   
    bufferSize*2 );
if(address==NULL) 
    FAIL(MapViewOfFile);
UnmapViewOfFile(address);
// found it. hopefully it'll remain free while we map to it

addr = ((UINT_PTR)address);
address = MapViewOfFileEx (
    hMapFile,
    FILE_MAP_ALL_ACCESS,
    0,                   
    0,                   
    bufferSize, 
    (LPVOID)addr );

addr = ((UINT_PTR)address) + bufferSize;        
address2 = MapViewOfFileEx (
    hMapFile,
    FILE_MAP_ALL_ACCESS,
    0,                   
    0,                   
    bufferSize,
    (LPVOID)addr);  

if(address2==NULL)      
    FAIL(MapViewOfFileEx);

// when you're done with your ring buffer, call UnmapViewOfFile for 
// address and address2 and CloseHandle(hMapFile)

3
“it doesn't guarantee this address space to be bufferSize*2 long”可以通过使用带有“MEM_RESERVE”标志的VirtualAlloc来修复,然后使用两个调用MapViewOfFileExVirtualAlloc找到的单个地址范围的两个半部分传递进去。 - Ben Voigt

2
从Windows 10开始,您可以使用VirtualAlloc2和MapViewOfFile3创建循环缓冲区,如VirtualAlloc2 MSDN页面上的示例部分所示。
与Ben Voigt的评论相反,VirtualAlloc不能用于为MapViewOfFile保留内存。根据MSDN
“在用于映射的区域中不能进行任何其他内存分配,包括使用VirtualAlloc或VirtualAllocEx函数来保留内存。”
在旧版本的Windows上,可以通过使用VirtualAlloc来保留虚拟内存区域,然后使用VirtualFree释放它,并尝试在新释放的区域中创建映射作为一种不太理想的解决方法。显然,这种方法容易受到其他线程尝试分配虚拟内存的竞争影响。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接