From 4d8c3856d4b9cb4dfdf05638cbbfd0e8f7601fb5 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sat, 22 Feb 2025 15:37:22 +0100 Subject: [PATCH] core+system: Add a new "system library" with a shared memory ring buffer implementation --- build.zig | 8 ++- core/build.zig | 4 +- system/build.zig | 4 +- system/init/build.zig | 4 +- system/lib/ring_buffer.zig | 130 +++++++++++++++++++++++++++++++++++++ system/lib/system.zig | 1 + 6 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 system/lib/ring_buffer.zig create mode 100644 system/lib/system.zig 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");