diff --git a/build.zig b/build.zig
index 2f5e0f7..2c33f9c 100644
--- a/build.zig
+++ b/build.zig
@@ -7,8 +7,12 @@ pub fn build(b: *std.Build) void {
 
     const optimize = b.standardOptimizeOption(.{});
 
-    core.build(b, build_step, optimize);
-    system.build(b, build_step, optimize);
+    const system_module = b.addModule("system", .{
+        .root_source_file = b.path("system/lib/system.zig"),
+    });
+
+    core.buildAsSubmodule(b, build_step, optimize, system_module);
+    system.buildAsSubmodule(b, build_step, optimize, system_module);
 
     b.default_step = build_step;
 }
diff --git a/core/build.zig b/core/build.zig
index 5acde36..2b05c35 100644
--- a/core/build.zig
+++ b/core/build.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 const here = "core";
 
-pub fn build(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
+pub fn buildAsSubmodule(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.OptimizeMode, system_module: *std.Build.Module) void {
     var disabled_features = std.Target.Cpu.Feature.Set.empty;
     var enabled_features = std.Target.Cpu.Feature.Set.empty;
 
@@ -38,6 +38,8 @@ pub fn build(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.O
         },
     });
 
+    core.root_module.addImport("system", system_module);
+
     var kernel_step = b.step("core", "Build the core microkernel");
     kernel_step.dependOn(&core.step);
     kernel_step.dependOn(&install.step);
diff --git a/system/build.zig b/system/build.zig
index 04851d0..49dbeaf 100644
--- a/system/build.zig
+++ b/system/build.zig
@@ -1,9 +1,9 @@
 const std = @import("std");
 const init = @import("init/build.zig");
 
