mirror of
https://github.com/rdmsr/zag.git
synced 2026-10-27 13:58:20 +00:00
ke+mm: add TLB shootdown and memory reclamation.
This commit is contained in:
@@ -116,6 +116,8 @@ fn particle(param: ?*anyopaque) void {
|
||||
data.act_y = parent_data.act_y;
|
||||
const explosion_range = parent_data.explosion_range;
|
||||
|
||||
mm.zone.gpa.destroy(parent_data);
|
||||
|
||||
const angle = @rem(rand(), 65536);
|
||||
data.vel_x = mul_fp_fp(cos(angle), rand_fp_sign()) * explosion_range;
|
||||
data.vel_y = mul_fp_fp(sin(angle), rand_fp_sign()) * explosion_range;
|
||||
|
||||
+4
-47
@@ -8,64 +8,21 @@ const ps = r.ps;
|
||||
const amd64 = @import("arch");
|
||||
const turnstile = ke.private.turnstile;
|
||||
|
||||
var mtx: ke.Mutex = .init();
|
||||
var shared_counter: usize = 0;
|
||||
|
||||
var work_item: ex.WorkItem = undefined;
|
||||
var work_item2: ex.WorkItem = undefined;
|
||||
|
||||
fn mutex_test(_: ?*anyopaque) void {
|
||||
while (true) {
|
||||
mtx.acquire();
|
||||
shared_counter += 1;
|
||||
mtx.release();
|
||||
}
|
||||
}
|
||||
|
||||
fn foo(_: ?*anyopaque) void {
|
||||
std.log.info("foo cpu {}", .{ke.cpu.current()});
|
||||
var timer: ke.Timer = undefined;
|
||||
timer.init();
|
||||
ke.timer.set(&timer, std.time.ns_per_ms * 500, null);
|
||||
_ = ke.wait.wait_one(&timer.hdr, null) catch unreachable;
|
||||
}
|
||||
|
||||
fn bar(_: ?*anyopaque) void {
|
||||
std.log.info("bar cpu {}", .{ke.cpu.current()});
|
||||
}
|
||||
|
||||
pub fn init(boot_info: *pl.BootInfo) void {
|
||||
mm.init(boot_info);
|
||||
ps.init();
|
||||
|
||||
pl.late_init(boot_info);
|
||||
mm.late_init();
|
||||
ke.sched.late_init();
|
||||
ps.init();
|
||||
ex.private.workqueue.init() catch @panic("e");
|
||||
|
||||
work_item = .init(.Normal, foo, null);
|
||||
work_item2 = .init(.Normal, bar, null);
|
||||
|
||||
ex.workqueue.enqueue(&work_item);
|
||||
ex.workqueue.enqueue(&work_item2);
|
||||
|
||||
if (false) {
|
||||
if (boot_info.framebuffer != null) {
|
||||
const t = ps.thread.create_kernel(ke.Thread.Priority.default, ex.fireworks.start, boot_info) catch @panic("oom");
|
||||
ke.sched.enqueue(&t.kern);
|
||||
}
|
||||
|
||||
while (true) {}
|
||||
}
|
||||
|
||||
const ipl = ke.ipl.raise(.Dispatch);
|
||||
|
||||
for (0..10) |_| {
|
||||
const t = ps.thread.create_kernel(ke.Thread.Priority.default, mutex_test, null) catch @panic("oom");
|
||||
if (boot_info.framebuffer != null) {
|
||||
const t = ps.thread.create_kernel(ke.Thread.Priority.default, ex.fireworks.start, boot_info) catch @panic("oom");
|
||||
ke.sched.enqueue(&t.kern);
|
||||
}
|
||||
|
||||
ke.ipl.lower(ipl);
|
||||
while (true) {}
|
||||
|
||||
while (true) {
|
||||
std.atomic.spinLoopHint();
|
||||
|
||||
@@ -5,6 +5,8 @@ const amd64 = r.arch;
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const tlb_max_pages = 33;
|
||||
|
||||
pub const init = @import("init.zig");
|
||||
pub const early_init = init.early_init;
|
||||
|
||||
@@ -114,3 +116,19 @@ pub inline fn restore_interrupts(state: bool) void {
|
||||
pub inline fn send_resched_ipi(target: u32) void {
|
||||
pl.impl.send_resched_ipi(target);
|
||||
}
|
||||
|
||||
pub inline fn send_tlb_ipi(target: u32) void {
|
||||
pl.impl.send_tlb_ipi(target);
|
||||
}
|
||||
|
||||
pub inline fn flush_full_tlb() void {
|
||||
const prev_cr3 = amd64.read_cr(3);
|
||||
// Reload CR3 to flush the entire TLB.
|
||||
// This works because we assume global pages are never unmapped.
|
||||
// If they are in the future, this should toggle CR4.PGE instead.
|
||||
amd64.write_cr(3, prev_cr3);
|
||||
}
|
||||
|
||||
pub inline fn flush_tlb(va: r.VAddr) void {
|
||||
amd64.invlpg(va);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const r = @import("root");
|
||||
const ke = r.ke;
|
||||
const ki = ke.private;
|
||||
const pl = r.pl;
|
||||
const mi = r.mm.private;
|
||||
|
||||
extern const __interrupt_vectors: [256]usize;
|
||||
|
||||
@@ -84,6 +85,10 @@ export fn isr_handler_main(frame: *const amd64.IrqFrame) callconv(.{ .x86_64_sys
|
||||
ki.timer.clock();
|
||||
}
|
||||
|
||||
if (frame.intno == 241) {
|
||||
ki.shootdown.ipi_handler();
|
||||
}
|
||||
|
||||
if (frame.intno >= 32) {
|
||||
pl.impl.apic.eoi();
|
||||
}
|
||||
|
||||
@@ -121,6 +121,8 @@ fn dispatch_queue(cpu: u32) void {
|
||||
sched_cpu.start_timer = false;
|
||||
sched_cpu.preemption_reason = .None;
|
||||
|
||||
ki.shootdown.process_shootdowns();
|
||||
|
||||
if (sched_cpu.next_thread != null) {
|
||||
ki.sched.handle_preemption(sched_cpu);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ pub fn init(boot_info: *pl.BootInfo) linksection(r.init) void {
|
||||
pl.early_init(boot_info);
|
||||
ki.cpu.init_cpu(0);
|
||||
ki.turnstile.init_turnstiles();
|
||||
ki.shootdown.init();
|
||||
|
||||
thread0.init(@intFromPtr(&stack), r.kib(16), ke.Thread.Priority.idle, ki.sched.idle, null);
|
||||
thread0.priority = 0;
|
||||
|
||||
@@ -28,3 +28,4 @@ pub const event = @import("event.zig");
|
||||
pub const turnstile = @import("turnstile.zig");
|
||||
pub const mutex = @import("mutex.zig");
|
||||
pub const queue = @import("queue.zig");
|
||||
pub const shootdown = @import("shootdown.zig");
|
||||
|
||||
@@ -17,9 +17,11 @@ pub const Console = p.log.Console;
|
||||
pub const CpuLocal = p.cpu.CpuLocal;
|
||||
pub const ExportedCpuLocal = p.cpu.ExportedCpuLocal;
|
||||
pub const CpuMask = p.cpu.CpuMask;
|
||||
pub const AtomicCpuMask = p.cpu.AtomicCpuMask;
|
||||
pub const Event = p.event.Event;
|
||||
pub const Queue = p.queue.Queue;
|
||||
pub const Mutex = p.mutex.Mutex;
|
||||
pub const ShootdownState = p.shootdown.ShootdownState;
|
||||
|
||||
// === Exported Interfaces ===
|
||||
pub const panic = p.panic.panic;
|
||||
@@ -72,5 +74,10 @@ pub const cpu = struct {
|
||||
pub const current = p.cpu.current;
|
||||
};
|
||||
|
||||
pub const shootdown = struct {
|
||||
pub const submit = p.shootdown.submit;
|
||||
pub const shootdowns = &p.shootdown.shootdowns;
|
||||
};
|
||||
|
||||
/// Number of CPUs on the system
|
||||
pub var ncpus: usize = 0;
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
//! Kernel-side mechanism for asynchronous TLB shootdown and deferred memory reclamation.
|
||||
|
||||
const std = @import("std");
|
||||
const config = @import("config");
|
||||
const rtl = @import("rtl");
|
||||
const r = @import("root");
|
||||
const ke = r.ke;
|
||||
const ki = ke.private;
|
||||
const mm = r.mm;
|
||||
|
||||
comptime {
|
||||
if (!@hasDecl(ki.impl, "send_tlb_ipi")) @compileError("impl must provide send_tlb_ipi");
|
||||
if (!@hasDecl(ki.impl, "flush_full_tlb")) @compileError("impl must provide flush_full_tlb");
|
||||
if (!@hasDecl(ki.impl, "flush_tlb")) @compileError("impl must provide flush_tlb");
|
||||
}
|
||||
|
||||
/// Structure representing a single shootdown request.
|
||||
pub const ShootdownState = struct {
|
||||
/// State of the request.
|
||||
/// This is used as a counter when the request is outstanding,
|
||||
/// otherwise it is set to the `slot_*` values below.
|
||||
state: std.atomic.Value(u16),
|
||||
link: rtl.List.Entry,
|
||||
/// Base virtual address of the shootdown.
|
||||
base: r.VAddr,
|
||||
/// Number of pages to flush.
|
||||
npages: u32,
|
||||
/// Additional metadata.
|
||||
payload: [2]usize,
|
||||
|
||||
pub fn release(self: *ShootdownState) void {
|
||||
self.state.store(slot_free, .release);
|
||||
}
|
||||
};
|
||||
|
||||
const PerCpu = struct {
|
||||
/// States attached to this CPU.
|
||||
states: [64]ShootdownState,
|
||||
/// States relevant to each CPU.
|
||||
/// If CPU0 sent a shootdown to CPU1, then CPU0 will have `valid_states[CPU1]`
|
||||
/// pointing to that shootdown, which will sit in `states`.
|
||||
valid_states: [config.ncpus]std.atomic.Value(u64),
|
||||
/// CPUs that have sent this CPU a shootdown.
|
||||
senders: ke.AtomicCpuMask,
|
||||
};
|
||||
|
||||
const slot_free: u16 = std.math.maxInt(u16);
|
||||
const slot_reserved: u16 = std.math.maxInt(u16) - 1;
|
||||
const percpu = ke.CpuLocal(PerCpu, undefined);
|
||||
|
||||
pub var shootdowns: ke.Queue = undefined;
|
||||
|
||||
var sync_counter: std.atomic.Value(usize) = .init(0);
|
||||
var sync_addr: r.VAddr = 0;
|
||||
var sync_count: usize = 0;
|
||||
var shootdown_lock: ke.SpinLock = .init();
|
||||
|
||||
export const tlb_percpu_init linksection(r.percpu_init) = &pcpu_init;
|
||||
|
||||
fn pcpu_init() linksection(r.init) callconv(.c) void {
|
||||
const local = percpu.local();
|
||||
|
||||
local.valid_states = @splat(.init(0));
|
||||
local.senders = .init(false);
|
||||
local.states = @splat(.{ .base = 0, .npages = 0, .state = .init(slot_free), .payload = undefined, .link = undefined });
|
||||
}
|
||||
|
||||
/// Allocate an invalidation state slot on this CPU.
|
||||
/// O(N) algorithm but N is tiny.
|
||||
fn allocate_slot(cpu: *PerCpu) ?usize {
|
||||
for (0.., &cpu.states) |slot, *state| {
|
||||
if (state.state.load(.monotonic) != slot_free) continue;
|
||||
|
||||
if (state.state.cmpxchgStrong(slot_free, slot_reserved, .acquire, .monotonic) == null) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn flush_range(va: r.VAddr, npages: usize) void {
|
||||
if (npages > ki.impl.tlb_max_pages) {
|
||||
// Just flush the entire thing.
|
||||
ki.impl.flush_full_tlb();
|
||||
return;
|
||||
}
|
||||
|
||||
for (0..npages) |i| {
|
||||
ki.impl.flush_tlb(va + i * mm.page_size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronous shootdown path.
|
||||
fn do_synchronous_shootdown(state: ShootdownState) void {
|
||||
shootdown_lock.acquire_no_ipl();
|
||||
|
||||
sync_addr = state.base;
|
||||
sync_count = state.npages;
|
||||
sync_counter.store(ke.ncpus - 1, .release);
|
||||
|
||||
const cur = ke.cpu.current();
|
||||
|
||||
for (0..ke.ncpus) |i| {
|
||||
if (i == cur) continue;
|
||||
ki.impl.send_tlb_ipi(@truncate(i));
|
||||
}
|
||||
|
||||
while (sync_counter.load(.monotonic) != 0) {
|
||||
std.atomic.spinLoopHint();
|
||||
}
|
||||
|
||||
shootdown_lock.release_no_ipl();
|
||||
}
|
||||
|
||||
/// Called by the TLB interrupt handler.
|
||||
pub fn ipi_handler() void {
|
||||
flush_range(sync_addr, sync_count);
|
||||
_ = sync_counter.fetchSub(1, .release);
|
||||
}
|
||||
|
||||
/// Called in a quiescent state to process the pending shootdowns.
|
||||
/// This must be called at IPL dispatch.
|
||||
pub fn process_shootdowns() void {
|
||||
const cpu = percpu.local();
|
||||
const curcpu = ke.cpu.current();
|
||||
|
||||
if (cpu.senders.is_all(false, .monotonic)) {
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
var iter = cpu.senders.iter(.monotonic);
|
||||
|
||||
// Go through every sender and get their states.
|
||||
while (iter.next()) |i| {
|
||||
if (i == curcpu) continue;
|
||||
|
||||
cpu.senders.clear(i, .monotonic);
|
||||
|
||||
const sender = percpu.remote(@truncate(i));
|
||||
|
||||
while (true) {
|
||||
var states = sender.valid_states[curcpu].swap(0, .acquire);
|
||||
if (states == 0) break;
|
||||
|
||||
while (states != 0) {
|
||||
// Go through each set bit and invalidate the slot.
|
||||
const bit = @ctz(states);
|
||||
states &= ~(@as(u64, 1) << @intCast(bit));
|
||||
|
||||
const state = &sender.states[bit];
|
||||
flush_range(state.base, state.npages);
|
||||
|
||||
if (state.state.fetchSub(1, .release) == 1) {
|
||||
shootdowns.insert(&state.link, .Tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Submit a shootdown to occur asynchronously on `target_mask`.
|
||||
/// Returns an error if a synchronous shootdown was done instead.
|
||||
pub fn submit(state: ShootdownState, target_mask: ke.CpuMask) !void {
|
||||
const ipl = ke.ipl.raise(.Dispatch);
|
||||
defer ke.ipl.lower(ipl);
|
||||
|
||||
// Flush on our local TLB.
|
||||
flush_range(state.base, state.npages);
|
||||
|
||||
// Get a slot and fill it.
|
||||
const curcpu = ke.cpu.current();
|
||||
const cpu = percpu.local();
|
||||
const slot = allocate_slot(cpu) orelse {
|
||||
do_synchronous_shootdown(state);
|
||||
return error.NoSlot;
|
||||
};
|
||||
|
||||
cpu.states[slot] = state;
|
||||
cpu.states[slot].state.store(@truncate(target_mask.count()), .release);
|
||||
|
||||
var iter = target_mask.iter();
|
||||
|
||||
while (iter.next()) |bit| {
|
||||
if (bit == curcpu) continue;
|
||||
|
||||
_ = cpu.valid_states[bit].fetchOr(@as(u64, 1) << @truncate(slot), .release);
|
||||
|
||||
ki.ipl.set_softint_pending(@truncate(bit), .Dispatch);
|
||||
|
||||
// Tell him we have something for him.
|
||||
percpu.remote(@truncate(bit)).senders.set(curcpu, .release);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() void {
|
||||
shootdowns.init(ke.ncpus);
|
||||
}
|
||||
+7
-1
@@ -16,8 +16,14 @@ pub fn alloc(size: usize) mm.Error!*anyopaque {
|
||||
mi.kernel_space.pmap.map_range_allocating(addr, size, .{
|
||||
.read = true,
|
||||
.write = true,
|
||||
.global = true,
|
||||
.global = false,
|
||||
});
|
||||
|
||||
return @ptrFromInt(addr);
|
||||
}
|
||||
|
||||
pub fn free(va: r.VAddr, size: usize) void {
|
||||
const ipl = mi.kernel_space.lock.acquire();
|
||||
mi.tlb.reclaim_range(&mi.kernel_space, va, size);
|
||||
mi.kernel_space.lock.release(ipl);
|
||||
}
|
||||
|
||||
@@ -103,4 +103,5 @@ pub fn init(boot_info: *pl.BootInfo) linksection(r.init) void {
|
||||
|
||||
pub fn late_init() linksection(r.init) void {
|
||||
mi.zone.late_init();
|
||||
mi.tlb.init();
|
||||
}
|
||||
|
||||
+5
-9
@@ -16,13 +16,11 @@ const ke = r.ke;
|
||||
|
||||
const log = std.log.scoped(.@"mm/phys");
|
||||
|
||||
// === Early state ===
|
||||
var bootstrapped = false;
|
||||
var early_alloc_entry_idx: usize = 0;
|
||||
var memory_map: *pl.BootInfo.MemMap = undefined;
|
||||
var early_allocs: usize = 0;
|
||||
|
||||
// === Memory allocator state ===
|
||||
/// Head for the global treiber stack.
|
||||
const GlobalHead = packed struct(u64) {
|
||||
pfn: mm.Pfn,
|
||||
@@ -41,13 +39,11 @@ const PerCpu = struct {
|
||||
|
||||
const percpu = ke.CpuLocal(PerCpu, undefined);
|
||||
|
||||
const null_pfn: mm.Pfn = std.math.maxInt(mm.Pfn);
|
||||
|
||||
// TODO: make this configurable (or dynamic).
|
||||
const batch_size: u8 = 64;
|
||||
|
||||
var batch_pool: std.atomic.Value(GlobalHead) = .init(.{
|
||||
.pfn = null_pfn,
|
||||
.pfn = mm.null_pfn,
|
||||
.tag = 0,
|
||||
});
|
||||
|
||||
@@ -83,7 +79,7 @@ fn pop_from_pool() ?*mm.Page {
|
||||
while (true) {
|
||||
const batch_pfn = old_head.pfn;
|
||||
|
||||
if (batch_pfn == null_pfn) {
|
||||
if (batch_pfn == mm.null_pfn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -177,15 +173,15 @@ pub fn free(addr: r.PAddr) void {
|
||||
|
||||
const page = mm.pfn_to_struct_page(mm.page_to_pfn(addr));
|
||||
|
||||
page.free.next_pfn = null_pfn;
|
||||
page.free.batch_next = null_pfn;
|
||||
page.free.next_pfn = mm.null_pfn;
|
||||
page.free.batch_next = mm.null_pfn;
|
||||
page.free.batch_count = 0;
|
||||
|
||||
if (cpu.active_batch) |curr| {
|
||||
// Add the page to the current batch.
|
||||
page.free.next_pfn = mm.struct_page_to_pfn(curr);
|
||||
} else {
|
||||
page.free.next_pfn = null_pfn;
|
||||
page.free.next_pfn = mm.null_pfn;
|
||||
}
|
||||
|
||||
cpu.active_batch = page;
|
||||
|
||||
@@ -103,6 +103,17 @@ pub const PMap = struct {
|
||||
self.map_from(va, size, src);
|
||||
}
|
||||
|
||||
/// Unmap a contiguous range of virtual pages.
|
||||
/// Returns a list threaded through PFNs of the unmapped physical pages if any.
|
||||
pub fn unmap(self: *Self, va: r.VAddr, size: usize) ?mm.Pfn {
|
||||
return self.impl.unmap(va, size);
|
||||
}
|
||||
|
||||
/// Flush the TLB entry of a single page.
|
||||
pub fn flush(self: *Self, va: r.VAddr) void {
|
||||
self.impl.flush(va);
|
||||
}
|
||||
|
||||
/// Map a single page. Convenience wrapper around map_contiguous_range.
|
||||
pub fn map_page(self: *Self, va: r.VAddr, pa: r.PAddr, flags: mm.MapFlags) void {
|
||||
self.map_contiguous_range(va, pa, mm.page_size, flags);
|
||||
|
||||
+6
-1
@@ -7,6 +7,7 @@ pub const vmem = @import("vmem.zig");
|
||||
pub const heap = @import("heap.zig");
|
||||
pub const zone = @import("zone.zig");
|
||||
pub const PMap = pmap.PMap;
|
||||
pub const tlb = @import("tlb.zig");
|
||||
|
||||
const ke = @import("root").ke;
|
||||
|
||||
@@ -22,4 +23,8 @@ pub const impl = switch (config.arch) {
|
||||
else => @compileError("unsupported architecture"),
|
||||
};
|
||||
|
||||
pub var kernel_space: Space = undefined;
|
||||
pub var kernel_space: Space = .{
|
||||
.pmap = undefined,
|
||||
.arena = undefined,
|
||||
.lock = .init(),
|
||||
};
|
||||
|
||||
@@ -109,6 +109,46 @@ pub fn RadixPmap(comptime Impl: type) type {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unmap a contiguous range of virtual pages.
|
||||
/// Only small pages are supported.
|
||||
/// Return a pfn that points to the physical pages that were unmapped.
|
||||
pub fn unmap(self: *Self, va: r.VAddr, size: usize) ?mm.Pfn {
|
||||
std.debug.assert(std.mem.isAligned(va, mm.page_size));
|
||||
|
||||
var c = self.cursor(va);
|
||||
|
||||
const npages: usize = size / mm.page_size;
|
||||
var head_pfn: mm.Pfn = mm.null_pfn;
|
||||
|
||||
for (0..npages) |_| {
|
||||
var level: usize = num_levels - 1;
|
||||
blk: while (true) {
|
||||
const idx = index_for_level(c.va, level);
|
||||
const pte = c.tables[level][idx];
|
||||
|
||||
if (!pte.present) break :blk;
|
||||
|
||||
if (level == 0) {
|
||||
// Found a present entry, add it to the list.
|
||||
const addr = pte.address();
|
||||
const pfn = mm.page_to_pfn(addr);
|
||||
const page = mm.pfn_to_struct_page(pfn);
|
||||
page.free.next_pfn = head_pfn;
|
||||
head_pfn = pfn;
|
||||
c.tables[level][idx] = std.mem.zeroes(Impl.Pte);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!c.walk_down_resolve(level - 1)) return null;
|
||||
level -= 1;
|
||||
}
|
||||
|
||||
c.advance(mm.page_size);
|
||||
}
|
||||
|
||||
return if (head_pfn == mm.null_pfn) null else head_pfn;
|
||||
}
|
||||
|
||||
/// Ensure all intermediate page tables down to `target_level` exist for
|
||||
/// the given virtual address range, allocating them as needed. Useful for
|
||||
/// pre-populating a shared region (e.g. the kernel half of a user pmap)
|
||||
|
||||
@@ -15,6 +15,7 @@ pub const Space = private.Space;
|
||||
// 16 TB of maximum physical memory.
|
||||
// This should be fine on consumer hardware for at least a decade :^)
|
||||
pub const Pfn = u32;
|
||||
pub const null_pfn: Pfn = std.math.maxInt(Pfn);
|
||||
|
||||
pub const Page = extern union {
|
||||
free: extern struct {
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
//! MM-level TLB shootdown support
|
||||
|
||||
const r = @import("root");
|
||||
const std = @import("std");
|
||||
const rtl = @import("rtl");
|
||||
const ke = r.ke;
|
||||
const mm = r.mm;
|
||||
const mi = mm.private;
|
||||
const ps = r.ps;
|
||||
|
||||
pub var sync_shootdowns: std.atomic.Value(usize) = .init(0);
|
||||
pub var async_shootdowns: std.atomic.Value(usize) = .init(0);
|
||||
|
||||
fn free_pages(head_pfn: mm.Pfn) void {
|
||||
var pfn = head_pfn;
|
||||
|
||||
// Free all the physical pages.
|
||||
while (pfn != mm.null_pfn) {
|
||||
const page = mm.pfn_to_struct_page(pfn);
|
||||
const next = page.free.next_pfn;
|
||||
mm.phys.free(mm.pfn_to_page(pfn));
|
||||
pfn = next;
|
||||
}
|
||||
}
|
||||
|
||||
/// Reclaim the memory associated with a state.
|
||||
/// This can only be done when all CPUs have flushed their TLB.
|
||||
fn reclaim_state(state: *ke.ShootdownState) void {
|
||||
const space: *mm.Space = @ptrFromInt(state.payload[0]);
|
||||
const pfn: u32 = @truncate(state.payload[1]);
|
||||
const base = state.base;
|
||||
const npages = state.npages;
|
||||
|
||||
// Reclaim the virtual address space.
|
||||
const ipl = space.lock.acquire();
|
||||
|
||||
space.arena.free(base, @as(usize, npages) * mm.page_size) catch unreachable;
|
||||
space.lock.release(ipl);
|
||||
|
||||
_ = async_shootdowns.fetchAdd(1, .monotonic);
|
||||
|
||||
// Free the physical pages
|
||||
free_pages(pfn);
|
||||
|
||||
// Release the slot after all copied state has been consumed.
|
||||
state.release();
|
||||
}
|
||||
|
||||
fn worker_thread(_: ?*anyopaque) void {
|
||||
while (true) {
|
||||
const entry = ke.shootdown.shootdowns.remove();
|
||||
const state: *ke.ShootdownState = @fieldParentPtr("link", entry);
|
||||
|
||||
_ = async_shootdowns.fetchAdd(1, .monotonic);
|
||||
reclaim_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unmap and flush a virtual range on the given space.
|
||||
/// Space lock is held on entry and IPL is raised to Dispatch.
|
||||
pub fn reclaim_range(space: *mm.Space, va: r.VAddr, size: usize) void {
|
||||
std.debug.assert(std.mem.isAligned(va, mm.page_size));
|
||||
|
||||
// Unmap the virtual addresses and get the backing physical pages.
|
||||
const head_pfn = space.pmap.unmap(va, size) orelse {
|
||||
// If no physical pages, free the VA directly.
|
||||
space.arena.free(va, size) catch unreachable;
|
||||
return;
|
||||
};
|
||||
|
||||
const state: ke.ShootdownState = .{
|
||||
.base = va,
|
||||
.npages = @truncate(size / mm.page_size),
|
||||
.link = undefined,
|
||||
.state = .init(0),
|
||||
.payload = .{
|
||||
@intFromPtr(space),
|
||||
@intCast(head_pfn),
|
||||
},
|
||||
};
|
||||
|
||||
var mask: ke.CpuMask = .init(false);
|
||||
|
||||
for (0..ke.ncpus) |i| {
|
||||
if (i != ke.cpu.current()) {
|
||||
mask.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
ke.shootdown.submit(state, mask) catch {
|
||||
_ = sync_shootdowns.fetchAdd(1, .monotonic);
|
||||
space.arena.free(va, size) catch unreachable;
|
||||
free_pages(head_pfn);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init() void {
|
||||
const td = ps.thread.create_kernel(ke.Thread.Priority.default, worker_thread, null) catch unreachable;
|
||||
ke.sched.enqueue(&td.kern);
|
||||
}
|
||||
+7
-5
@@ -20,8 +20,10 @@ const Slab = struct {
|
||||
/// Reference count indicating how many objects are in use.
|
||||
refcount: u16,
|
||||
capacity: u16,
|
||||
/// Base address of the slab.
|
||||
/// Base address of the slab + its color.
|
||||
base: usize,
|
||||
/// Base address of the slab allocation.
|
||||
buf: usize,
|
||||
|
||||
pub fn bitmap(self: *Slab) [*]u64 {
|
||||
return @ptrFromInt(@intFromPtr(self) + @sizeOf(Slab));
|
||||
@@ -623,6 +625,7 @@ pub const Zone = struct {
|
||||
if (rem > 0) bm[full_words] = (@as(u64, 1) << @intCast(rem)) - 1;
|
||||
|
||||
ret.base = @intFromPtr(buf) + self.color;
|
||||
ret.buf = @intFromPtr(buf);
|
||||
ret.capacity = @intCast(capacity);
|
||||
ret.refcount = 0;
|
||||
|
||||
@@ -667,7 +670,7 @@ pub const Zone = struct {
|
||||
const alloc_size = @sizeOf(Slab) + Slab.bitmap_bytes(slab.capacity);
|
||||
gpa.free(@as([*]u8, @ptrCast(slab))[0..alloc_size]);
|
||||
|
||||
// FIXME: call free_pages on slab.base here!
|
||||
mm.heap.free(slab.buf, self.slab_size);
|
||||
} else {
|
||||
free_page(@ptrFromInt(std.mem.alignBackward(usize, @intFromPtr(slab), mm.page_size)));
|
||||
}
|
||||
@@ -842,9 +845,8 @@ fn gpa_free(
|
||||
_: usize,
|
||||
) void {
|
||||
if (buf.len > 2048) {
|
||||
//const pages = std.mem.alignForward(usize, buf.len, mm.page_size);
|
||||
// TODO
|
||||
//mi.heap.free_pages(@ptrCast(buf.ptr), pages);
|
||||
const pages = std.mem.alignForward(usize, buf.len, mm.page_size);
|
||||
mm.heap.free(@intFromPtr(buf.ptr), pages);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,3 +109,7 @@ pub fn arm_timer(deadline: r.Nanoseconds) void {
|
||||
pub fn send_resched_ipi(target_cpu: u32) void {
|
||||
apic.send_ipi(smp.cpu_id_to_apic_id[target_cpu], 240, 0);
|
||||
}
|
||||
|
||||
pub fn send_tlb_ipi(target_cpu: u32) void {
|
||||
apic.send_ipi(smp.cpu_id_to_apic_id[target_cpu], 241, 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user