[Threading] Add thread priority mapping with timer-driven quantum decay
And default ignore_thread_priorities to false. Map Xenon's 0-31 priority range across all 5 host priority levels instead of collapsing 0-17 into kNormal. Use timer-driven quantum decay (~20ms period) matching Xenon's 60-quantum / 3-per-tick cycle to prevent starvation by gradually lowering effective priority for non-real-time threads (< 18), piggybacking on the existing 1ms timestamp timer.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#endif
|
||||
|
||||
#include "xenia/base/byte_stream.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
@@ -24,8 +25,9 @@
|
||||
#include "xenia/kernel/user_module.h"
|
||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h"
|
||||
|
||||
DEFINE_bool(ignore_thread_priorities, true,
|
||||
DEFINE_bool(ignore_thread_priorities, false,
|
||||
"Ignores game-specified thread priorities.", "Kernel");
|
||||
UPDATE_from_bool(ignore_thread_priorities, 2026, 4, 9, 12, true);
|
||||
DEFINE_bool(ignore_thread_affinities, true,
|
||||
"Ignores game-specified thread affinities.", "Kernel");
|
||||
|
||||
@@ -670,28 +672,87 @@ void XThread::RundownAPCs() {
|
||||
|
||||
int32_t XThread::QueryPriority() { return thread_->priority(); }
|
||||
|
||||
void XThread::SetPriority(int32_t increment) {
|
||||
if (is_guest_thread()) {
|
||||
guest_object<X_KTHREAD>()->priority = static_cast<uint8_t>(increment);
|
||||
}
|
||||
priority_ = increment;
|
||||
int32_t target_priority = 0;
|
||||
if (increment > 0x22) {
|
||||
target_priority = xe::threading::ThreadPriority::kHighest;
|
||||
} else if (increment > 0x11) {
|
||||
target_priority = xe::threading::ThreadPriority::kAboveNormal;
|
||||
} else if (increment < -0x22) {
|
||||
target_priority = xe::threading::ThreadPriority::kLowest;
|
||||
} else if (increment < -0x11) {
|
||||
target_priority = xe::threading::ThreadPriority::kBelowNormal;
|
||||
// Map Xenon's 0-31 priority range across the available host priority levels.
|
||||
// Priority 18 (0x12) is the Xenon real-time threshold — threads at or above
|
||||
// it don't get quantum decay on real hardware.
|
||||
static int32_t GuestPriorityToHost(int32_t guest_priority) {
|
||||
if (guest_priority >= 24) {
|
||||
return xe::threading::ThreadPriority::kHighest;
|
||||
} else if (guest_priority >= 17) {
|
||||
return xe::threading::ThreadPriority::kAboveNormal;
|
||||
} else if (guest_priority >= 10) {
|
||||
return xe::threading::ThreadPriority::kNormal;
|
||||
} else if (guest_priority >= 5) {
|
||||
return xe::threading::ThreadPriority::kBelowNormal;
|
||||
} else {
|
||||
target_priority = xe::threading::ThreadPriority::kNormal;
|
||||
return xe::threading::ThreadPriority::kLowest;
|
||||
}
|
||||
}
|
||||
|
||||
void XThread::SetPriority(int32_t increment) {
|
||||
// Clamp to valid Xenon priority range. Negative values can arrive via
|
||||
// KeSetBasePriorityThread (signed offset from process base).
|
||||
int32_t clamped = std::max(increment, 0);
|
||||
if (is_guest_thread()) {
|
||||
guest_object<X_KTHREAD>()->priority = static_cast<uint8_t>(clamped);
|
||||
}
|
||||
priority_ = clamped;
|
||||
base_priority_ = clamped;
|
||||
quantum_start_ms_ = Clock::QueryHostUptimeMillis();
|
||||
if (!cvars::ignore_thread_priorities) {
|
||||
thread_->set_priority(target_priority);
|
||||
thread_->set_priority(GuestPriorityToHost(clamped));
|
||||
}
|
||||
}
|
||||
|
||||
void XThread::CheckQuantumAndDecay() {
|
||||
if (cvars::ignore_thread_priorities) return;
|
||||
// Real-time threads (priority >= 18) don't decay on Xenon.
|
||||
if (priority_ >= 18) return;
|
||||
|
||||
uint64_t now = Clock::QueryHostUptimeMillis();
|
||||
uint64_t elapsed = now - quantum_start_ms_;
|
||||
// On Xenon, the clock interrupt fires every ~1ms and decrements the
|
||||
// thread's quantum by 3. The process quantum is 60, so it takes ~20ms
|
||||
// for quantum to expire. When it does, the scheduler decays the
|
||||
// effective priority by exactly 1 and resets quantum. We approximate
|
||||
// this by decaying 1 priority level per 20ms of elapsed wall-clock time.
|
||||
constexpr uint64_t kQuantumPeriodMs = 20;
|
||||
if (elapsed < kQuantumPeriodMs) return;
|
||||
|
||||
// TODO(has207): The real kernel also subtracts the accumulated priority
|
||||
// boost (boost_accumulator in X_KTHREAD) during decay:
|
||||
// new_prio = priority - boost_accumulator - 1
|
||||
// We don't implement priority boosting on wait completion yet, so
|
||||
// the boost accumulator is always effectively 0. When boost-on-wake
|
||||
// is added, the accumulator needs to be drained here as well.
|
||||
int32_t decay_steps = static_cast<int32_t>(elapsed / kQuantumPeriodMs);
|
||||
int32_t new_priority = priority_ - decay_steps;
|
||||
if (new_priority < base_priority_) {
|
||||
new_priority = base_priority_;
|
||||
}
|
||||
if (new_priority != priority_) {
|
||||
priority_ = new_priority;
|
||||
if (is_guest_thread()) {
|
||||
guest_object<X_KTHREAD>()->priority = static_cast<uint8_t>(new_priority);
|
||||
}
|
||||
thread_->set_priority(GuestPriorityToHost(new_priority));
|
||||
}
|
||||
quantum_start_ms_ = now;
|
||||
}
|
||||
|
||||
void XThread::ResetQuantum() {
|
||||
if (cvars::ignore_thread_priorities) return;
|
||||
if (priority_ != base_priority_) {
|
||||
priority_ = base_priority_;
|
||||
if (is_guest_thread()) {
|
||||
guest_object<X_KTHREAD>()->priority =
|
||||
static_cast<uint8_t>(base_priority_);
|
||||
}
|
||||
thread_->set_priority(GuestPriorityToHost(base_priority_));
|
||||
}
|
||||
quantum_start_ms_ = Clock::QueryHostUptimeMillis();
|
||||
}
|
||||
|
||||
void XThread::SetAffinity(uint32_t affinity) {
|
||||
SetActiveCpu(GetFakeCpuNumber(affinity));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user