ke/sched: add hard affinity cpumask

This commit is contained in:
Loïc Grégoire
2026-08-20 21:19:50 -04:00
parent 3204a5de6b
commit f6f20599ba
2 changed files with 40 additions and 26 deletions
+38 -26
View File
@@ -589,7 +589,7 @@ fn calendar_queue_increment(cpu: *PerCpu) void {
} }
} }
fn pick_realtime_thread(cpu: *PerCpu, minimum_prio: u8, migrate: bool) ?*ke.Thread { fn pick_realtime_thread(cpu: *PerCpu, minimum_prio: u8, target: ?u32) ?*ke.Thread {
std.debug.assert(cpu.queues_lock.is_locked()); std.debug.assert(cpu.queues_lock.is_locked());
const runq = &cpu.realtime_queue; const runq = &cpu.realtime_queue;
@@ -620,9 +620,9 @@ fn pick_realtime_thread(cpu: *PerCpu, minimum_prio: u8, migrate: bool) ?*ke.Thre
td.state.store(.Selected, .monotonic); td.state.store(.Selected, .monotonic);
if (migrate) { if (target) |tgt| {
// Find the first thread that is not pinned. // Find the first thread that is not pinned and can run on this CPU.
while (td.pinned) { while (td.pinned or td.hard_affinity.get(tgt) == false) {
const entry = td.runq_link.next; const entry = td.runq_link.next;
// Every thread is pinned, go through another queue // Every thread is pinned, go through another queue
@@ -646,7 +646,7 @@ fn pick_realtime_thread(cpu: *PerCpu, minimum_prio: u8, migrate: bool) ?*ke.Thre
return null; return null;
} }
fn pick_batch_thread(cpu: *PerCpu, migrate: bool) ?*ke.Thread { fn pick_batch_thread(cpu: *PerCpu, target: ?u32) ?*ke.Thread {
std.debug.assert(cpu.queues_lock.is_locked()); std.debug.assert(cpu.queues_lock.is_locked());
const runq = &cpu.calendar_queue; const runq = &cpu.calendar_queue;
@@ -679,9 +679,9 @@ fn pick_batch_thread(cpu: *PerCpu, migrate: bool) ?*ke.Thread {
td.state.store(.Selected, .monotonic); td.state.store(.Selected, .monotonic);
if (migrate) { if (target) |tgt| {
// Find the first thread that is not pinned. // Find the first thread that is not pinned and can run on this CPU.
while (td.pinned) { while (td.pinned or td.hard_affinity.get(tgt) == false) {
const entry = td.runq_link.next; const entry = td.runq_link.next;
// Every thread is pinned, go through another queue // Every thread is pinned, go through another queue
@@ -698,7 +698,7 @@ fn pick_batch_thread(cpu: *PerCpu, migrate: bool) ?*ke.Thread {
// Queue is now empty, clear its bit and increment runidx. // Queue is now empty, clear its bit and increment runidx.
runq.status &= ~bit; runq.status &= ~bit;
if (!migrate) { if (target == null) {
calendar_queue_increment(cpu); calendar_queue_increment(cpu);
} }
} }
@@ -709,18 +709,24 @@ fn pick_batch_thread(cpu: *PerCpu, migrate: bool) ?*ke.Thread {
return null; return null;
} }
fn pick_idle_thread(cpu: *PerCpu, migrate: bool) ?*ke.Thread { fn pick_idle_thread(cpu: *PerCpu, target: ?u32) ?*ke.Thread {
std.debug.assert(cpu.queues_lock.is_locked()); std.debug.assert(cpu.queues_lock.is_locked());
if (cpu.idle_queue.is_empty()) return null; if (cpu.idle_queue.is_empty()) return null;
// Just get the first thread from the queue. // Just get the first thread from the queue that can run.
const td: *ke.Thread = @fieldParentPtr( var td: *ke.Thread = @fieldParentPtr(
"runq_link", "runq_link",
cpu.idle_queue.first(), cpu.idle_queue.first(),
); );
if (migrate and td.pinned) { if (target) |tgt| {
return null; while (td.pinned or td.hard_affinity.get(tgt) == false) {
const entry = td.runq_link.next;
if (entry == &cpu.idle_queue.head) return null;
td = @fieldParentPtr("runq_link", entry);
}
} }
td.state.store(.Selected, .monotonic); td.state.store(.Selected, .monotonic);
@@ -820,12 +826,12 @@ fn remove_from_queue(cpu: *PerCpu, td: *ke.Thread) void {
} }
// Find a thread to preempt `cur` with. // Find a thread to preempt `cur` with.
fn select_thread(cur: ?*ke.Thread, cpu: *PerCpu, migrate: bool) ?*ke.Thread { fn select_thread(cur: ?*ke.Thread, cpu: *PerCpu, target: ?u32) ?*ke.Thread {
// First try to pick from the realtime queues. // First try to pick from the realtime queues.
if (pick_realtime_thread( if (pick_realtime_thread(
cpu, cpu,
if (cur) |c| c.priority else 0, if (cur) |c| c.priority else 0,
migrate, target,
)) |td| { )) |td| {
_ = cpu.load.fetchSub(1, .monotonic); _ = cpu.load.fetchSub(1, .monotonic);
if (!td.pinned) { if (!td.pinned) {
@@ -842,7 +848,7 @@ fn select_thread(cur: ?*ke.Thread, cpu: *PerCpu, migrate: bool) ?*ke.Thread {
} }
// Then try picking a batch thread. // Then try picking a batch thread.
if (pick_batch_thread(cpu, migrate)) |td| { if (pick_batch_thread(cpu, target)) |td| {
_ = cpu.load.fetchSub(1, .monotonic); _ = cpu.load.fetchSub(1, .monotonic);
if (!td.pinned) { if (!td.pinned) {
_ = cpu.migratable.fetchSub(1, .monotonic); _ = cpu.migratable.fetchSub(1, .monotonic);
@@ -856,7 +862,7 @@ fn select_thread(cur: ?*ke.Thread, cpu: *PerCpu, migrate: bool) ?*ke.Thread {
} }
// Finally try picking from the idle queue. // Finally try picking from the idle queue.
if (pick_idle_thread(cpu, migrate)) |td| { if (pick_idle_thread(cpu, target)) |td| {
_ = cpu.load.fetchSub(1, .monotonic); _ = cpu.load.fetchSub(1, .monotonic);
if (!td.pinned) { if (!td.pinned) {
_ = cpu.migratable.fetchSub(1, .monotonic); _ = cpu.migratable.fetchSub(1, .monotonic);
@@ -939,10 +945,11 @@ fn pick_cpu(td: *ke.Thread, slept: u64) u32 {
// least loaded preemptible one. // least loaded preemptible one.
// We try to pick the CPU on which we can run immediately, but if there is // We try to pick the CPU on which we can run immediately, but if there is
// a tie we use the average load to break it. // a tie we use the average load to break it.
const local = percpu.local(); const local = percpu.local();
local.pick_offset +%= 1; local.pick_offset +%= 1;
const mask = td.hard_affinity;
var least: ?u32 = null; var least: ?u32 = null;
var least_load: usize = 0; var least_load: usize = 0;
var least_preempt: ?u32 = null; var least_preempt: ?u32 = null;
@@ -952,6 +959,11 @@ fn pick_cpu(td: *ke.Thread, slept: u64) u32 {
for (0..ke.ncpus) |n| { for (0..ke.ncpus) |n| {
const i = (local.pick_offset + n) % ke.ncpus; const i = (local.pick_offset + n) % ke.ncpus;
if (!mask.get(i)) {
continue;
}
const cpu: u32 = @truncate(i); const cpu: u32 = @truncate(i);
const data = percpu.remote(cpu); const data = percpu.remote(cpu);
const depth = data.load.load(.monotonic); const depth = data.load.load(.monotonic);
@@ -1158,7 +1170,7 @@ pub fn clock(_: *ke.Dpc, _: ?*anyopaque) void {
} }
// Pick a new thread to run. // Pick a new thread to run.
const newtd = select_thread(curtd, cpu, false); const newtd = select_thread(curtd, cpu, null);
// Ensure the timer gets reloaded. // Ensure the timer gets reloaded.
cpu.start_timer = true; cpu.start_timer = true;
@@ -1212,7 +1224,7 @@ pub fn yield_locked() void {
sched_cpu.next_thread = null; sched_cpu.next_thread = null;
} else { } else {
// Pick a new thread to run. // Pick a new thread to run.
next = select_thread(null, sched_cpu, false); next = select_thread(null, sched_cpu, null);
} }
if (next == null) { if (next == null) {
@@ -1364,7 +1376,7 @@ pub fn update_priority_locked(td: *ke.Thread, new_prio: u8) void {
const next = pick_realtime_thread( const next = pick_realtime_thread(
cpu, cpu,
new_prio + 1, new_prio + 1,
false, null,
) orelse return; ) orelse return;
_ = cpu.load.fetchSub(1, .monotonic); _ = cpu.load.fetchSub(1, .monotonic);
@@ -1474,14 +1486,14 @@ fn find_least_loaded(exclude: ?*ke.CpuMask) ?u32 {
return least; return least;
} }
fn steal_thread_from_cpu(cpu: *PerCpu, curcpu: ?*PerCpu) ?*ke.Thread { fn steal_thread_from_cpu(cpu: *PerCpu, curcpu: ?*PerCpu, target: ?u32) ?*ke.Thread {
cpu.queues_lock.acquire_no_ipl(); cpu.queues_lock.acquire_no_ipl();
defer cpu.queues_lock.release_no_ipl(); defer cpu.queues_lock.release_no_ipl();
if (curcpu != null and curcpu.?.load.load(.monotonic) > 0) if (curcpu != null and curcpu.?.load.load(.monotonic) > 0)
return null; return null;
const td = select_thread(null, cpu, true); const td = select_thread(null, cpu, target);
if (td) |t| { if (td) |t| {
detach_load_avg(cpu, t); detach_load_avg(cpu, t);
@@ -1506,7 +1518,7 @@ fn steal_work(cpu: u32) ?*ke.Thread {
} }
const ipl = ke.ipl.raise(.Dispatch); const ipl = ke.ipl.raise(.Dispatch);
const td = steal_thread_from_cpu(sched_other, c); const td = steal_thread_from_cpu(sched_other, c, cpu);
ke.ipl.lower(ipl); ke.ipl.lower(ipl);
return td; return td;
} }
@@ -1565,7 +1577,7 @@ fn balance(_: *ke.Dpc, _: ?*anyopaque) void {
// Steal a thread from the high CPU and put it on the low CPU. // Steal a thread from the high CPU and put it on the low CPU.
const sched_high = percpu.remote(high.?); const sched_high = percpu.remote(high.?);
const td = steal_thread_from_cpu(sched_high, null); const td = steal_thread_from_cpu(sched_high, null, low.?);
if (td) |thread| { if (td) |thread| {
thread.lock.acquire_no_ipl(); thread.lock.acquire_no_ipl();
+2
View File
@@ -131,6 +131,7 @@ pub const Thread = struct {
/// to complete. /// to complete.
switching: std.atomic.Value(bool), switching: std.atomic.Value(bool),
smr_sections: rtl.List, smr_sections: rtl.List,
hard_affinity: ke.CpuMask,
/// Initialize a thread. /// Initialize a thread.
/// - `stack`: Address of the base of the stack used by the thread. /// - `stack`: Address of the base of the stack used by the thread.
@@ -180,6 +181,7 @@ pub const Thread = struct {
.avg = .{}, .avg = .{},
.acct = .{}, .acct = .{},
.smr_sections = undefined, .smr_sections = undefined,
.hard_affinity = .init(true),
}; };
thread.turnstiles_owned.init(); thread.turnstiles_owned.init();