From 77fd0ce4cf2515eb3d0ec846a2827de042f4a5d8 Mon Sep 17 00:00:00 2001 From: Jack Halford Date: Tue, 22 Aug 2023 00:27:27 +0200 Subject: [PATCH] towards 0.10 --- src/arch/x86/idt.zig | 24 +++---- src/arch/x86/interrupt.zig | 6 +- src/driver/index.zig | 4 +- src/index.zig | 10 +-- src/pci/pci.zig | 22 +++--- src/ps2.zig | 6 +- src/task.zig | 140 +++++++++++++++++++------------------ src/vga.zig | 2 +- src/vmem.zig | 74 ++++++-------------- 9 files changed, 132 insertions(+), 156 deletions(-) diff --git a/src/arch/x86/idt.zig b/src/arch/x86/idt.zig index c3229d2..babbc10 100644 --- a/src/arch/x86/idt.zig +++ b/src/arch/x86/idt.zig @@ -58,32 +58,32 @@ pub fn initialize() void { x86.isr.install_exceptions(); x86.isr.install_irqs(); x86.isr.install_syscalls(); - x86.interrupt.registerIRQ(0, kernel.time.increment); - x86.interrupt.registerIRQ(1, kernel.ps2.keyboard_handler); - x86.interrupt.register(1, debug_trap); - x86.interrupt.register(13, general_protection_fault); - x86.interrupt.register(14, page_fault); + // x86.interrupt.registerIRQ(0, kernel.time.increment); + // x86.interrupt.registerIRQ(1, kernel.ps2.keyboard_handler); + // x86.interrupt.register(1, debug_trap); + // x86.interrupt.register(13, general_protection_fault); + // x86.interrupt.register(14, page_fault); // load IDT x86.instr.lidt(@ptrToInt(&idtr)); } fn general_protection_fault() void { - kernel.println("general protection fault", .{}); + kernel.vga.println("general protection fault", .{}); x86.instr.hang(); } fn debug_trap() void { - kernel.println("debug fault/trap", .{}); - kernel.println("dr7: 0b{b}", .{x86.instr.dr7()}); + kernel.vga.println("debug fault/trap", .{}); + kernel.vga.println("dr7: 0b{b}", .{x86.instr.dr7()}); } fn page_fault() void { const vaddr = x86.instr.cr2(); - kernel.println("cr2: 0x{x}", .{vaddr}); - kernel.println("phy: 0x{x}", .{kernel.paging.translate(vaddr)}); - kernel.println("pde: 0x{x} ({})", .{ kernel.paging.pde(vaddr), vaddr >> 22 }); - kernel.println("pte: 0x{x} ({})", .{ kernel.paging.pte(vaddr), vaddr >> 12 }); + kernel.vga.println("cr2: 0x{x}", .{vaddr}); + kernel.vga.println("phy: 0x{x}", .{x86.paging.translate(vaddr)}); + kernel.vga.println("pde: 0x{x} ({})", .{ x86.paging.pde(vaddr), vaddr >> 22 }); + kernel.vga.println("pte: 0x{x} ({})", .{ x86.paging.pte(vaddr), vaddr >> 12 }); // paging.format(); x86.instr.hang(); } diff --git a/src/arch/x86/interrupt.zig b/src/arch/x86/interrupt.zig index 680a002..fe51cec 100644 --- a/src/arch/x86/interrupt.zig +++ b/src/arch/x86/interrupt.zig @@ -31,7 +31,7 @@ const IRQ_15 = IRQ_0 + 15; const SYSCALL = 128; // Registered interrupt handlers. (see x86.isr.s) -var handlers = [_]fn () void{unhandled} ** 48; +const handlers = [_]fn () void{unhandled} ** 48; // Registered IRQ subscribers. (see x86.isr.s) // var irq_subscribers = []MailboxId{MailboxId.Kernel} ** 16; @@ -130,11 +130,11 @@ inline fn endOfInterrupt(irq: u8) void { } } -pub fn register(n: u8, handler: fn () void) void { +pub fn register(n: u8, comptime handler: fn () void) void { handlers[n] = handler; } -pub fn registerIRQ(irq: u8, handler: fn () void) void { +pub fn registerIRQ(irq: u8, comptime handler: fn () void) void { register(IRQ_0 + irq, handler); maskIRQ(irq, false); // Unmask the IRQ. } diff --git a/src/driver/index.zig b/src/driver/index.zig index 1f4e3e9..1c5dd6f 100644 --- a/src/driver/index.zig +++ b/src/driver/index.zig @@ -1,6 +1,6 @@ -pub usingnamespace @import("../common.zig"); +// pub usingnamespace @import("../common.zig"); -pub const kernel = @import("../index.zig"); +// pub const kernel = @import("../index.zig"); pub const x86 = @import("../arch/x86/index.zig"); diff --git a/src/index.zig b/src/index.zig index c9bf802..c060a62 100644 --- a/src/index.zig +++ b/src/index.zig @@ -1,11 +1,11 @@ +pub const dq = @import("delta_queue.zig"); // pub usingnamespace @import("common.zig"); -// pub usingnamespace @import("delta_queue.zig"); // pub usingnamespace @import("ring_buffer.zig"); pub const vga = @import("vga.zig"); //drivers -// pub const driver = @import("driver/index.zig"); +pub const driver = @import("driver/index.zig"); //arch pub const x86 = @import("arch/x86/index.zig"); @@ -15,11 +15,11 @@ pub const constants = @import("constants.zig"); pub const layout = @import("layout.zig"); pub const multiboot = @import("multiboot.zig"); pub const vmem = @import("vmem.zig"); -// pub const task = @import("task.zig"); +pub const task = @import("task.zig"); pub const time = @import("time.zig"); //extra // pub const console = @import("console.zig"); // pub const bio = @import("bio.zig"); -// pub const pci = @import("pci/pci.zig"); -// pub const ps2 = @import("ps2.zig"); +pub const pci = @import("pci/pci.zig"); +pub const ps2 = @import("ps2.zig"); diff --git a/src/pci/pci.zig b/src/pci/pci.zig index 4567329..fd9bf7c 100644 --- a/src/pci/pci.zig +++ b/src/pci/pci.zig @@ -1,6 +1,8 @@ -pub usingnamespace @import("../index.zig"); -pub const virtio = @import("virtio.zig"); -pub const ide = @import("ide.zig"); +pub const std = @import("std"); + +pub const kernel = @import("index.zig"); +pub const x86 = @import("x86.zig"); +pub const driver = @import("driver/index.zig"); const PCI_CONFIG_ADDRESS = 0xCF8; const PCI_CONFIG_DATA = 0xCFC; @@ -41,10 +43,10 @@ pub const PciDevice = struct { } pub fn format(self: PciDevice) void { - print("{}:{}.{}", .{ self.bus, self.slot, self.function }); - print(" {x},{x:2}", .{ self.class(), self.subclass() }); - print(" 0x{x} 0x{x}", .{ self.vendor, self.device() }); - println(" {}", .{if (self.driver()) |d| d.name else " (none)"}); + kernel.vga.print("{}:{}.{}", .{ self.bus, self.slot, self.function }); + kernel.vga.print(" {x},{x:2}", .{ self.class(), self.subclass() }); + kernel.vga.print(" 0x{x} 0x{x}", .{ self.vendor, self.device() }); + kernel.vga.println(" {}", .{if (self.driver()) |d| d.name else " (none)"}); } pub fn driver(self: PciDevice) ?Driver { @@ -94,7 +96,7 @@ pub const PciDevice = struct { return self.config_read(u8, 0x3c); } pub fn bar(self: PciDevice, comptime n: usize) u32 { - assert(n <= 5); + std.debug.assert(n <= 5); return self.config_read(u32, 0x10 + 4 * n); } // only for header_type == 0 @@ -102,7 +104,7 @@ pub const PciDevice = struct { return self.config_read(u8, 0x2e); } - pub inline fn config_write(self: PciDevice, value: var, comptime offset: u8) void { + pub inline fn config_write(self: PciDevice, value: anytype, comptime offset: u8) void { // ask for access before writing config x86.outl(PCI_CONFIG_ADDRESS, self.address(offset)); switch (@TypeOf(value)) { @@ -172,7 +174,7 @@ pub fn scan() void { pub fn lspci() void { var slot: u5 = 0; - println("b:s.f c, s vendor device driver", .{}); + kernel.vga.println("b:s.f c, s vendor device driver", .{}); while (slot < 31) : (slot += 1) { if (PciDevice.init(0, slot, 0)) |dev| { var function: u3 = 0; diff --git a/src/ps2.zig b/src/ps2.zig index 3075a8e..5238d4e 100644 --- a/src/ps2.zig +++ b/src/ps2.zig @@ -1,5 +1,5 @@ usingnamespace @import("index.zig"); -// const x86 = @import("x86"); +const x86 = @import("x86"); const PS2_DATA = 0x60; const PS2_STATUS = 0x64; @@ -67,8 +67,8 @@ const KEYMAP_US = [_][]const u8{ fn ps2_scancode() u8 { var scancode: u8 = 0; - while (true) if (x86.inb(PS2_DATA) != scancode) { - scancode = x86.inb(PS2_DATA); + while (true) if (x86.io.inb(PS2_DATA) != scancode) { + scancode = x86.io.inb(PS2_DATA); if (scancode > 0) return scancode; }; } diff --git a/src/task.zig b/src/task.zig index b246aed..c6e202f 100644 --- a/src/task.zig +++ b/src/task.zig @@ -1,25 +1,27 @@ -pub usingnamespace @import("index.zig"); +const kernel = @import("kernel"); +const std = @import("std"); +const x86 = @import("x86"); var boot_task = Task{ .tid = 0, .esp = 0x47, .state = .Running, .born = true }; var tid_counter: u16 = 1; pub const TaskNode = std.TailQueue(*Task).Node; -pub const SleepNode = DeltaQueue(*TaskNode).Node; +pub const SleepNode = kernel.dq.DeltaQueue(*TaskNode).Node; pub var current_task: *TaskNode = &TaskNode.init(&boot_task); pub var cleaner_task: *TaskNode = undefined; pub var ready_tasks = std.TailQueue(*Task).init(); pub var blocked_tasks = std.TailQueue(*Task).init(); pub var terminated_tasks = std.TailQueue(*Task).init(); -pub var sleeping_tasks = DeltaQueue(*TaskNode).init(); +pub var sleeping_tasks = kernel.dq.DeltaQueue(*TaskNode).init(); const STACK_SIZE = x86.PAGE_SIZE; // Size of thread stacks. var timer_last_count: u64 = 0; pub fn update_time_used() void { - const current_count = time.offset_us; + const current_count = kernel.time.offset_us; const elapsed = current_count - timer_last_count; - // if (current_task.data.tid == 1) println("{} adding {} time", current_task.data.tid, elapsed); + // if (current_task.data.tid == 1) kernel.vga.println("{} adding {} time", current_task.data.tid, elapsed); timer_last_count = current_count; current_task.data.time_used += elapsed; } @@ -45,22 +47,22 @@ pub const Task = struct { pub fn create(entrypoint: usize) !*Task { // Allocate and initialize the thread structure. - var t = try vmem.allocator.create(Task); - errdefer vmem.allocator.destroy(t); + var t = try kernel.vmem.allocator.create(Task); + errdefer kernel.vmem.allocator.destroy(t); t.time_used = 0; t.state = .ReadyToRun; t.tid = tid_counter; tid_counter +%= 1; - assert(tid_counter != 0); //overflow + std.debug.assert(tid_counter != 0); //overflow // allocate a new stack - t.stack = try vmem.allocator.create([STACK_SIZE]u8); + t.stack = try kernel.vmem.allocator.create([STACK_SIZE]u8); t.esp = @ptrToInt(t.stack) + STACK_SIZE; // if the tasks rets from its main function, it will go to terminate // NOTE: if terminate is called this way it has an incorrect ebp! t.esp -= 4; - @intToPtr(*usize, t.esp).* = @ptrToInt(task.terminate); + @intToPtr(*usize, t.esp).* = @ptrToInt(terminate); // this will be what ret goes to t.esp -= 4; @intToPtr(*usize, t.esp).* = entrypoint; @@ -72,8 +74,8 @@ pub const Task = struct { } pub fn destroy(self: *Task) void { - vmem.allocator.destroy(self.stack); - vmem.allocator.destroy(self); + kernel.vmem.allocator.destroy(self.stack); + kernel.vmem.allocator.destroy(self); } }; @@ -81,11 +83,11 @@ pub const Task = struct { extern fn switch_tasks(new_esp: usize, old_esp_addr: usize) void; pub fn new(entrypoint: usize) !*TaskNode { - task.lock_scheduler(); - defer task.unlock_scheduler(); + kernel.task.lock_scheduler(); + defer kernel.task.unlock_scheduler(); - const node = try vmem.allocator.create(TaskNode); - errdefer vmem.allocator.destroy(node); + const node = try kernel.vmem.allocator.create(TaskNode); + errdefer kernel.vmem.allocator.destroy(node); node.data = try Task.create(entrypoint); ready_tasks.prepend(node); @@ -93,16 +95,16 @@ pub fn new(entrypoint: usize) !*TaskNode { } pub fn wakeup_tick(tick: usize) bool { - task.lock_scheduler(); - defer task.unlock_scheduler(); + kernel.task.lock_scheduler(); + defer kernel.task.unlock_scheduler(); - task.sleeping_tasks.decrement(tick); + kernel.task.sleeping_tasks.decrement(tick); var popped = false; - while (task.sleeping_tasks.popZero()) |sleepnode| { + while (kernel.task.sleeping_tasks.popZero()) |sleepnode| { const tasknode = sleepnode.data; tasknode.data.state = .ReadyToRun; - vmem.allocator.destroy(sleepnode); - task.ready_tasks.prepend(tasknode); + kernel.vmem.allocator.destroy(sleepnode); + ready_tasks.prepend(tasknode); popped = true; } return popped; @@ -110,10 +112,10 @@ pub fn wakeup_tick(tick: usize) bool { // TODO: make a sleep without malloc pub fn usleep(usec: u64) !void { - assert(current_task.data.state == .Running); + std.debug.assert(current_task.data.state == .Running); update_time_used(); - const node = try vmem.allocator.create(SleepNode); + const node = try kernel.vmem.allocator.create(SleepNode); lock_scheduler(); defer unlock_scheduler(); @@ -126,9 +128,9 @@ pub fn usleep(usec: u64) !void { } pub fn block(state: TaskState) void { - assert(current_task.data.state == .Running); - assert(state != .Running); - assert(state != .ReadyToRun); + std.debug.assert(current_task.data.state == .Running); + std.debug.assert(state != .Running); + std.debug.assert(state != .ReadyToRun); update_time_used(); // println("blocking {} as {}", current_task.data.tid, state); @@ -161,7 +163,7 @@ pub fn unblock(node: *TaskNode) void { } pub fn terminate() noreturn { - assert(current_task.data.state == .Running); + std.debug.assert(current_task.data.state == .Running); lock_scheduler(); current_task.data.state = .Terminated; @@ -179,7 +181,7 @@ pub fn cleaner_loop() noreturn { if (terminated_tasks.popFirst()) |n| { notify("DESTROYING {}", .{n.data.tid}); n.data.destroy(); - vmem.allocator.destroy(n); + kernel.vmem.allocator.destroy(n); } else { notify("NOTHING TO CLEAN", .{}); block(.Paused); @@ -191,15 +193,15 @@ pub var IRQ_disable_counter: usize = 0; pub var postpone_task_switches_counter: isize = 0; // this counter can go negative when we are scheduling after a postpone pub var postpone_task_switches_flag: bool = false; fn lock_scheduler() void { - if (constants.SMP == false) { - x86.cli(); + if (kernel.constants.SMP == false) { + x86.instr.cli(); IRQ_disable_counter += 1; postpone_task_switches_counter += 1; } } fn unlock_scheduler() void { - if (constants.SMP == false) { - assert(IRQ_disable_counter > 0); + if (kernel.constants.SMP == false) { + std.debug.assert(IRQ_disable_counter > 0); postpone_task_switches_counter -= 1; if (postpone_task_switches_flag == true and postpone_task_switches_counter == 0) { postpone_task_switches_flag = false; @@ -208,7 +210,7 @@ fn unlock_scheduler() void { } IRQ_disable_counter -= 1; // must be the last instruction because we do interrupts inside interrupts - if (IRQ_disable_counter == 0) x86.sti(); + if (IRQ_disable_counter == 0) x86.instr.sti(); } } @@ -218,7 +220,7 @@ pub fn preempt() void { update_time_used(); if (ready_tasks.first == null) { notify("NO PREEMPT SINGLE TASK", .{}); - time.task_slice_remaining = 0; + kernel.time.task_slice_remaining = 0; return; } @@ -234,8 +236,8 @@ pub fn preempt() void { // - scheduler is locked // - the tasks being switched to will unlock_scheduler() pub fn switch_to(chosen: *TaskNode) void { - assert(chosen.data.state == .ReadyToRun); - assert(current_task.data.state != .Running); + std.debug.assert(chosen.data.state == .ReadyToRun); + std.debug.assert(current_task.data.state != .Running); // save old stack const old_task_esp_addr = ¤t_task.data.esp; @@ -243,8 +245,8 @@ pub fn switch_to(chosen: *TaskNode) void { // switch states chosen.data.state = .Running; current_task = chosen; - if (ready_tasks.first == null) time.task_slice_remaining = 0; - if (ready_tasks.first != null) time.task_slice_remaining = time.TASK_SLICE; + if (ready_tasks.first == null) kernel.time.task_slice_remaining = 0; + if (ready_tasks.first != null) kernel.time.task_slice_remaining = kernel.time.TASK_SLICE; // we don't have any startup code for tasks, so i do it here if (current_task.data.born == false) { @@ -267,8 +269,8 @@ pub var CPU_idle_start_time: u64 = 0; // unlock_scheduler should be called after // current_task is blocked or running (preemption) pub fn schedule() void { - assert(IRQ_disable_counter > 0); - assert(current_task.data.state != .ReadyToRun); + std.debug.assert(IRQ_disable_counter > 0); + std.debug.assert(current_task.data.state != .ReadyToRun); // postponed if (postpone_task_switches_counter != 0 and current_task.data.state == .Running) { @@ -291,7 +293,7 @@ pub fn schedule() void { // single task if (current_task.data.state == .Running) { notify("SINGLE TASK", .{}); - time.task_slice_remaining = 0; + kernel.time.task_slice_remaining = 0; return; } // no tasks @@ -299,21 +301,21 @@ pub fn schedule() void { } fn idle_mode() void { - assert(ready_tasks.first == null); - assert(current_task.data.state != .Running); - assert(current_task.data.state != .ReadyToRun); + std.debug.assert(ready_tasks.first == null); + std.debug.assert(current_task.data.state != .Running); + std.debug.assert(current_task.data.state != .ReadyToRun); notify("IDLE", .{}); // borrow the current task const borrow = current_task; - CPU_idle_start_time = time.offset_us; //for power management + CPU_idle_start_time = kernel.time.offset_us; //for power management while (true) { // idle loop if (ready_tasks.popFirst()) |t| { // found a new task - CPU_idle_time += time.offset_us - CPU_idle_start_time; // count time as idle - timer_last_count = time.offset_us; // don't count time as used + CPU_idle_time += kernel.time.offset_us - CPU_idle_start_time; // count time as idle + timer_last_count = kernel.time.offset_us; // don't count time as used // println("went into idle mode for {}usecs", time.offset_us - CPU_idle_start_time); if (t == borrow) { @@ -322,43 +324,43 @@ fn idle_mode() void { } return switch_to(t); } else { // no tasks ready, let the timer fire - x86.sti(); // enable interrupts to allow the timer to fire - x86.hlt(); // halt and wait for the timer to fire - x86.cli(); // disable interrupts again to see if there is something to do + x86.instr.sti(); // enable interrupts to allow the timer to fire + x86.instr.hlt(); // halt and wait for the timer to fire + x86.instr.cli(); // disable interrupts again to see if there is something to do } } } -pub fn notify(comptime message: []const u8, args: var) void { - const bg = vga.background; - const fg = vga.foreground; - const cursor = vga.cursor; - vga.background = fg; - vga.foreground = bg; - vga.cursor = 80 - message.len; - vga.cursor_enabled = false; +pub fn notify(comptime message: []const u8, args: anytype) void { + const bg = kernel.vga.background; + const fg = kernel.vga.foreground; + const cursor = kernel.vga.cursor; + kernel.vga.background = fg; + kernel.vga.foreground = bg; + kernel.vga.cursor = 80 - message.len; + kernel.vga.cursor_enabled = false; - print(message, args); + kernel.vga.print(message, args); - vga.cursor_enabled = true; - vga.cursor = cursor; - vga.background = bg; - vga.foreground = fg; + kernel.vga.cursor_enabled = true; + kernel.vga.cursor = cursor; + kernel.vga.background = bg; + kernel.vga.foreground = fg; } pub fn format_short() void { - print("{}R {}B {}S", .{ ready_tasks.len, blocked_tasks.len, sleeping_tasks.len }); + kernel.vga.print("{}R {}B {}S", .{ ready_tasks.len, blocked_tasks.len, sleeping_tasks.len }); } pub fn format() void { update_time_used(); - println("{}", .{current_task.data}); + kernel.vga.println("{}", .{current_task.data}); var it = ready_tasks.first; - while (it) |node| : (it = node.next) println("{}", .{node.data}); + while (it) |node| : (it = node.next) kernel.vga.println("{}", .{node.data}); it = blocked_tasks.first; - while (it) |node| : (it = node.next) println("{}", .{node.data}); + while (it) |node| : (it = node.next) kernel.vga.println("{}", .{node.data}); var sit = sleeping_tasks.first; - while (sit) |node| : (sit = node.next) println("{} {}", .{ node.data.data, node.counter }); + while (sit) |node| : (sit = node.next) kernel.vga.println("{} {}", .{ node.data.data, node.counter }); } diff --git a/src/vga.zig b/src/vga.zig index c80d8f1..bab936b 100644 --- a/src/vga.zig +++ b/src/vga.zig @@ -91,7 +91,7 @@ pub fn topbar() void { } } -fn printCallback(string: []const u8) Errors!void { +fn printCallback(string: []const u8) anyerror!void { vga.writeString(string); } diff --git a/src/vmem.zig b/src/vmem.zig index c5a30ca..4c88147 100644 --- a/src/vmem.zig +++ b/src/vmem.zig @@ -18,67 +18,39 @@ pub fn available() usize { return stack_index * x86.PAGE_SIZE; } -fn dealloc(addr: usize) void { +fn free(addr: usize) void { x86.paging.unmap(addr); stack[stack_index] = addr; stack_index += 1; } -const Error = error{OutOfMemory}; +// const Error = error{OutOfMemory}; -fn realloc( - self: *std.mem.Allocator, - old_mem: []u8, - old_alignment: u29, - new_byte_count: usize, - new_alignment: u29, -) ![]u8 { - if (old_mem.len == 0) { - // new allocation - std.debug.assert(new_byte_count < x86.PAGE_SIZE); // this allocator only support 1:1 mapping - if (available() == 0) return error.OutOfMemory; - stack_index -= 1; - var vaddr: usize = stack[stack_index]; - try x86.paging.mmap(vaddr, null); - return @intToPtr([*]u8, vaddr)[0..new_byte_count]; - } - // free - if (new_byte_count == 0) { - dealloc(@ptrToInt(&old_mem[0])); - return &[_]u8{}; - } - kernel.vga.println("vmem: unsupported allocator operation", .{}); - x86.hang(); - // return undefined; -} - -fn shrink( - self: *std.mem.Allocator, - old_mem: []u8, - old_alignment: u29, - new_byte_count: usize, - new_alignment: u29, -) []u8 { - // free - if (new_byte_count == 0) { - dealloc(@ptrToInt(&old_mem[0])); - return &[_]u8{}; - } - - kernel.vga.println("vmem doesn't support shrinking, {}, {}, {}, {}", .{ - old_mem, - old_alignment, - new_byte_count, - new_alignment, - }); - x86.hang(); - // return undefined; +fn alloc( + ctx: *anyopaque, + len: usize, + ptr_align: u8, + ret_addr: usize, +) ?[*]u8 { + // new allocation + std.debug.assert(len < x86.PAGE_SIZE); // this allocator only support 1:1 mapping + if (available() == 0) return error.OutOfMemory; + stack_index -= 1; + var vaddr: usize = stack[stack_index]; + try x86.paging.mmap(vaddr, null); + return @intToPtr([*]u8, vaddr)[0..len]; } pub fn init() void { allocator = std.mem.Allocator{ - .reallocFn = realloc, - .shrinkFn = shrink, + .ptr = undefined, + .vtable = std.mem.Allocator.VTable{ + .alloc = alloc, + .resize = undefined, + .free = free, + } + // .reallocFn = realloc, + // .shrinkFn = shrink, }; var addr: usize = kernel.layout.HEAP; while (addr < kernel.layout.HEAP_END) : (addr += x86.PAGE_SIZE) {