To expose POSIX shared memory to JavaScript for lock-free Atomics operations,
you must bypass the standard C-level N-API and drop directly into the native
V8 C++ API.
The JavaScript Atomics engine strictly type-checks for a SharedArrayBuffer.
N-API's external buffer functions currently yield standard ArrayBuffers, which
will cause Atomics.store() to throw a TypeError. V8 provides the
NewBackingStore method to wrap a raw mmap pointer with a custom lifecycle
hook.
Here are the three files required to build this bridge.
1. The V8 C++ Addon (mesh_shm.cc)
This addon handles the POSIX shm_open, maps the memory, and wires the kernel's
munmap to the V8 Garbage Collector so memory doesn't leak when the buffer goes
out of scope.
#include <node.h> #include <v8.h> #include <sys/mman.h> #include <sys/stat.h>
#include <fcntl.h> #include <unistd.h> using namespace v8; // Triggered
automatically by the V8 Garbage Collector void UnmapSharedMemory(void* data,
size_t length, void* deleter_data) { munmap(data, length); } void
OpenSHM(const FunctionCallbackInfo<Value>& args) { Isolate* isolate =
args.GetIsolate(); Local<Context> context = isolate->GetCurrentContext(); if
(args.Length() < 2 || !args[0]->IsString() || !args[1]->IsNumber()) {
isolate->ThrowException(Exception::TypeError( String::NewFromUtf8(isolate,
"Expected: (name: string, size: number)").ToLocalChecked())); return; }
String::Utf8Value shmName(isolate, args[0]); size_t size = args[1]->IntegerValu
e(context).FromMaybe(0); // Open POSIX shared memory object int fd =
shm_open(*shmName, O_CREAT | O_RDWR, 0666); if (fd == -1) { isolate->ThrowExcep
tion(Exception::Error( String::NewFromUtf8(isolate, "Failed to
shm_open").ToLocalChecked())); return; } // Allocate the physical memory pages
if (ftruncate(fd, size) == -1) { close(fd); isolate->ThrowException(Exception::
Error( String::NewFromUtf8(isolate, "Failed to ftruncate").ToLocalChecked()));
return; } // Map into the Node.js process address space void* mapped =
mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); //
File descriptor is no longer needed after mmap if (mapped == MAP_FAILED) {
isolate->ThrowException(Exception::Error( String::NewFromUtf8(isolate, "Failed
to mmap").ToLocalChecked())); return; } // Wrap the raw pointer in a V8
SharedArrayBuffer BackingStore std::shared_ptr<BackingStore> backing =
SharedArrayBuffer::NewBackingStore( mapped, size, UnmapSharedMemory, nullptr
); // Yield the SharedArrayBuffer to JavaScript Local<SharedArrayBuffer> sab =
SharedArrayBuffer::New(isolate, backing); args.GetReturnValue().Set(sab); }
void Initialize(Local<Object> exports) { NODE_SET_METHOD(exports, "open",
OpenSHM); } NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
2. The Build Configuration (binding.gyp)
Because this utilizes POSIX real-time extensions for shm_open, you must
explicitly link the rt library on Linux environments.
{ "targets": [ { "target_name": "mesh_shm", "sources": [ "mesh_shm.cc" ],
"libraries": [ "-lrt" ] } ] }
Compile the addon by running node-gyp rebuild in the same directory.
3. The Node.js Orchestrator Implementation
Once compiled, you can require the native module in your Node.js microkernel,
open the POSIX segment, and cast standard Int32Array views over the control
headers to communicate with the C daemon.
const meshShm = require('./build/Release/mesh_shm.node'); // 1. Allocate a
16MB Shared Memory segment named "/genesis_mem" const TOTAL_SIZE = 16 * 1024 *
1024; const sab = meshShm.open('/genesis_mem', TOTAL_SIZE); // 2. Cast an
Int32Array view over the Control Header (first 64 bytes) const ctrlView = new
Int32Array(sab, 0, 16); // Indices corresponding to the C control_header_t
struct const IDX_HEAD = 0; const IDX_TAIL = 1; const IDX_RUNNER_ACTIVE = 2;
const IDX_TICK_FREQ_MS = 3; const IDX_TICK_COUNTER = 4; // 3. Initialize the
control state Atomics.store(ctrlView, IDX_RUNNER_ACTIVE, 0); // Start paused
Atomics.store(ctrlView, IDX_TICK_FREQ_MS, 250); // 4 ticks per second
console.log(`[IPC] Shared Memory initialized. Runner active: ${Atomics.load(ctr
lView, IDX_RUNNER_ACTIVE)}`); // Example: Function to wake the C Coprocessor
via Atomics function startSimulation() { Atomics.store(ctrlView,
IDX_RUNNER_ACTIVE, 1); console.log("[IPC] C Coprocessor awakened."); }