aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile13
-rw-r--r--boot/grub/grub.cfg2
-rw-r--r--build.zig2
-rw-r--r--mirai/common/constants/invocations.zig1
-rw-r--r--mirai/drivers/serial/serial.zig6
-rw-r--r--mirai/hikari/loader.zig2
-rw-r--r--mirai/invocations/handler.zig2
-rw-r--r--mirai/invocations/kata/reap.zig62
-rw-r--r--mirai/invocations/kata/spawn.zig102
-rw-r--r--mirai/invocations/kata/wait.zig3
-rw-r--r--mirai/invocations/kata/yield.zig22
-rw-r--r--mirai/kata/memory.zig68
-rw-r--r--mirai/kata/pool.zig23
-rw-r--r--mirai/kata/sensei/sensei.zig1
-rw-r--r--mirai/kata/sensei/waker.zig3
-rw-r--r--mirai/kata/shift.zig12
-rw-r--r--mirai/kata/types.zig6
-rw-r--r--mirai/memory/paging.zig107
-rw-r--r--mirai/memory/pmm.zig53
-rw-r--r--resources/system/akiba.world17
-rw-r--r--system/libraries/format/format.zig7
-rw-r--r--system/libraries/kata/kata.zig5
-rw-r--r--system/libraries/sys/sys.zig4
-rw-r--r--system/pulse/pulse.zig10
-rw-r--r--system/shinigami/shinigami.zig17
-rw-r--r--system/shinigami/shinigami.zon9
26 files changed, 491 insertions, 68 deletions
diff --git a/Makefile b/Makefile
index 6a26c04..bee9f5a 100644
--- a/Makefile
+++ b/Makefile
@@ -146,7 +146,7 @@ prepare-filesystem: build-grub
@echo "→ Building kernel..."
@zig build --cache-dir $(BUILD_DIR)/cache --prefix $(BUILD_DIR)
- @mv $(BUILD_DIR)/bin/mirai.akibakernel $(FS_ROOT)/system/akiba/
+ @mv $(BUILD_DIR)/bin/mirai.kernel $(FS_ROOT)/system/akiba/
@echo "→ Building libraries..."
@mkdir -p $(BUILD_DIR)/lib $(FS_ROOT)/system/libraries
@@ -182,15 +182,12 @@ prepare-filesystem: build-grub
echo " Compiling $$sysname..."; \
$(BUILD_DIR)/bin/akibacompile "$$sysdir" "$(BUILD_DIR)/system/$$sysname" "system/libraries" && \
if [ "$$sysname" = "pulse" ]; then \
- echo " Creating pulse.akibainit..."; \
- $(BUILD_DIR)/bin/akibabuilder "$(BUILD_DIR)/system/$$sysname" "$(FS_ROOT)/system/akiba/pulse.akibainit" init && \
- echo " ✓ pulse.akibainit"; \
+ $(BUILD_DIR)/bin/akibabuilder "$(BUILD_DIR)/system/$$sysname" "$(FS_ROOT)/system/akiba/pulse.gen" init; \
else \
- echo " Wrapping $$sysname.akiba..."; \
mkdir -p "$(FS_ROOT)/system/$$sysname" && \
- $(BUILD_DIR)/bin/akibabuilder "$(BUILD_DIR)/system/$$sysname" "$(FS_ROOT)/system/$$sysname/$$sysname.akiba" cli && \
- echo " ✓ system/$$sysname/$$sysname.akiba"; \
- fi; \
+ $(BUILD_DIR)/bin/akibabuilder "$(BUILD_DIR)/system/$$sysname" "$(FS_ROOT)/system/$$sysname/$$sysname.gen" cli; \
+ fi && \
+ echo " ✓ $$sysname.gen"; \
fi; \
fi; \
done
diff --git a/boot/grub/grub.cfg b/boot/grub/grub.cfg
index 8c4291f..3540af0 100644
--- a/boot/grub/grub.cfg
+++ b/boot/grub/grub.cfg
@@ -25,6 +25,6 @@ set root='hd0,gpt2'
menuentry "Akiba" {
echo "Loading kernel from AFS partition..."
- multiboot2 /system/akiba/mirai.akibakernel
+ multiboot2 /system/akiba/mirai.kernel
boot
} \ No newline at end of file
diff --git a/build.zig b/build.zig
index af96a41..d90a774 100644
--- a/build.zig
+++ b/build.zig
@@ -15,7 +15,7 @@ pub fn build(b: *std.Build) void {
});
const kernel = b.addExecutable(.{
- .name = "mirai.akibakernel",
+ .name = "mirai.kernel",
.root_module = mirai_module,
});
diff --git a/mirai/common/constants/invocations.zig b/mirai/common/constants/invocations.zig
index 775d39e..afeb9d4 100644
--- a/mirai/common/constants/invocations.zig
+++ b/mirai/common/constants/invocations.zig
@@ -19,6 +19,7 @@ pub const MEMINFO: u64 = 0x10;
pub const UPTIME: u64 = 0x11;
pub const GETTIME: u64 = 0x12;
pub const DISKINFO: u64 = 0x13;
+pub const REAP: u64 = 0x14;
pub const ERROR: u64 = @as(u64, @bitCast(@as(i64, -1)));
pub const NO_DATA: u64 = @as(u64, @bitCast(@as(i64, -2)));
diff --git a/mirai/drivers/serial/serial.zig b/mirai/drivers/serial/serial.zig
index 07d3af7..b793c1a 100644
--- a/mirai/drivers/serial/serial.zig
+++ b/mirai/drivers/serial/serial.zig
@@ -119,9 +119,9 @@ fn print_arg_string(arg: anytype) void {
const child_info = @typeInfo(child);
if (child == u8) {
// [*]const u8 - null-terminated string pointer
- var p = arg;
- while (p[0] != 0) : (p += 1) {
- write(p[0]);
+ var i: usize = 0;
+ while (arg[i] != 0) : (i += 1) {
+ write(arg[i]);
}
} else if (child_info == .array and child_info.array.child == u8) {
// *const [N]u8 or *const [N:0]u8 - pointer to string literal
diff --git a/mirai/hikari/loader.zig b/mirai/hikari/loader.zig
index 2d33672..a09b4ce 100644
--- a/mirai/hikari/loader.zig
+++ b/mirai/hikari/loader.zig
@@ -18,7 +18,7 @@ const sensei = @import("../kata/sensei/sensei.zig");
const serial = @import("../drivers/serial/serial.zig");
const fs_limits = @import("../common/limits/fs.zig");
-const INIT_LOCATION = "/system/akiba/pulse.akibainit";
+const INIT_LOCATION = "/system/akiba/pulse.gen";
pub fn init(fs: *afs.AFS(ahci.BlockDevice)) !u32 {
const init_size = fs.get_unit_size(INIT_LOCATION) catch |err| {
diff --git a/mirai/invocations/handler.zig b/mirai/invocations/handler.zig
index 0855c72..bf3fa47 100644
--- a/mirai/invocations/handler.zig
+++ b/mirai/invocations/handler.zig
@@ -19,6 +19,7 @@ const viewstack = @import("fs/viewstack.zig");
const exit = @import("kata/exit.zig");
const postman = @import("kata/postman.zig");
+const reap = @import("kata/reap.zig");
const spawn = @import("kata/spawn.zig");
const wait = @import("kata/wait.zig");
const yield = @import("kata/yield.zig");
@@ -85,6 +86,7 @@ pub fn handle(ctx: *InvocationContext) void {
invocations.UPTIME => uptime.invoke(ctx),
invocations.GETTIME => gettime.invoke(ctx),
invocations.DISKINFO => diskinfo.invoke(ctx),
+ invocations.REAP => reap.invoke(ctx),
else => result.set_error(ctx),
}
}
diff --git a/mirai/invocations/kata/reap.zig b/mirai/invocations/kata/reap.zig
new file mode 100644
index 0000000..fd47794
--- /dev/null
+++ b/mirai/invocations/kata/reap.zig
@@ -0,0 +1,62 @@
+//! Reap invocation - Shinigami cleans up zombie katas
+
+const handler = @import("../handler.zig");
+const memory = @import("../../kata/memory.zig");
+const paging_const = @import("../../common/constants/paging.zig");
+const pool = @import("../../kata/pool.zig");
+const serial = @import("../../drivers/serial/serial.zig");
+const types = @import("../../kata/types.zig");
+
+const HIGHER_HALF: u64 = 0xFFFF800000000000;
+
+fn check_ash_pd256() u64 {
+ for (&pool.pool, 0..) |*k, i| {
+ if (pool.used[i] and k.id == 3 and k.page_table != 0) {
+ const pml4: [*]volatile u64 = @ptrFromInt(k.page_table + HIGHER_HALF);
+ if ((pml4[0] & 1) == 0) return 0xDEAD0001;
+ const pdpt: [*]volatile u64 = @ptrFromInt((pml4[0] & paging_const.PTE_MASK) + HIGHER_HALF);
+ if ((pdpt[0] & 1) == 0) return 0xDEAD0002;
+ const pd: [*]volatile u64 = @ptrFromInt((pdpt[0] & paging_const.PTE_MASK) + HIGHER_HALF);
+ return pd[256];
+ }
+ }
+ return 0xDEAD0000;
+}
+
+/// Reap zombie katas - called by Shinigami
+/// Returns the number of zombies reaped
+pub fn invoke(ctx: *handler.InvocationContext) void {
+ var reaped: u64 = 0;
+
+ for (&pool.pool, 0..) |*kata, i| {
+ if (!pool.used[i]) continue;
+ if (kata.state != .Zombie) continue;
+
+ // Found a zombie - reap it
+ const kata_id = kata.id;
+ const pt = kata.page_table;
+
+ const pd256_before = check_ash_pd256();
+ serial.printf("Shinigami: reaping kata {d} pt={x} ash_pd256={x}\n", .{ kata_id, pt, pd256_before });
+
+ // Destroy the page table
+ if (kata.page_table != 0) {
+ memory.destroy_zombie_page_table(kata.page_table);
+ kata.page_table = 0;
+ }
+
+ const pd256_after = check_ash_pd256();
+ if (pd256_after != pd256_before) {
+ serial.printf("Shinigami: CORRUPTION during reap! pd256: {x} -> {x}\n", .{ pd256_before, pd256_after });
+ }
+
+ // Mark as dissolved and free the slot
+ kata.state = .Dissolved;
+ pool.used[i] = false;
+
+ serial.printf("Shinigami: reaped kata {d}\n", .{kata_id});
+ reaped += 1;
+ }
+
+ ctx.rax = reaped;
+}
diff --git a/mirai/invocations/kata/spawn.zig b/mirai/invocations/kata/spawn.zig
index 051810d..83ae61d 100644
--- a/mirai/invocations/kata/spawn.zig
+++ b/mirai/invocations/kata/spawn.zig
@@ -8,15 +8,86 @@ const handler = @import("../handler.zig");
const hikari = @import("../../hikari/loader.zig");
const kata_limits = @import("../../common/limits/kata.zig");
const memory_limits = @import("../../common/limits/memory.zig");
+const paging_const = @import("../../common/constants/paging.zig");
+const pmm = @import("../../memory/pmm.zig");
+const pool = @import("../../kata/pool.zig");
const result = @import("../../utils/types/result.zig");
+const serial = @import("../../drivers/serial/serial.zig");
const slice = @import("../../utils/mem/slice.zig");
+const HIGHER_HALF: u64 = 0xFFFF800000000000;
+
var afs_instance: ?*afs.AFS(ahci.BlockDevice) = null;
pub fn set_afs_instance(fs: *afs.AFS(ahci.BlockDevice)) void {
afs_instance = fs;
}
+fn check_ash_pd256() u64 {
+ for (&pool.pool, 0..) |*k, i| {
+ if (pool.used[i] and k.id == 3 and k.page_table != 0) {
+ const pml4: [*]volatile u64 = @ptrFromInt(k.page_table + HIGHER_HALF);
+ if ((pml4[0] & 1) == 0) return 0xDEAD0001;
+ const pdpt: [*]volatile u64 = @ptrFromInt((pml4[0] & paging_const.PTE_MASK) + HIGHER_HALF);
+ if ((pdpt[0] & 1) == 0) return 0xDEAD0002;
+ const pd: [*]volatile u64 = @ptrFromInt((pdpt[0] & paging_const.PTE_MASK) + HIGHER_HALF);
+ return pd[256];
+ }
+ }
+ return 0xDEAD0000;
+}
+
+fn verify_kernel_mapping(phys: u64) bool {
+ // Check if physical address is correctly mapped via higher half
+ const virt = phys + HIGHER_HALF;
+
+ // Read from that address and write back - if mapping is wrong, we'd write to wrong place
+ const ptr: [*]volatile u64 = @ptrFromInt(virt);
+ const val = ptr[0];
+ _ = val;
+
+ // Check kernel's CR3 mapping of this address
+ const asm_memory = @import("../../asm/memory.zig");
+ const kernel_cr3 = asm_memory.read_page_table_base() & ~@as(u64, 0xFFF);
+
+ const pml4_idx = (virt >> 39) & 0x1FF;
+ const pdpt_idx = (virt >> 30) & 0x1FF;
+ const pd_idx = (virt >> 21) & 0x1FF;
+ const pt_idx = (virt >> 12) & 0x1FF;
+
+ const pml4: [*]volatile u64 = @ptrFromInt(kernel_cr3 + HIGHER_HALF);
+ if ((pml4[pml4_idx] & 1) == 0) {
+ serial.printf("VERIFY: pml4[{d}] not present for phys {x}\n", .{ pml4_idx, phys });
+ return false;
+ }
+
+ const pdpt: [*]volatile u64 = @ptrFromInt((pml4[pml4_idx] & paging_const.PTE_MASK) + HIGHER_HALF);
+ if ((pdpt[pdpt_idx] & 1) == 0) {
+ serial.printf("VERIFY: pdpt[{d}] not present for phys {x}\n", .{ pdpt_idx, phys });
+ return false;
+ }
+
+ const pd: [*]volatile u64 = @ptrFromInt((pdpt[pdpt_idx] & paging_const.PTE_MASK) + HIGHER_HALF);
+ if ((pd[pd_idx] & 1) == 0) {
+ serial.printf("VERIFY: pd[{d}] not present for phys {x}\n", .{ pd_idx, phys });
+ return false;
+ }
+
+ const pt: [*]volatile u64 = @ptrFromInt((pd[pd_idx] & paging_const.PTE_MASK) + HIGHER_HALF);
+ if ((pt[pt_idx] & 1) == 0) {
+ serial.printf("VERIFY: pt[{d}] not present for phys {x}\n", .{ pt_idx, phys });
+ return false;
+ }
+
+ const mapped_phys = pt[pt_idx] & paging_const.PTE_MASK;
+ if (mapped_phys != phys) {
+ serial.printf("VERIFY: phys {x} maps to {x} instead!\n", .{ phys, mapped_phys });
+ return false;
+ }
+
+ return true;
+}
+
pub fn invoke(ctx: *handler.InvocationContext) void {
const fs = afs_instance orelse return result.set_error(ctx);
@@ -53,9 +124,40 @@ pub fn invoke(ctx: *handler.InvocationContext) void {
}
}
+ const pd256_before = check_ash_pd256();
+
+ // Verify kernel can correctly access Ash's PD
+ if (pmm.ash_pd_phys != 0) {
+ if (!verify_kernel_mapping(pmm.ash_pd_phys)) {
+ serial.printf("spawn: Kernel mapping of Ash's PD is WRONG!\n", .{});
+ }
+ }
+
const kata_id = hikari.load_with_args(fs, location, params[0..param_count]) catch {
return result.set_error(ctx);
};
+ const pd256_after = check_ash_pd256();
+
+ // Track Ash's PD page (kata 3 is Ash)
+ if (kata_id == 3) {
+ if (pool.get(kata_id)) |kata| {
+ if (kata.page_table != 0) {
+ const pml4: [*]volatile u64 = @ptrFromInt(kata.page_table + HIGHER_HALF);
+ if ((pml4[0] & 1) != 0) {
+ const pdpt: [*]volatile u64 = @ptrFromInt((pml4[0] & paging_const.PTE_MASK) + HIGHER_HALF);
+ if ((pdpt[0] & 1) != 0) {
+ const pd_phys = pdpt[0] & paging_const.PTE_MASK;
+ pmm.set_ash_pd(pd_phys);
+ }
+ }
+ }
+ }
+ } else if (pd256_after != pd256_before and pd256_before != 0xDEAD0000) {
+ serial.printf("spawn: CORRUPTION during spawn of kata {d}! pd256: {x} -> {x}\n", .{ kata_id, pd256_before, pd256_after });
+ }
+
+ serial.printf("spawn: created kata {d}\n", .{kata_id});
+
result.set_value(ctx, kata_id);
}
diff --git a/mirai/invocations/kata/wait.zig b/mirai/invocations/kata/wait.zig
index e46b08b..e657e90 100644
--- a/mirai/invocations/kata/wait.zig
+++ b/mirai/invocations/kata/wait.zig
@@ -11,7 +11,8 @@ pub fn invoke(ctx: *handler.InvocationContext) void {
const target = kata_mod.get_kata(target_id) orelse return result.set_error(ctx);
- if (target.state == .Dissolved) {
+ // Zombie or Dissolved means the child has exited
+ if (target.state == .Zombie or target.state == .Dissolved) {
return result.set_value(ctx, target.exit_code);
}
diff --git a/mirai/invocations/kata/yield.zig b/mirai/invocations/kata/yield.zig
index bc4028a..0da91af 100644
--- a/mirai/invocations/kata/yield.zig
+++ b/mirai/invocations/kata/yield.zig
@@ -10,6 +10,28 @@ pub fn invoke(ctx: *handler.InvocationContext) void {
kata.state = kata_mod.State.Alive;
+ // Save context before switching - schedule() may not return
+ kata.context.rax = 0; // Return value for yield
+ kata.context.rbx = ctx.rbx;
+ kata.context.rcx = ctx.rcx;
+ kata.context.rdx = ctx.rdx;
+ kata.context.rsi = ctx.rsi;
+ kata.context.rdi = ctx.rdi;
+ kata.context.rbp = ctx.rbp;
+ kata.context.rsp = ctx.rsp;
+ kata.context.r8 = ctx.r8;
+ kata.context.r9 = ctx.r9;
+ kata.context.r10 = ctx.r10;
+ kata.context.r11 = ctx.r11;
+ kata.context.r12 = ctx.r12;
+ kata.context.r13 = ctx.r13;
+ kata.context.r14 = ctx.r14;
+ kata.context.r15 = ctx.r15;
+ kata.context.rip = ctx.rip;
+ kata.context.rflags = ctx.rflags;
+ kata.context.cs = ctx.cs;
+ kata.context.ss = ctx.ss;
+
if (!sensei.is_in_queue(kata)) {
sensei.enqueue_kata(kata);
}
diff --git a/mirai/kata/memory.zig b/mirai/kata/memory.zig
index 5639bf5..f90c9f0 100644
--- a/mirai/kata/memory.zig
+++ b/mirai/kata/memory.zig
@@ -4,6 +4,7 @@ const asm_memory = @import("../asm/memory.zig");
const memory_const = @import("../common/constants/memory.zig");
const memory_limits = @import("../common/limits/memory.zig");
const paging = @import("../memory/paging.zig");
+const paging_const = @import("../common/constants/paging.zig");
const pmm = @import("../memory/pmm.zig");
const types = @import("types.zig");
@@ -13,39 +14,6 @@ const PAGE_SIZE = memory_const.PAGE_SIZE;
const KERNEL_VMALLOC_START: u64 = 0xFFFFFF8000000000;
var next_vmalloc_addr: u64 = KERNEL_VMALLOC_START;
-// Deferred page table destruction queue
-const MAX_DEFERRED: usize = 16;
-var deferred_page_tables: [MAX_DEFERRED]u64 = [_]u64{0} ** MAX_DEFERRED;
-var deferred_count: usize = 0;
-
-/// Queue a page table for deferred destruction
-fn queue_deferred_destroy(page_table: u64) void {
- if (deferred_count < MAX_DEFERRED) {
- deferred_page_tables[deferred_count] = page_table;
- deferred_count += 1;
- }
- // If queue full, leak the page table (shouldn't happen in practice)
-}
-
-/// Process deferred page table destructions.
-/// Safe to call when CR3 points to a page table we want to keep.
-pub fn process_deferred_cleanup(exclude_pt: u64) void {
- const current_cr3 = asm_memory.read_page_table_base();
- var write_idx: usize = 0;
-
- for (0..deferred_count) |i| {
- const pt = deferred_page_tables[i];
- if (pt != 0 and pt != current_cr3 and pt != exclude_pt) {
- paging.destroy_page_table(pt);
- } else if (pt != 0) {
- // Keep in queue
- deferred_page_tables[write_idx] = pt;
- write_idx += 1;
- }
- }
- deferred_count = write_idx;
-}
-
pub const VirtualBuffer = struct {
data: []u8,
virt_base: u64,
@@ -114,8 +82,16 @@ pub fn setup(kata: *types.Kata, framebuffer_phys: u64, framebuffer_size: u64) !v
kata.user_stack_bottom = memory_const.USER_STACK_TOP - (memory_const.USER_STACK_MAX_PAGES * PAGE_SIZE);
kata.user_stack_committed = memory_const.USER_STACK_TOP - (memory_const.USER_STACK_INITIAL_PAGES * PAGE_SIZE);
+ const serial = @import("../drivers/serial/serial.zig");
+
for (0..memory_const.USER_STACK_INITIAL_PAGES) |i| {
const page = pmm.alloc_page() orelse return error.OutOfMemory;
+
+ // Check if we got Ash's PD
+ if (pmm.ash_pd_phys != 0 and page == pmm.ash_pd_phys) {
+ serial.printf("SETUP: Got Ash's PD {x} for user stack page!\n", .{page});
+ }
+
const virt = kata.user_stack_committed + (i * PAGE_SIZE);
_ = try paging.map_page_in_table(kata.page_table, virt, page, paging.PAGE_WRITABLE | paging.PAGE_USER);
@@ -126,6 +102,12 @@ pub fn setup(kata: *types.Kata, framebuffer_phys: u64, framebuffer_size: u64) !v
}
const first_page = pmm.alloc_page() orelse return error.OutOfMemory;
+
+ // Check if we got Ash's PD for kernel stack
+ if (pmm.ash_pd_phys != 0 and first_page == pmm.ash_pd_phys) {
+ serial.printf("SETUP: Got Ash's PD {x} for kernel stack!\n", .{first_page});
+ }
+
const kernel_stack_base = first_page;
// Identity map first page
@@ -222,6 +204,13 @@ pub fn load_segment(
}
} else {
page_phys = pmm.alloc_page() orelse return error.OutOfMemory;
+
+ // Check if we got Ash's PD
+ if (pmm.ash_pd_phys != 0 and page_phys == pmm.ash_pd_phys) {
+ const serial = @import("../drivers/serial/serial.zig");
+ serial.printf("KATA-MEM: Got Ash's PD {x} for data page at vaddr {x}!\n", .{ page_phys, page_vaddr });
+ }
+
_ = try paging.map_page_in_table(kata.page_table, page_vaddr, page_phys, page_flags);
const zero_ptr: [*]volatile u8 = @ptrFromInt(page_phys + HIGHER_HALF);
@@ -255,13 +244,10 @@ pub fn cleanup(kata: *types.Kata) void {
if (kata.page_table != 0) {
const current_cr3 = asm_memory.read_page_table_base();
if (current_cr3 != kata.page_table) {
- // Safe to destroy immediately
paging.destroy_page_table(kata.page_table);
- } else {
- // Queue for deferred destruction
- queue_deferred_destroy(kata.page_table);
+ kata.page_table = 0;
}
- kata.page_table = 0;
+ // If CR3 == kata.page_table, leave page_table intact for Shinigami
}
kata.stack_top = 0;
kata.user_stack_top = 0;
@@ -269,6 +255,12 @@ pub fn cleanup(kata: *types.Kata) void {
kata.user_stack_committed = 0;
}
+/// Called by Shinigami to destroy a zombie's page table.
+/// Safe because Shinigami runs with its own page table.
+pub fn destroy_zombie_page_table(page_table: u64) void {
+ paging.destroy_page_table(page_table);
+}
+
pub fn grow_stack(kata: *types.Kata, fault_addr: u64) bool {
const page_addr = fault_addr & ~@as(u64, 0xFFF);
diff --git a/mirai/kata/pool.zig b/mirai/kata/pool.zig
index 030909a..08814d8 100644
--- a/mirai/kata/pool.zig
+++ b/mirai/kata/pool.zig
@@ -77,20 +77,38 @@ pub fn get(id: u32) ?*types.Kata {
pub fn dissolve(kata_id: u32) void {
for (&pool, 0..) |*kata, i| {
if (used[i] and kata.id == kata_id) {
+ // Mark as dying
+ kata.state = .Dying;
+
+ // Clean up attachments
for (&kata.attachments) |*slot| {
if (slot.*) |ptr| {
attachment.free(ptr);
slot.* = null;
}
}
+
+ // Clean up letter data
if (kata.letter_data) |data| {
heap.free(@ptrCast(data), kata.letter_capacity);
kata.letter_data = null;
kata.letter_capacity = 0;
}
+
+ // Try to clean up memory
memory.cleanup(kata);
- kata.state = .Dissolved;
- used[i] = false;
+
+ // Check if page table was destroyed or needs Shinigami
+ if (kata.page_table != 0) {
+ // Page table still exists - become zombie for Shinigami to reap
+ kata.state = .Zombie;
+ // Keep used[i] = true so slot isn't reused yet
+ } else {
+ // Fully cleaned up
+ kata.state = .Dissolved;
+ used[i] = false;
+ }
+
waker.wake_waiting(kata_id);
return;
}
@@ -101,6 +119,7 @@ fn create_empty() types.Kata {
return types.Kata{
.id = 0,
.state = .Dissolved,
+ .mode = .Persona,
.context = types.Context.init(),
.page_table = 0,
.stack_top = 0,
diff --git a/mirai/kata/sensei/sensei.zig b/mirai/kata/sensei/sensei.zig
index 7985ad9..77f9cc9 100644
--- a/mirai/kata/sensei/sensei.zig
+++ b/mirai/kata/sensei/sensei.zig
@@ -3,6 +3,7 @@
const cpu = @import("../../asm/cpu.zig");
const pool = @import("../pool.zig");
const queue = @import("queue.zig");
+const serial = @import("../../drivers/serial/serial.zig");
const shift = @import("../shift.zig");
const types = @import("../types.zig");
const waker = @import("waker.zig");
diff --git a/mirai/kata/sensei/waker.zig b/mirai/kata/sensei/waker.zig
index c4334e3..d18388b 100644
--- a/mirai/kata/sensei/waker.zig
+++ b/mirai/kata/sensei/waker.zig
@@ -14,7 +14,8 @@ pub fn wake_all_waiting() void {
if (kata.state != .Stalled) continue;
const target = pool.get(kata.waiting_for);
- if (target == null or target.?.state == .Dissolved) {
+ // Wake if target doesn't exist, is zombie, or dissolved
+ if (target == null or target.?.state == .Zombie or target.?.state == .Dissolved) {
kata.state = .Alive;
queue.enqueue(kata);
kata.waiting_for = 0;
diff --git a/mirai/kata/shift.zig b/mirai/kata/shift.zig
index 3ad64be..52e87fb 100644
--- a/mirai/kata/shift.zig
+++ b/mirai/kata/shift.zig
@@ -2,16 +2,20 @@
const context = @import("../asm/context.zig");
const gdt = @import("../boot/gdt/gdt.zig");
-const kata_memory = @import("memory.zig");
+const paging = @import("../memory/paging.zig");
+const serial = @import("../drivers/serial/serial.zig");
const tss = @import("../boot/tss/tss.zig");
const types = @import("types.zig");
var current_context: ?*types.Context = null;
pub fn to_kata(kata: *types.Kata) void {
- // Process any deferred page table cleanups before switching
- // Exclude the target kata's page table from cleanup
- kata_memory.process_deferred_cleanup(kata.page_table);
+ // Validate that the target RIP is mapped in the page table
+ if (paging.virt_to_phys(kata.page_table, kata.context.rip) == null) {
+ serial.printf("shift: FATAL - kata {d} rip {x} not mapped!\n", .{ kata.id, kata.context.rip });
+ paging.dump_pt_structure(kata.page_table, "corrupted");
+ while (true) {}
+ }
tss.set_kernel_stack(kata.stack_top);
current_context = &kata.context;
diff --git a/mirai/kata/types.zig b/mirai/kata/types.zig
index 85f653f..c416212 100644
--- a/mirai/kata/types.zig
+++ b/mirai/kata/types.zig
@@ -15,6 +15,11 @@ pub const State = enum {
Dissolved, // Gone, slot reusable
};
+pub const Mode = enum {
+ Persona, // Normal user process - can be reaped
+ Protected, // System process - page tables are protected
+};
+
pub const Context = packed struct {
rax: u64,
rbx: u64,
@@ -66,6 +71,7 @@ pub const Context = packed struct {
pub const Kata = struct {
id: u32,
state: State,
+ mode: Mode,
context: Context,
page_table: u64,
diff --git a/mirai/memory/paging.zig b/mirai/memory/paging.zig
index 61c69b7..ea3e7e1 100644
--- a/mirai/memory/paging.zig
+++ b/mirai/memory/paging.zig
@@ -83,7 +83,15 @@ pub fn map_page(virt: u64, phys: u64, flags: u64) !void {
}
pub fn create_page_table() !u64 {
+ const serial = @import("../drivers/serial/serial.zig");
+
const new_pml4_phys = pmm.alloc_page() orelse return error.OutOfMemory;
+
+ // Check if we're about to zero Ash's PD (used as PML4)
+ if (pmm.ash_pd_phys != 0 and new_pml4_phys == pmm.ash_pd_phys) {
+ serial.printf("PAGING: About to zero Ash's PD {x} (as PML4)!\n", .{new_pml4_phys});
+ }
+
const new_pml4: [*]volatile u64 = @ptrFromInt(new_pml4_phys + HIGHER_HALF_START);
for (0..paging_const.PML4_ENTRIES) |i| {
@@ -111,6 +119,9 @@ pub fn create_page_table() !u64 {
}
pub fn map_page_in_table(page_table_phys: u64, virt: u64, phys: u64, flags: u64) !struct { bool, u64 } {
+ const serial = @import("../drivers/serial/serial.zig");
+ const pool = @import("../kata/pool.zig");
+
const pml4_index = (virt >> 39) & 0x1FF;
const pdpt_index = (virt >> 30) & 0x1FF;
const pd_index = (virt >> 21) & 0x1FF;
@@ -118,11 +129,38 @@ pub fn map_page_in_table(page_table_phys: u64, virt: u64, phys: u64, flags: u64)
const pml4: [*]volatile u64 = @ptrFromInt(page_table_phys + HIGHER_HALF_START);
+ // Helper to check Ash's pd[256]
+ const ash_pd256 = struct {
+ fn get() u64 {
+ for (&pool.pool, 0..) |*k, i| {
+ if (pool.used[i] and k.id == 3 and k.page_table != 0) {
+ const pml4_ptr: [*]volatile u64 = @ptrFromInt(k.page_table + HIGHER_HALF_START);
+ if ((pml4_ptr[0] & 1) == 0) return 0xDEAD0001;
+ const pdpt: [*]volatile u64 = @ptrFromInt((pml4_ptr[0] & paging_const.PTE_MASK) + HIGHER_HALF_START);
+ if ((pdpt[0] & 1) == 0) return 0xDEAD0002;
+ const pd: [*]volatile u64 = @ptrFromInt((pdpt[0] & paging_const.PTE_MASK) + HIGHER_HALF_START);
+ return pd[256];
+ }
+ }
+ return 0;
+ }
+ };
+
+ const before = ash_pd256.get();
+
var pdpt_phys: u64 = undefined;
if ((pml4[pml4_index] & PAGE_PRESENT) == 0) {
pdpt_phys = pmm.alloc_page() orelse return error.OutOfMemory;
+ if (pmm.ash_pd_phys != 0 and pdpt_phys == pmm.ash_pd_phys) {
+ serial.printf("PAGING: About to zero Ash's PD {x} (as PDPT) for virt {x}!\n", .{ pdpt_phys, virt });
+ }
zero_page(pdpt_phys + HIGHER_HALF_START);
pml4[pml4_index] = pdpt_phys | PAGE_PRESENT | PAGE_WRITABLE | PAGE_USER;
+
+ const after = ash_pd256.get();
+ if (before != 0 and after != before) {
+ serial.printf("CORRUPTION after PDPT alloc! virt={x} pdpt={x} before={x} after={x}\n", .{ virt, pdpt_phys, before, after });
+ }
} else {
pdpt_phys = pml4[pml4_index] & paging_const.PTE_MASK;
}
@@ -132,8 +170,16 @@ pub fn map_page_in_table(page_table_phys: u64, virt: u64, phys: u64, flags: u64)
var pd_phys: u64 = undefined;
if ((pdpt[pdpt_index] & PAGE_PRESENT) == 0) {
pd_phys = pmm.alloc_page() orelse return error.OutOfMemory;
+ if (pmm.ash_pd_phys != 0 and pd_phys == pmm.ash_pd_phys) {
+ serial.printf("PAGING: About to zero Ash's PD {x} for virt {x}!\n", .{ pd_phys, virt });
+ }
zero_page(pd_phys + HIGHER_HALF_START);
pdpt[pdpt_index] = pd_phys | PAGE_PRESENT | PAGE_WRITABLE | PAGE_USER;
+
+ const after = ash_pd256.get();
+ if (before != 0 and after != before) {
+ serial.printf("CORRUPTION after PD alloc! virt={x} pd={x} before={x} after={x}\n", .{ virt, pd_phys, before, after });
+ }
} else {
pd_phys = pdpt[pdpt_index] & paging_const.PTE_MASK;
}
@@ -143,8 +189,16 @@ pub fn map_page_in_table(page_table_phys: u64, virt: u64, phys: u64, flags: u64)
var pt_phys: u64 = undefined;
if ((pd[pd_index] & PAGE_PRESENT) == 0) {
pt_phys = pmm.alloc_page() orelse return error.OutOfMemory;
+ if (pmm.ash_pd_phys != 0 and pt_phys == pmm.ash_pd_phys) {
+ serial.printf("PAGING: About to zero Ash's PD {x} (as PT) for virt {x}!\n", .{ pt_phys, virt });
+ }
zero_page(pt_phys + HIGHER_HALF_START);
pd[pd_index] = pt_phys | PAGE_PRESENT | PAGE_WRITABLE | PAGE_USER;
+
+ const after = ash_pd256.get();
+ if (before != 0 and after != before) {
+ serial.printf("CORRUPTION after PT alloc! virt={x} pt={x} pd_idx={d} before={x} after={x}\n", .{ virt, pt_phys, pd_index, before, after });
+ }
} else {
pt_phys = pd[pd_index] & paging_const.PTE_MASK;
}
@@ -154,6 +208,12 @@ pub fn map_page_in_table(page_table_phys: u64, virt: u64, phys: u64, flags: u64)
const was_mapped = (pt[pt_index] & PAGE_PRESENT) != 0;
if (!was_mapped) {
pt[pt_index] = phys | flags | PAGE_PRESENT;
+
+ const after = ash_pd256.get();
+ if (before != 0 and after != before) {
+ serial.printf("CORRUPTION after PT entry write! virt={x} pt={x} pt_idx={d} before={x} after={x}\n", .{ virt, pt_phys, pt_index, before, after });
+ }
+
return .{ false, phys };
} else {
const existing_phys = pt[pt_index] & paging_const.PTE_MASK;
@@ -256,6 +316,53 @@ fn should_free_page(virt: u64, phys: u64) bool {
return true;
}
+pub fn dump_pt_structure(page_table_phys: u64, label: []const u8) void {
+ const serial = @import("../drivers/serial/serial.zig");
+ serial.print("PT dump: ");
+ serial.print(label);
+ serial.printf(" pml4={x}\n", .{page_table_phys});
+
+ const pml4: [*]volatile u64 = @ptrFromInt(page_table_phys + HIGHER_HALF_START);
+
+ // Just dump PML4[0] chain since that's where user code is
+ const pml4_entry = pml4[0];
+ if ((pml4_entry & PAGE_PRESENT) == 0) {
+ serial.print("PT dump: pml4[0] not present\n");
+ return;
+ }
+
+ const pdpt_phys = pml4_entry & paging_const.PTE_MASK;
+ serial.printf("PT dump: pml4[0]={x} -> pdpt={x}\n", .{ pml4_entry, pdpt_phys });
+
+ const pdpt: [*]volatile u64 = @ptrFromInt(pdpt_phys + HIGHER_HALF_START);
+ const pdpt_entry = pdpt[0];
+ if ((pdpt_entry & PAGE_PRESENT) == 0) {
+ serial.print("PT dump: pdpt[0] not present\n");
+ return;
+ }
+
+ const pd_phys = pdpt_entry & paging_const.PTE_MASK;
+ serial.printf("PT dump: pdpt[0]={x} -> pd={x}\n", .{ pdpt_entry, pd_phys });
+
+ // Check PD[256] which is where 0x20000000 maps (256 = 0x20000000 >> 21 & 0x1FF)
+ const pd: [*]volatile u64 = @ptrFromInt(pd_phys + HIGHER_HALF_START);
+ const pd_entry = pd[256];
+ if ((pd_entry & PAGE_PRESENT) == 0) {
+ serial.print("PT dump: pd[256] not present\n");
+ return;
+ }
+
+ const pt_phys = pd_entry & paging_const.PTE_MASK;
+ serial.printf("PT dump: pd[256]={x} -> pt={x}\n", .{ pd_entry, pt_phys });
+}
+
+pub fn set_shinigami_pt_addrs(pdpt: u64, pd: u64, pt: u64) void {
+ // No longer used, kept for compatibility
+ _ = pdpt;
+ _ = pd;
+ _ = pt;
+}
+
pub fn destroy_page_table(page_table_phys: u64) void {
const pml4: [*]volatile u64 = @ptrFromInt(page_table_phys + HIGHER_HALF_START);
diff --git a/mirai/memory/pmm.zig b/mirai/memory/pmm.zig
index 8207387..cf66855 100644
--- a/mirai/memory/pmm.zig
+++ b/mirai/memory/pmm.zig
@@ -12,6 +12,8 @@ const HIGHER_HALF = memory_const.HIGHER_HALF_START;
var bitmap: [*]u8 = undefined;
var bitmap_size: usize = 0;
+var bitmap_phys_start: u64 = 0;
+var bitmap_phys_end: u64 = 0;
var total_pages: u64 = 0;
var used_pages: u64 = 0;
var initialized: bool = false;
@@ -21,6 +23,14 @@ pub const MemoryInfo = struct {
used: u64,
};
+pub fn get_bitmap_range() struct { start: u64, end: u64 } {
+ return .{ .start = bitmap_phys_start, .end = bitmap_phys_end };
+}
+
+pub fn is_in_bitmap(phys: u64) bool {
+ return phys >= bitmap_phys_start and phys < bitmap_phys_end;
+}
+
pub fn init(kernel_end_phys: u64, memory_map: []multiboot.MemoryEntry) void {
serial.print("\n=== PMM ===\n");
@@ -39,8 +49,12 @@ pub fn init(kernel_end_phys: u64, memory_map: []multiboot.MemoryEntry) void {
serial.printf("Memory: {} MB, {} pages\n", .{ highest_addr / (1024 * 1024), total_pages });
- const bitmap_phys = align_up(kernel_end_phys, PAGE_SIZE);
- bitmap = @ptrFromInt(bitmap_phys + HIGHER_HALF);
+ bitmap_phys_start = align_up(kernel_end_phys, PAGE_SIZE);
+ bitmap = @ptrFromInt(bitmap_phys_start + HIGHER_HALF);
+
+ const bitmap_pages = (bitmap_size + PAGE_SIZE - 1) / PAGE_SIZE;
+ bitmap_phys_end = bitmap_phys_start + bitmap_pages * PAGE_SIZE;
+ serial.printf("Bitmap: phys={x}-{x} size={x} pages={d}\n", .{ bitmap_phys_start, bitmap_phys_end, bitmap_size, bitmap_pages });
for (0..bitmap_size) |i| {
bitmap[i] = pmm_const.BITMAP_MARK_USED;
@@ -54,11 +68,10 @@ pub fn init(kernel_end_phys: u64, memory_map: []multiboot.MemoryEntry) void {
}
const kernel_size = kernel_end_phys - pmm_const.KERNEL_BASE;
- const bitmap_pages = (bitmap_size + PAGE_SIZE - 1) / PAGE_SIZE;
reserve_region(0, pmm_const.FIRST_MB);
reserve_region(pmm_const.KERNEL_BASE, kernel_size);
- reserve_region(bitmap_phys, bitmap_pages * PAGE_SIZE);
+ reserve_region(bitmap_phys_start, bitmap_phys_end - bitmap_phys_start);
reserve_region(pmm_const.MMIO_PCI_BASE, pmm_const.MMIO_PCI_SIZE);
reserve_region(pmm_const.MMIO_FRAMEBUFFER_BASE, pmm_const.MMIO_FRAMEBUFFER_SIZE);
@@ -70,13 +83,40 @@ pub fn init(kernel_end_phys: u64, memory_map: []multiboot.MemoryEntry) void {
initialized = true;
}
+pub var ash_pd_phys: u64 = 0;
+
+pub fn set_ash_pd(phys: u64) void {
+ ash_pd_phys = phys;
+ serial.printf("PMM: Tracking Ash PD at {x}\n", .{phys});
+
+ // Verify it's marked as used
+ const page = phys / PAGE_SIZE;
+ if (is_page_used(page)) {
+ serial.printf("PMM: Ash PD page {x} is correctly marked USED\n", .{phys});
+ } else {
+ serial.printf("PMM: BUG! Ash PD page {x} is marked FREE!\n", .{phys});
+ }
+}
+
+pub fn check_ash_pd_status() void {
+ if (ash_pd_phys == 0) return;
+ const page = ash_pd_phys / PAGE_SIZE;
+ if (!is_page_used(page)) {
+ serial.printf("PMM: Ash PD {x} became FREE!\n", .{ash_pd_phys});
+ }
+}
+
pub fn alloc_page() ?u64 {
if (!initialized) return null;
for (0..total_pages) |i| {
if (!is_page_used(i)) {
+ const phys = i * PAGE_SIZE;
+ if (ash_pd_phys != 0 and phys == ash_pd_phys) {
+ serial.printf("PMM: ALLOCATING Ash's PD {x}! BUG - should be marked used!\n", .{phys});
+ }
set_page_used(i);
- return i * PAGE_SIZE;
+ return phys;
}
}
return null;
@@ -87,6 +127,9 @@ pub fn free_page(phys_addr: u64) void {
const page = phys_addr / PAGE_SIZE;
if (page < total_pages) {
+ if (ash_pd_phys != 0 and phys_addr == ash_pd_phys) {
+ serial.printf("PMM: FREEING Ash's PD {x}!\n", .{phys_addr});
+ }
set_page_free(page);
}
}
diff --git a/resources/system/akiba.world b/resources/system/akiba.world
new file mode 100644
index 0000000..8993cdd
--- /dev/null
+++ b/resources/system/akiba.world
@@ -0,0 +1,17 @@
+world {
+ name = akiba
+ startup {
+ binary {
+ name = shinigami
+ path = "/system/shinigami/shinigami.gen"
+ mode = protected
+ respawn = true
+ }
+ binary {
+ name = ash
+ path = "/system/ash/ash.gen"
+ mode = protected
+ respawn = true
+ }
+ }
+}
diff --git a/system/libraries/format/format.zig b/system/libraries/format/format.zig
index 02a5632..f9c8068 100644
--- a/system/libraries/format/format.zig
+++ b/system/libraries/format/format.zig
@@ -20,3 +20,10 @@ pub const colorf = printmod.colorf;
pub const Table = tablemod.Table;
pub const Column = tablemod.Column;
pub const Alignment = tablemod.Alignment;
+
+/// Print a u64 value as decimal
+pub fn print_u64(num: u64) void {
+ var buf: [20]u8 = undefined;
+ const str = int.toStr(num, &buf);
+ print(str);
+}
diff --git a/system/libraries/kata/kata.zig b/system/libraries/kata/kata.zig
index 39f4556..1144294 100644
--- a/system/libraries/kata/kata.zig
+++ b/system/libraries/kata/kata.zig
@@ -43,3 +43,8 @@ pub fn wait(pid: u32) Error!u64 {
yield();
}
}
+
+/// Reap zombie katas - returns number of zombies reaped
+pub fn reap() u64 {
+ return sys.syscall(.reap, .{});
+}
diff --git a/system/libraries/sys/sys.zig b/system/libraries/sys/sys.zig
index b080a90..56186f1 100644
--- a/system/libraries/sys/sys.zig
+++ b/system/libraries/sys/sys.zig
@@ -26,6 +26,7 @@ pub const Invocation = enum(u64) {
uptime = 0x11,
gettime = 0x12,
diskinfo = 0x13,
+ reap = 0x14,
};
pub inline fn syscall(invocation: Invocation, args: anytype) u64 {
@@ -48,8 +49,7 @@ pub inline fn syscall(invocation: Invocation, args: anytype) u64 {
[arg3] "{r10}" (arg3),
[arg4] "{r8}" (arg4),
[arg5] "{r9}" (arg5),
- : .{ .rcx = true, .r11 = true, .memory = true }
- );
+ : .{ .rcx = true, .r11 = true, .memory = true });
}
inline fn toU64(value: anytype) u64 {
diff --git a/system/pulse/pulse.zig b/system/pulse/pulse.zig
index 3ef1091..b917f8c 100644
--- a/system/pulse/pulse.zig
+++ b/system/pulse/pulse.zig
@@ -8,8 +8,16 @@ export fn main(pc: u32, pv: [*]const [*:0]const u8) u8 {
_ = pc;
_ = pv;
+ // Spawn Shinigami - the zombie reaper
+ _ = kata.spawn("/system/shinigami/shinigami.gen") catch {
+ format.println("Pulse: FATAL - Failed to spawn Shinigami");
+ return 1;
+ };
+ format.println("Pulse: Shinigami started");
+
+ // Main shell loop
while (true) {
- const shell_pid = kata.spawn("/system/ash/ash.akiba") catch {
+ const shell_pid = kata.spawn("/system/ash/ash.gen") catch {
format.println("Pulse: Failed to spawn shell");
kata.yield();
continue;
diff --git a/system/shinigami/shinigami.zig b/system/shinigami/shinigami.zig
new file mode 100644
index 0000000..4f4973c
--- /dev/null
+++ b/system/shinigami/shinigami.zig
@@ -0,0 +1,17 @@
+//! Shinigami - The Soul Reaper
+//! Background service that reaps zombie katas
+
+const kata = @import("kata");
+
+export fn main(pc: u32, pv: [*]const [*:0]const u8) u8 {
+ _ = pc;
+ _ = pv;
+
+ // Shinigami runs forever, reaping zombie souls
+ while (true) {
+ _ = kata.reap();
+ kata.yield();
+ }
+
+ return 0;
+}
diff --git a/system/shinigami/shinigami.zon b/system/shinigami/shinigami.zon
new file mode 100644
index 0000000..2516af3
--- /dev/null
+++ b/system/shinigami/shinigami.zon
@@ -0,0 +1,9 @@
+.{
+ .name = "shinigami",
+ .version = "0.1.0",
+ .dependencies = .{
+ .sys,
+ .kata,
+ },
+ .entry = "shinigami.zig",
+}