-
-
Save nickva/7277351b066b5bba35f3 to your computer and use it in GitHub Desktop.
The magic ring buffer.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define _CRT_SECURE_NO_DEPRECATE | |
#include <stdio.h> | |
#include <string.h> | |
#include <Windows.h> | |
// This allocates a "magic ring buffer" that is mapped twice, with the two | |
// copies being contiguous in (virtual) memory. The advantage of this is | |
// that this allows any function that expects data to be contiguous in | |
// memory to read from (or write to) such a buffer. It also means that | |
// block reads/writes never need to be split into two halves, and makes | |
// wraparound handling quite cheap. | |
// | |
// The flipside is that allocating such a beast is a bit dicey (see | |
// comments below) and subject to various restrictions (e.g. on Windows, | |
// the size of such a buffer must be a multiple of 64k). So the usual | |
// disclaimer applies: code responsibly, with great power comes great | |
// responsibility, and when you use this code to shoot yourself in the | |
// foot it might blow off your face instead (what with the wraparound | |
// and all). | |
class MagicRingBuffer | |
{ | |
HANDLE mapping; | |
size_t size; | |
char *baseptr; | |
public: | |
MagicRingBuffer() | |
: mapping(0), size(0), baseptr(0) | |
{ | |
} | |
~MagicRingBuffer() | |
{ | |
free(); | |
} | |
// Allocate a magic ring buffer at a given target address. | |
// ring_size size of one copy of the ring; must be a multiple of 64k. | |
// desired_addr location where you'd like it. | |
void *alloc_at(size_t ring_size, void *desired_addr=0) | |
{ | |
// if we already hold one allocation, refuse to make another. | |
if (baseptr) | |
return 0; | |
// is ring_size a multiple of 64k? if not, this won't ever work! | |
if ((ring_size & 0xffff) != 0) | |
return 0; | |
// try to allocate and map our space | |
size_t alloc_size = ring_size * 2; | |
if (!(mapping = CreateFileMappingA(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, (unsigned long long)alloc_size >> 32, alloc_size & 0xffffffffu, 0)) || | |
!(baseptr = (char *)MapViewOfFileEx(mapping, FILE_MAP_ALL_ACCESS, 0, 0, ring_size, desired_addr)) || | |
!MapViewOfFileEx(mapping, FILE_MAP_ALL_ACCESS, 0, 0, ring_size, (char *)desired_addr + ring_size)) | |
{ | |
// something went wrong - clean up | |
free(); | |
} | |
else // success! | |
size = ring_size; | |
return baseptr; | |
} | |
// This function will allocate a magic ring buffer at a system-determined base address. | |
// | |
// Sadly, there's no way (that I can see) in the Win32 API to first reserve | |
// a memory region then fill it in using mmaps; you can reserve memory via | |
// VirtualAlloc, but that address range can then only be used to commit | |
// memory via another VirtualAlloc, and can not be mmap'ed. Furthermore, | |
// there's also no way to do the two back-to-back mmaps atomically. What we | |
// do here is to reserve enough memory via VirtualAlloc, then immediately | |
// free it and try to put our allocation there. This is subject to a race | |
// condition - another thread might end up allocating that very memory | |
// region in the interim. What this means is that even when an alloc should | |
// work (i.e. there's enough memory available) it can still fail spuriously | |
// sometimes. Hence the "dicey" comment above. | |
// | |
// What we do here is just retry the alloc a given number of times and hope | |
// that we don't get screwed every single time. This increases the | |
// likelihood of success, but doesn't eliminate the chance of spurious | |
// failure, so be religious about checking return values! | |
void *alloc(size_t ring_size, int num_retries=5) | |
{ | |
void *ptr = 0; | |
while (!ptr && num_retries-- != 0) | |
{ | |
void *target_addr = determine_viable_addr(ring_size * 2); | |
if (target_addr) | |
ptr = alloc_at(ring_size, target_addr); | |
} | |
return ptr; | |
} | |
// Frees the allocated region again. | |
void free() | |
{ | |
if (baseptr) | |
{ | |
UnmapViewOfFile(baseptr); | |
UnmapViewOfFile(baseptr + size); | |
baseptr = 0; | |
} | |
if (mapping) | |
{ | |
CloseHandle(mapping); | |
mapping = 0; | |
} | |
size = 0; | |
} | |
private: | |
// Determine a viable target address of "size" memory mapped bytes by | |
// allocating memory using VirtualAlloc and immediately freeing it. This | |
// is subject to a potential race condition, see notes above. | |
static void *determine_viable_addr(size_t size) | |
{ | |
void *ptr = VirtualAlloc(0, size, MEM_RESERVE, PAGE_NOACCESS); | |
if (!ptr) | |
return 0; | |
VirtualFree(ptr, 0, MEM_RELEASE); | |
return ptr; | |
} | |
}; | |
int main() | |
{ | |
static const int ringsize = 64*1024; | |
MagicRingBuffer mrb; | |
char *buf = (char *)mrb.alloc(ringsize); | |
if (!buf) | |
{ | |
printf("MRB allocation failed!\n"); | |
return 0; | |
} | |
// Okay, now get ready for a magic trick! | |
memset(buf, 0, ringsize * 2); // clear it all to zeroes | |
strcpy(buf + ringsize - 3, "Hello world!"); | |
printf("%s\n", buf + ringsize - 3); | |
printf("%s\n", buf); | |
// nothing up my sleeve! | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment