Hi there,
i am trying to port over a lock-free ringbuffer which should not require any wrapping code, using a technique called memory mirroring. The idea of this technique is simple: Allocate a block of memory twice the length you need. Release it directly and then map the first block to the first half and map the second block to the second half. This should in “theory” wrap automatically when you write past the first block.
Unfortunatly on win32 this wont work and i have no idea why. I got no error on creating the mirror thing, but when i write past the first block it simply crashes. This is the code i have so far:
typedef struct MirrorMemoryBuffer {
void* basePtr;
HANDLE fileHandle;
uint32_t length;
} MirrorMemoryBuffer;
static bool InitMirrorMemory(MirrorMemoryBuffer* memory, const uint32_t desiredLength) {
if(memory == NULL || desiredLength == 0) return(false);
// Get length in multiple of page-sizes
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
uint32_t roundedSize = (desiredLength + sysInfo.dwPageSize - 1) / sysInfo.dwPageSize * sysInfo.dwPageSize;
uint8_t* blockAddress;
HANDLE fileHandle;
// Keep trying until we get our buffer, needed to handle race conditions
int retries = 3;
while(retries-- > 0) {
// Create mapped file with the length of the buffer
fileHandle = CreateFileMappingA(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, roundedSize, NULL);
if(fileHandle == INVALID_HANDLE_VALUE) {
// Failed, we cannot continue
break;
}
// Reserve two memory of twice the length of the buffer
blockAddress = (uint8_t*)VirtualAlloc(NULL, roundedSize * 2, MEM_RESERVE, PAGE_NOACCESS);
if(blockAddress == NULL) {
// Failed, try again
CloseHandle(fileHandle);
continue;
}
// Release the full range immediately, but retain the address for the re-mapping
VirtualFree(blockAddress, 0, MEM_RELEASE);
// Re-map both buffers to both buffers (these may fail, when the OS already used our memory elsewhere)
if((MapViewOfFileEx(fileHandle, FILE_MAP_ALL_ACCESS, 0, 0, roundedSize, blockAddress) == blockAddress) ||
(MapViewOfFileEx(fileHandle, FILE_MAP_ALL_ACCESS, 0, 0, roundedSize, blockAddress + roundedSize)) == blockAddress + roundedSize) {
// Success, we can use the blockAddress as our base-ptr
break;
}
// Failed cleanup and try again
CloseHandle(fileHandle);
fileHandle = NULL;
blockAddress = NULL;
}
if(blockAddress != NULL) {
// Test memory writing
memset(blockAddress, 1, roundedSize); // full fill
memset(blockAddress + roundedSize - 10, 42, 20); // should wrap
memset(blockAddress + roundedSize, 1337, 40); // should wrap
ZeroMemory(memory, sizeof(*memory));
memory->length = roundedSize;
memory->basePtr = (void*)blockAddress;
memory->fileHandle = fileHandle;
return(true);
} else {
return(false);
}
}
static void CleanupMirrorMemory(MirrorMemoryBuffer* memory) {
if(memory->basePtr != NULL) {
UnmapViewOfFile(memory->basePtr);
UnmapViewOfFile((uint8_t*)memory->basePtr + memory->length);
VirtualFree(memory->basePtr, memory->length * 2, MEM_RELEASE);
}
if(memory->fileHandle != NULL) {
CloseHandle(memory->fileHandle);
}
ZeroMemory(memory, sizeof(*memory));
}
Does someone have a idea why the code does not work? The memory writing at the very end of InitMirrorMemory()
function crashes.
The original idea comes from (https://atastypixel.com/blog/a-simple-fast-circular-buffer-implementation-for-audio-processing/comment-page-1/), but the code there is for MacOS only.