Skip to content

Instantly share code, notes, and snippets.

@rygorous
Created July 22, 2012 03:55
Show Gist options
  • Star 67 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save rygorous/3158316 to your computer and use it in GitHub Desktop.
Save rygorous/3158316 to your computer and use it in GitHub Desktop.
The magic ring buffer.
#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