Last active
November 8, 2024 13:34
-
-
Save mmozeiko/3b09a340f3c53e5eaed699a1aea95250 to your computer and use it in GitHub Desktop.
Magic RingBuffer for Windows, Linux and macOS
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
#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