Skip to content

Instantly share code, notes, and snippets.

@mmozeiko
Last active November 8, 2024 13:34
Show Gist options
  • Save mmozeiko/3b09a340f3c53e5eaed699a1aea95250 to your computer and use it in GitHub Desktop.
Save mmozeiko/3b09a340f3c53e5eaed699a1aea95250 to your computer and use it in GitHub Desktop.
Magic RingBuffer for Windows, Linux and macOS
#pragma once
//
// Magic Ring Buffer
// https://gist.github.com/mmozeiko/3b09a340f3c53e5eaed699a1aea95250
//
// Sets up memory mapping so the same memory block follows itself in virtual address space:
//
// [abcd...xyz][abc...xyz]
//
// This allows reading or writing memory to this ringbuffer without worries of accessing
// memory past the end of buffer. Normally you would need to split such reads or writes
// into two read/write operations. But because memory is duplicated in virtual address
// space, the buffer reads/writes can always happen on sequential bytes in one operation.
//
// Typical usage:
//
// MagicRG Buffer;
// MRB_Init(&Buffer, 4096); // buffer will have space for at least 4096 bytes
//
// size_t WriteAvailable = MRB_GetFree(&Buffer); // how many bytes available to write
// void* WritePtr = MRB_WritePtr(&Buffer); // get pointer to write to
// ... write up to WriteAvailable bytes to WritePtr
// MRB_WriteEnd(&Buffer, WriteSize); // finish writing, now reader is allowed
// // to read WriteSize amount of bytes
//
// size_t ReadAvailable = MRB_GetUsed(&Buffer); // how many bytes available to read
// void* ReadPtr = MRB_ReadPtr(&Buffer); // get pointer to read from
// ... read up to ReadAvailable bytes from ReadPtr
// MRB_ReadEnd(&Buffer, ReadSize); // finish reading, now writer is allowed
// // to write extra ReadSize amount of bytes
//
// Basically writer is allowed to write up to "free" amount of bytes.
// And reader is allowed to read up to "used" amount of bytes.
// It's up to you to check how many free or used bytes are in buffer before reading and writing.
//
#include <stddef.h>
#include <stdbool.h>
//
// interface
//
typedef struct {
void* Data;
size_t Size;
size_t Read;
size_t Write;
} MagicRB;
// allocation & freeing, size will be rounded up to multiple of page size
static inline void MRB_Init(MagicRB* RingBuffer, size_t Size);
static inline void MRB_Done(MagicRB* RingBuffer);
// size queries:
// used = how many bytes are available for reading
// free = how many bytes are available for writing
static inline size_t MRB_GetUsed(const MagicRB* RingBuffer);
static inline size_t MRB_GetFree(const MagicRB* RingBuffer);
// check if buffer is empty or full
static inline bool MRB_IsEmpty(const MagicRB* RingBuffer);
static inline bool MRB_IsFull(const MagicRB* RingBuffer);
// reading, must not read more than "used" amount of bytes
static inline void* MRB_ReadPtr(MagicRB* RingBuffer);
static inline void MRB_ReadEnd(MagicRB* RingBuffer, size_t ReadSize);
// writing, must not write more than "free" amount of bytes
static inline void* MRB_WritePtr(MagicRB* RingBuffer);
static inline void MRB_WriteEnd(MagicRB* RingBuffer, size_t WriteSize);
//
// implementation
//
#if defined(_WIN32)
# include <windows.h>
# pragma comment (lib, "onecore")
#elif defined(__linux__)
# include <sys/syscall.h>
# include <sys/mman.h>
# include <unistd.h>
# include <fcntl.h>
#elif defined(__APPLE__)
# include <mach/mach.h>
# include <mach/mach_vm.h>
# include <mach/vm_map.h>
# include <mach/vm_page_size.h>
#else
# error target not supported
#endif
#if !defined(MRB_ASSERT)
# if defined(_MSC_VER)
# if !defined(NDEBUG)
# include <intrin.h>
# define MRB_ASSERT(cond) do { if (!(cond)) __debugbreak(); } while (0)
# else
# define MRB_ASSERT(cond) do { (void)sizeof(cond); } while (0)
# endif
# else
# include <assert.h>
# define MRB_ASSERT(cond) assert(cond)
# endif
#endif
void MRB_Init(MagicRB* RingBuffer, size_t Size)
{
MRB_ASSERT(Size != 0 && Size <= ~((size_t)0) >> 2);
#if defined(_WIN32)
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);
const size_t PageSize = SystemInfo.dwPageSize;
Size = (Size + PageSize - 1) & ~(PageSize - 1);
char* Placeholder1 = (char*)VirtualAlloc2(NULL, NULL, 2 * Size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, NULL, 0);
char* Placeholder2 = (char*)Placeholder1 + Size;
MRB_ASSERT(Placeholder1 && "failed to reserve placeholder");
BOOL Ok = VirtualFree(Placeholder1, Size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER);
MRB_ASSERT(Ok && "failed to split reservation into two placeholders");
HANDLE Section = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, (DWORD)(Size >> 32), (DWORD)Size, NULL);
MRB_ASSERT(Section && "failed to create mapping");
void* View1 = MapViewOfFile3(Section, NULL, Placeholder1, 0, Size, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, NULL, 0);
MRB_ASSERT(View1 && "failed to map first half of mapping");
void* View2 = MapViewOfFile3(Section, NULL, Placeholder2, 0, Size, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, NULL, 0);
MRB_ASSERT(View2 && "failed to map second half of mapping");
CloseHandle(Section);
VirtualFree(Placeholder1, 0, MEM_RELEASE);
VirtualFree(Placeholder2, 0, MEM_RELEASE);
RingBuffer->Data = View1;
#elif defined(__linux__)
const size_t PageSize = sysconf(_SC_PAGESIZE);
Size = (Size + PageSize - 1) & ~(PageSize - 1);
int MemFd = syscall(__NR_memfd_create, "MagicRingBuffer", FD_CLOEXEC);
MRB_ASSERT(MemFd > 0 && "failed to create memfd");
int Ok = ftruncate(MemFd, Size);
MRB_ASSERT(Ok == 0 && "failed to set memfd size");
char* Base = (char*)mmap(NULL, 2 * Size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
MRB_ASSERT(Base != MAP_FAILED && "faioled to create memory mapping");
void* Mapped1 = mmap(Base + 0 * Size, Size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, MemFd, 0);
MRB_ASSERT(Mapped1 != MAP_FAILED && "failed to map first half of mapping");
void* Mapped2 = mmap(Base + 1 * Size, Size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, MemFd, 0);
MRB_ASSERT(Mapped2 != MAP_FAILED && "failed to map second half of mapping");
close(MemFd);
RingBuffer->Data = Base;
#elif defined(__APPLE__)
Size = mach_vm_round_page(Size);
mach_port_t Task = mach_task_self();
mach_vm_address_t Address;
int AllocateOk = mach_vm_allocate(Task, &Address, 2 * Size, VM_FLAGS_ANYWHERE);
MRB_ASSERT(AllocateOk == 0 && "failed to allocate memory");
int Mapping1 = mach_vm_allocate(Task, &Address, Size, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE);
MRB_ASSERT(Mapping1 == 0 && "failed to map first half of mapping");
const vm_prot_t PageProtection = VM_PROT_READ | VM_PROT_WRITE;
mach_port_t MemoryPort;
mach_vm_size_t MemorySize = Size;
int PortOk = mach_make_memory_entry_64(Task, &MemorySize, Address, PageProtection, &MemoryPort, MACH_PORT_NULL);
MRB_ASSERT(PortOk == 0 && "failed to create mach port");
mach_vm_address_t Address2 = Address + Size;
int Mapping2 = mach_vm_map(Task, &Address2, Size, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, MemoryPort, 0, FALSE, PageProtection, PageProtection, VM_INHERIT_NONE);
MRB_ASSERT(Mapping2 == 0 && "failed to map second half of mapping");
mach_port_deallocate(Task, MemoryPort);
RingBuffer->Data = (void*)Address;
#endif
RingBuffer->Size = Size;
RingBuffer->Read = 0;
RingBuffer->Write = 0;
}
void MRB_Done(MagicRB* RingBuffer)
{
#if defined(_WIN32)
UnmapViewOfFileEx((char*)RingBuffer->Data, 0);
UnmapViewOfFileEx((char*)RingBuffer->Data + RingBuffer->Size, 0);
#elif defined(__linux__)
munmap(RingBuffer->Data, 2 * RingBuffer->Size);
#elif defined(__APPLE__)
mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)RingBuffer->Data, 2 * RingBuffer->Size);
#endif
}
size_t MRB_GetUsed(const MagicRB* RingBuffer)
{
return RingBuffer->Write - RingBuffer->Read;
}
size_t MRB_GetFree(const MagicRB* RingBuffer)
{
return RingBuffer->Size - MRB_GetUsed(RingBuffer);
}
bool MRB_IsEmpty(const MagicRB* RingBuffer)
{
return MRB_GetUsed(RingBuffer) == 0;
}
bool MRB_IsFull(const MagicRB* RingBuffer)
{
return MRB_GetFree(RingBuffer) == 0;
}
void* MRB_ReadPtr(MagicRB* RingBuffer)
{
MRB_ASSERT(MRB_GetUsed(RingBuffer) != 0);
size_t Offset = RingBuffer->Read & (RingBuffer->Size - 1);
return (char*)RingBuffer->Data + Offset;
}
void MRB_ReadEnd(MagicRB* RingBuffer, size_t ReadSize)
{
MRB_ASSERT(ReadSize <= MRB_GetUsed(RingBuffer));
RingBuffer->Read += ReadSize;
}
void* MRB_WritePtr(MagicRB* RingBuffer)
{
MRB_ASSERT(MRB_GetFree(RingBuffer) != 0);
size_t Offset = RingBuffer->Write & (RingBuffer->Size - 1);
return (char*)RingBuffer->Data + Offset;
}
void MRB_WriteEnd(MagicRB* RingBuffer, size_t WriteSize)
{
MRB_ASSERT(WriteSize <= MRB_GetFree(RingBuffer));
RingBuffer->Write += WriteSize;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment