mirror of
https://github.com/rdmsr/zag.git
synced 2026-10-27 13:58:20 +00:00
ke/smr: add support for preemptible SMR
This commit is contained in:
@@ -126,6 +126,12 @@ fn dispatch_queue(cpu: u32) void {
|
|||||||
ki.shootdown.process_shootdowns();
|
ki.shootdown.process_shootdowns();
|
||||||
|
|
||||||
if (sched_cpu.next_thread != null) {
|
if (sched_cpu.next_thread != null) {
|
||||||
|
const curtd = sched_cpu.current_thread.?;
|
||||||
|
|
||||||
|
if (!curtd.smr_sections.is_empty()) {
|
||||||
|
ki.smr.mark_thread_stalled(curtd);
|
||||||
|
}
|
||||||
|
|
||||||
ki.sched.handle_preemption(sched_cpu);
|
ki.sched.handle_preemption(sched_cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+222
-10
@@ -7,6 +7,7 @@ const std = @import("std");
|
|||||||
const config = @import("config");
|
const config = @import("config");
|
||||||
|
|
||||||
const ke = r.ke;
|
const ke = r.ke;
|
||||||
|
const ki = ke.private;
|
||||||
|
|
||||||
pub const Sequence = u64;
|
pub const Sequence = u64;
|
||||||
|
|
||||||
@@ -26,17 +27,29 @@ const Clock = struct {
|
|||||||
pub const Domain = struct {
|
pub const Domain = struct {
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
cpus: [*]Cpu,
|
cpus: [*]Cpu,
|
||||||
|
preempt: bool,
|
||||||
|
|
||||||
fn cpu(dom: *Domain) *Cpu {
|
fn cpu(dom: *Domain) *Cpu {
|
||||||
return &dom.cpus[ke.cpu.current()];
|
return &dom.cpus[ke.cpu.current()];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(self: *Domain) void {
|
pub fn init(self: *Domain, preempt: bool) void {
|
||||||
self.clock.read_seq = .init(seq_init);
|
self.clock.read_seq = .init(seq_init);
|
||||||
self.clock.write_seq = .init(seq_init);
|
self.clock.write_seq = .init(seq_init);
|
||||||
|
self.preempt = preempt;
|
||||||
|
|
||||||
for (0..ke.ncpus) |i| {
|
for (0..ke.ncpus) |i| {
|
||||||
self.cpus[i].current_seq = .init(seq_invalid);
|
self.cpus[i] = .{
|
||||||
|
.current_seq = .init(seq_invalid),
|
||||||
|
.stall_lock = .init(),
|
||||||
|
.stall_seq = .init(seq_invalid),
|
||||||
|
.stalled = undefined,
|
||||||
|
.stalled_owners = undefined,
|
||||||
|
.stall_goal = seq_invalid,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.cpus[i].stalled.init();
|
||||||
|
self.cpus[i].stalled_owners.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -45,16 +58,40 @@ pub const Domain = struct {
|
|||||||
pub const Cpu = struct {
|
pub const Cpu = struct {
|
||||||
/// Current observed sequence.
|
/// Current observed sequence.
|
||||||
current_seq: std.atomic.Value(Sequence),
|
current_seq: std.atomic.Value(Sequence),
|
||||||
|
/// Lock protecting stalled readers.
|
||||||
|
stall_lock: ke.SpinLock,
|
||||||
|
/// Stalled readers.
|
||||||
|
stalled: rtl.List,
|
||||||
|
/// Stalled owner list.
|
||||||
|
stalled_owners: rtl.List,
|
||||||
|
/// Oldest active stalled sequence.
|
||||||
|
stall_seq: std.atomic.Value(Sequence),
|
||||||
|
stall_goal: Sequence,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Tracker for preemptible SMR
|
||||||
|
pub const Tracker = struct {
|
||||||
|
link: rtl.List.Entry,
|
||||||
|
td_link: rtl.List.Entry,
|
||||||
|
dom: *Domain,
|
||||||
|
seq: Sequence,
|
||||||
|
cpu: ?*Cpu,
|
||||||
|
owner: ki.turnstile.Owner,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Used by tests.
|
||||||
|
pub var full_scans: std.atomic.Value(usize) = .init(0);
|
||||||
|
|
||||||
/// Scan all CPUs and return the minimum observed value.
|
/// Scan all CPUs and return the minimum observed value.
|
||||||
/// If `wait` is true, this will spinloop until all CPUs have reached the given goal.
|
/// If `wait` is true, this will spinloop (or block) until all CPUs have reached the given goal.
|
||||||
fn scan(dom: *Domain, goal: Sequence, clock: Clock, wait: bool) Sequence {
|
fn scan(dom: *Domain, goal: Sequence, clock: Clock, wait: bool) Sequence {
|
||||||
rtl.barrier.mb();
|
rtl.barrier.mb();
|
||||||
|
|
||||||
const clk_write = clock.write_seq.raw;
|
const clk_write = clock.write_seq.raw;
|
||||||
const clk_read = clock.read_seq.raw;
|
const clk_read = clock.read_seq.raw;
|
||||||
|
|
||||||
|
_ = full_scans.fetchAdd(1, .monotonic);
|
||||||
|
|
||||||
// Current minimum value.
|
// Current minimum value.
|
||||||
var read_seq = clk_write;
|
var read_seq = clk_write;
|
||||||
|
|
||||||
@@ -93,6 +130,57 @@ fn scan(dom: *Domain, goal: Sequence, clock: Clock, wait: bool) Sequence {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dom.preempt) {
|
||||||
|
rtl.barrier.mb();
|
||||||
|
|
||||||
|
// Preemption is enabled, check stalled readers.
|
||||||
|
for (0..ke.ncpus) |i| {
|
||||||
|
const cpu = &dom.cpus[i];
|
||||||
|
|
||||||
|
var seq = cpu.stall_seq.load(.monotonic);
|
||||||
|
|
||||||
|
while (seq != seq_invalid) {
|
||||||
|
if (seq < clk_read) {
|
||||||
|
seq = clk_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goal <= seq) {
|
||||||
|
// The goal has been reached.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wait) break;
|
||||||
|
|
||||||
|
const ipl = ke.ipl.raise(.Dispatch);
|
||||||
|
|
||||||
|
cpu.stall_lock.acquire_no_ipl();
|
||||||
|
cpu.stall_goal = goal;
|
||||||
|
cpu.stall_lock.release_no_ipl();
|
||||||
|
|
||||||
|
const ts = ki.turnstile.lookup(cpu);
|
||||||
|
|
||||||
|
seq = cpu.stall_seq.load(.monotonic);
|
||||||
|
|
||||||
|
// Re-check under the chain lock.
|
||||||
|
if (seq == seq_invalid or goal <= seq) {
|
||||||
|
ki.turnstile.exit(cpu);
|
||||||
|
ke.ipl.lower(ipl);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ki.turnstile.block(ts, cpu, .{ .shared = &cpu.stalled_owners }, .Exclusive);
|
||||||
|
ke.ipl.lower(ipl);
|
||||||
|
|
||||||
|
seq = cpu.stall_seq.load(.monotonic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seq != seq_invalid) {
|
||||||
|
// Update the minimum read sequence.
|
||||||
|
read_seq = @min(read_seq, seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The orderings here serve two purposes:
|
// The orderings here serve two purposes:
|
||||||
// 1. All subsequent operations are ordered after the sequence has advanced.
|
// 1. All subsequent operations are ordered after the sequence has advanced.
|
||||||
// 2. Fast-path pollers must see all operations before the updated read_seq.
|
// 2. Fast-path pollers must see all operations before the updated read_seq.
|
||||||
@@ -139,13 +227,21 @@ pub fn poll(dom: *Domain, goal: Sequence, wait: bool) bool {
|
|||||||
|
|
||||||
clk.write_seq.raw = dom.clock.write_seq.load(.monotonic);
|
clk.write_seq.raw = dom.clock.write_seq.load(.monotonic);
|
||||||
|
|
||||||
if (goal > clk.write_seq.raw) {
|
if (goal == clk.write_seq.raw + seq_incr) {
|
||||||
// The goal is a deferred advance that was never committed,
|
// The goal is a deferred advance that was never committed,
|
||||||
// there is nothing to wait for unless we commit it ourselves.
|
// there is nothing to wait for unless we commit it ourselves.
|
||||||
if (!wait) return false;
|
if (!wait) return false;
|
||||||
|
|
||||||
deferred_advance_commit(dom, goal);
|
if (dom.clock.write_seq.cmpxchgStrong(
|
||||||
clk.write_seq.raw = goal;
|
clk.write_seq.raw,
|
||||||
|
goal,
|
||||||
|
.monotonic,
|
||||||
|
.monotonic,
|
||||||
|
)) |val| {
|
||||||
|
clk.write_seq.raw = val;
|
||||||
|
} else {
|
||||||
|
clk.write_seq.raw = goal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldest = scan(dom, goal, clk, wait);
|
const oldest = scan(dom, goal, clk, wait);
|
||||||
@@ -181,8 +277,7 @@ pub fn deferred_advance_commit(dom: *Domain, seq: Sequence) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enter a read section.
|
/// Enter a read section.
|
||||||
pub fn enter(dom: *Domain) ke.Ipl {
|
fn enter_internal(dom: *Domain) Sequence {
|
||||||
const ipl = ke.ipl.raise(.Dispatch);
|
|
||||||
const cpu = dom.cpu();
|
const cpu = dom.cpu();
|
||||||
|
|
||||||
// Store the currently observed write sequence into our CPU state.
|
// Store the currently observed write sequence into our CPU state.
|
||||||
@@ -201,13 +296,24 @@ pub fn enter(dom: *Domain) ke.Ipl {
|
|||||||
// poll declared reclaimable. See scan().
|
// poll declared reclaimable. See scan().
|
||||||
std.debug.assert(cpu.current_seq.load(.monotonic) == seq_invalid);
|
std.debug.assert(cpu.current_seq.load(.monotonic) == seq_invalid);
|
||||||
|
|
||||||
|
const wr_seq = dom.clock.write_seq.load(.monotonic);
|
||||||
|
|
||||||
if (config.arch == .amd64) {
|
if (config.arch == .amd64) {
|
||||||
_ = cpu.current_seq.fetchAdd(dom.clock.write_seq.load(.monotonic), .seq_cst);
|
_ = cpu.current_seq.fetchAdd(wr_seq, .seq_cst);
|
||||||
} else {
|
} else {
|
||||||
cpu.current_seq.store(dom.clock.write_seq.load(.monotonic), .monotonic);
|
cpu.current_seq.store(wr_seq, .monotonic);
|
||||||
rtl.barrier.mb();
|
rtl.barrier.mb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return wr_seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enter a read section.
|
||||||
|
pub fn enter(dom: *Domain) ke.Ipl {
|
||||||
|
const ipl = ke.ipl.raise(.Dispatch);
|
||||||
|
|
||||||
|
_ = enter_internal(dom);
|
||||||
|
|
||||||
return ipl;
|
return ipl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,3 +326,109 @@ pub fn exit(dom: *Domain, ipl: ke.Ipl) void {
|
|||||||
|
|
||||||
ke.ipl.lower(ipl);
|
ke.ipl.lower(ipl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called by the scheduler when a thread in an SMR section got preempted.
|
||||||
|
pub fn mark_thread_stalled(td: *ke.Thread) void {
|
||||||
|
var it = td.smr_sections.iterator();
|
||||||
|
while (it.next()) : (it.advance()) {
|
||||||
|
const tracker: *Tracker = @fieldParentPtr("td_link", it.get());
|
||||||
|
|
||||||
|
if (tracker.cpu == null) {
|
||||||
|
const cpu = tracker.dom.cpu();
|
||||||
|
|
||||||
|
cpu.stall_lock.acquire_no_ipl();
|
||||||
|
|
||||||
|
if (cpu.stalled.is_empty()) {
|
||||||
|
cpu.stall_seq.store(tracker.seq, .monotonic);
|
||||||
|
}
|
||||||
|
|
||||||
|
tracker.cpu = cpu;
|
||||||
|
|
||||||
|
cpu.current_seq.store(seq_invalid, .release);
|
||||||
|
|
||||||
|
const turnstile = ki.turnstile.lookup(cpu);
|
||||||
|
|
||||||
|
cpu.stalled_owners.insert_tail(&tracker.owner.link);
|
||||||
|
|
||||||
|
if (turnstile) |ts| {
|
||||||
|
ki.turnstile.owner_enter(ts, &tracker.owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
ki.turnstile.exit(cpu);
|
||||||
|
|
||||||
|
cpu.stalled.insert_tail(&tracker.link);
|
||||||
|
cpu.stall_lock.release_no_ipl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enter a preemptible read section.
|
||||||
|
pub fn enter_preempt(dom: *Domain, tracker: *Tracker) void {
|
||||||
|
const ipl = ke.ipl.raise(.Dispatch);
|
||||||
|
const curtd = ke.thread.current();
|
||||||
|
|
||||||
|
const s = enter_internal(dom);
|
||||||
|
|
||||||
|
tracker.* = .{
|
||||||
|
.cpu = null,
|
||||||
|
.dom = dom,
|
||||||
|
.link = undefined,
|
||||||
|
.td_link = undefined,
|
||||||
|
.seq = s,
|
||||||
|
.owner = .{
|
||||||
|
.thread = curtd,
|
||||||
|
.link = undefined,
|
||||||
|
.boost = .{ .link = undefined, .donated = null },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
curtd.smr_sections.insert_tail(&tracker.td_link);
|
||||||
|
ke.ipl.lower(ipl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit a preemptible read section.
|
||||||
|
pub fn exit_preempt(dom: *Domain, tracker: *Tracker) void {
|
||||||
|
const ipl = ke.ipl.raise(.Dispatch);
|
||||||
|
|
||||||
|
tracker.td_link.remove();
|
||||||
|
|
||||||
|
if (tracker.cpu == null) {
|
||||||
|
// We didn't get preempted, simply exit as we would normally.
|
||||||
|
exit(dom, ipl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cpu = tracker.cpu.?;
|
||||||
|
|
||||||
|
// We got preempted.
|
||||||
|
cpu.stall_lock.acquire_no_ipl();
|
||||||
|
|
||||||
|
// Remove ourselves from the stalled list.
|
||||||
|
tracker.link.remove();
|
||||||
|
|
||||||
|
const turnstile = ki.turnstile.lookup(cpu);
|
||||||
|
|
||||||
|
tracker.owner.link.remove();
|
||||||
|
|
||||||
|
ki.turnstile.owner_leave(&tracker.owner);
|
||||||
|
|
||||||
|
var wake = false;
|
||||||
|
|
||||||
|
if (cpu.stalled.is_empty()) {
|
||||||
|
cpu.stall_seq.store(seq_invalid, .release);
|
||||||
|
wake = true;
|
||||||
|
} else {
|
||||||
|
const first: *Tracker = @fieldParentPtr("link", cpu.stalled.first());
|
||||||
|
cpu.stall_seq.store(first.seq, .release);
|
||||||
|
wake = cpu.stall_goal <= first.seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wake) {
|
||||||
|
if (turnstile) |ts| ki.turnstile.wakeup(ts, .Exclusive, ts.waiters, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
ki.turnstile.exit(cpu);
|
||||||
|
cpu.stall_lock.release_no_ipl();
|
||||||
|
|
||||||
|
ke.ipl.lower(ipl);
|
||||||
|
}
|
||||||
|
|||||||
+3
-1
@@ -118,6 +118,7 @@ pub const Thread = struct {
|
|||||||
/// This is used to avoid taking thread next lock to wait for switch off
|
/// This is used to avoid taking thread next lock to wait for switch off
|
||||||
/// to complete.
|
/// to complete.
|
||||||
switching: std.atomic.Value(bool),
|
switching: std.atomic.Value(bool),
|
||||||
|
smr_sections: rtl.List,
|
||||||
|
|
||||||
/// Initialize a thread.
|
/// Initialize a thread.
|
||||||
/// - `stack`: Address of the **base** of the stack on which the initial context for the thread is built
|
/// - `stack`: Address of the **base** of the stack on which the initial context for the thread is built
|
||||||
@@ -162,11 +163,12 @@ pub const Thread = struct {
|
|||||||
.est = ki.sched.pelt_load_avg_max,
|
.est = ki.sched.pelt_load_avg_max,
|
||||||
.period_contrib = 0,
|
.period_contrib = 0,
|
||||||
},
|
},
|
||||||
|
.smr_sections = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
thread.turnstiles_owned.init();
|
thread.turnstiles_owned.init();
|
||||||
|
|
||||||
thread.timer.init();
|
thread.timer.init();
|
||||||
|
thread.smr_sections.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn priority_class(self: *Thread) Priority.Class {
|
pub fn priority_class(self: *Thread) Priority.Class {
|
||||||
|
|||||||
+2
-2
@@ -1424,10 +1424,10 @@ pub fn late_init() linksection(r.init) void {
|
|||||||
smr_cpu_zone.init("SMR CPU", @sizeOf(ke.smr.Cpu) * ke.ncpus, .{});
|
smr_cpu_zone.init("SMR CPU", @sizeOf(ke.smr.Cpu) * ke.ncpus, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn smr_domain_create() !*ke.smr.Domain {
|
pub fn smr_domain_create(preempt: bool) !*ke.smr.Domain {
|
||||||
var dom: *ke.smr.Domain = try smr_zone.create();
|
var dom: *ke.smr.Domain = try smr_zone.create();
|
||||||
dom.cpus = @ptrCast(@alignCast(try smr_cpu_zone.alloc(.{})));
|
dom.cpus = @ptrCast(@alignCast(try smr_cpu_zone.alloc(.{})));
|
||||||
dom.init();
|
dom.init(preempt);
|
||||||
|
|
||||||
return dom;
|
return dom;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user