-pub fn build(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
+pub fn buildAsSubmodule(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.OptimizeMode, system_module: *std.Build.Module) void {
     const system_step = b.step("system", "Build core system services");
-    init.build(b, system_step, optimize);
+    init.buildAsSubmodule(b, system_step, optimize, system_module);
 
     build_step.dependOn(system_step);
 }
diff --git a/system/init/build.zig b/system/init/build.zig
index 2938f19..3679ff3 100644
--- a/system/init/build.zig
+++ b/system/init/build.zig
@@ -2,7 +2,7 @@ const std = @import("std");
 
 const here = "system/init";
 
-pub fn build(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
+pub fn buildAsSubmodule(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.OptimizeMode, system_module: *std.Build.Module) void {
     var disabled_features = std.Target.Cpu.Feature.Set.empty;
     var enabled_features = std.Target.Cpu.Feature.Set.empty;
 
@@ -29,6 +29,8 @@ pub fn build(b: *std.Build, build_step: *std.Build.Step, optimize: std.builtin.O
         .code_model = .default,
     });
 
+    init.root_module.addImport("system", system_module);
+
     const install = b.addInstallArtifact(init, .{
         .dest_dir = .{
             .override = .{ .custom = "boot/" },
diff --git a/system/lib/ring_buffer.zig b/system/lib/ring_buffer.zig
new file mode 100644
index 0000000..b630566
--- /dev/null
+++ b/system/lib/ring_buffer.zig
@@ -0,0 +1,130 @@
+//! Single-producer single-consumer lockfree ring buffer, which supports reading and storing arbitrary amounts of bytes,
+//! and which supports being stored in shared memory, for usage in IPC.
+
+const std = @import("std");
+
+pub const RingBuffer = struct {
+    const Data = packed struct {
+        read_index: u16,
+        write_index: u16,
+        data_start: u8,
+    };
+
+    capacity: u16,
+    data: *Data,
+
+    pub fn init(buffer: [*]u8, length: u16, initialize: bool) RingBuffer {
+        const data: *Data = @alignCast(@ptrCast(buffer));
+
+        if (initialize) {
+            data.read_index = 0;
+            data.write_index = 0;
+        }
+
+        const capacity = length - (@sizeOf(@TypeOf(data.read_index)) + @sizeOf(@TypeOf(data.write_index)));
+
+        return .{ .capacity = capacity, .data = data };
+    }
+
+    pub fn write(self: *RingBuffer, data: [*]const u8, length: usize) bool {
+        const available = self.bytesAvailableToWrite();
+        var tail = @atomicLoad(u16, &self.data.write_index, .monotonic);
+
+        const buffer = self.dataPointer();
+
+        const bytes_to_write = length;
+        if (bytes_to_write == 0) return false;
+        if (bytes_to_write > available) return false;
+        if (self.capacity <= tail) return false;
+
+        var written: usize = 0;
+
+        // Write first segment: from tail up to the end of the buffer.
+        const first_chunk = @min(bytes_to_write, self.capacity - tail);
+        @memcpy(buffer[tail .. tail + first_chunk], data[0..first_chunk]);
+        written += first_chunk;
+        tail = (tail + first_chunk) % self.capacity;
+
+        // Write second segment if needed (wrap-around).
+        if (written < bytes_to_write) {
+            const second_chunk = bytes_to_write - written;
+            @memcpy(buffer[0..second_chunk], data[first_chunk .. first_chunk + second_chunk]);
+            tail = @intCast(second_chunk);
+            written += second_chunk;
+        }
+
+        @atomicStore(u16, &self.data.write_index, tail, .release);
+        return true;
+    }
+
+    fn read_impl(self: *RingBuffer, data: [*]u8, length: usize) ?u16 {
+        const available = self.bytesAvailableToRead();
+        var head = @atomicLoad(u16, &self.data.read_index, .monotonic);
+
+        const buffer = self.dataPointer();
+
+        const bytes_to_read = length;
+        if (bytes_to_read == 0) return null;
+        if (bytes_to_read > available) return null;
+        if (self.capacity <= head) return null;
+
+        var bytes_read: usize = 0;
+
+        // Read first segment: from head up to the end of the buffer.
+        const first_chunk = @min(bytes_to_read, self.capacity - head);
+        @memcpy(data[0..first_chunk], buffer[head .. head + first_chunk]);
+        bytes_read += first_chunk;
+        head = (head + first_chunk) % self.capacity;
+
+        // Read second segment if needed (wrap-around).
+        if (bytes_read < bytes_to_read) {
+            const second_chunk = bytes_to_read - bytes_read;
+            @memcpy(data[first_chunk .. first_chunk + second_chunk], buffer[0..second_chunk]);
+            head = @intCast(second_chunk);
+            bytes_read += second_chunk;
+        }
+
+        return head;
+    }
+
+    pub fn peek(self: *RingBuffer, data: [*]u8, length: usize) bool {
+        return switch (self.read_impl(data, length)) {
+            null => false,
+            else => true,
+        };
+    }
+
+    pub fn read(self: *RingBuffer, data: [*]u8, length: usize) bool {
+        const result = self.read_impl(data, length) orelse return false;
+
+        @atomicStore(u16, &self.data.read_index, result, .release);
+
+        return true;
+    }
+
+    fn dataPointer(self: *RingBuffer) [*]u8 {
+        return @ptrCast(&self.data.data_start);
+    }
+
+    fn bytesAvailableToWrite(self: *RingBuffer) u16 {
+        const head = @atomicLoad(u16, &self.data.read_index, .acquire);
+        const tail = @atomicLoad(u16, &self.data.write_index, .monotonic);
+        if (head >= self.capacity or tail >= self.capacity) return 0; // Who tampered with the indices??
+        if (tail >= head) {
+            return self.capacity - (tail - head) - 1;
+        } else {
+            return head - tail - 1;
+        }
+    }
+
+    fn bytesAvailableToRead(self: *RingBuffer) u16 {
+        const head = @atomicLoad(u16, &self.data.read_index, .monotonic);
+        const tail = @atomicLoad(u16, &self.data.write_index, .acquire);
+        if (head >= self.capacity or tail >= self.capacity) return 0; // Who tampered with the indices??
+        if (tail >= head) {
+            return tail - head;
+        } else {
+            return self.capacity - (head - tail);
+        }
+    }
+};
diff --git a/system/lib/system.zig b/system/lib/system.zig
new file mode 100644
index 0000000..0a7ccf8
--- /dev/null
+++ b/system/lib/system.zig
@@ -0,0 +1 @@
+pub const ring_buffer = @import("ring_buffer.zig");