[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:
@@ -511,7 +511,7 @@ class IConfigVarUpdate {
|
|||||||
// If you're reviewing a pull request with a change here, check if 1) has been
|
// If you're reviewing a pull request with a change here, check if 1) has been
|
||||||
// done by the submitter before merging.
|
// done by the submitter before merging.
|
||||||
static constexpr uint32_t kLastCommittedUpdateDate =
|
static constexpr uint32_t kLastCommittedUpdateDate =
|
||||||
MakeConfigVarUpdateDate(2025, 12, 4, 21);
|
MakeConfigVarUpdateDate(2026, 4, 9, 12);
|
||||||
|
|
||||||
virtual ~IConfigVarUpdate() = default;
|
virtual ~IConfigVarUpdate() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -1151,6 +1151,18 @@ void KernelState::UpdateKeTimestampBundle() {
|
|||||||
xe::store_and_swap<uint64_t>(&lpKeTimeStampBundle->system_time,
|
xe::store_and_swap<uint64_t>(&lpKeTimeStampBundle->system_time,
|
||||||
Clock::QueryGuestSystemTime());
|
Clock::QueryGuestSystemTime());
|
||||||
xe::store_and_swap<uint32_t>(&lpKeTimeStampBundle->tick_count, uptime_ms);
|
xe::store_and_swap<uint32_t>(&lpKeTimeStampBundle->tick_count, uptime_ms);
|
||||||
|
|
||||||
|
// Every 20 ticks (~20ms), decay priority on running guest threads.
|
||||||
|
// This simulates the Xenon decrementer-driven quantum expiration.
|
||||||
|
if (++quantum_timer_counter_ >= 20) {
|
||||||
|
quantum_timer_counter_ = 0;
|
||||||
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
for (auto& [id, thread] : threads_by_id_) {
|
||||||
|
if (thread->is_running()) {
|
||||||
|
thread->CheckQuantumAndDecay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t KernelState::GetKeTimestampBundle() {
|
uint32_t KernelState::GetKeTimestampBundle() {
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ class KernelState {
|
|||||||
BitMap tls_bitmap_;
|
BitMap tls_bitmap_;
|
||||||
uint32_t ke_timestamp_bundle_ptr_ = 0;
|
uint32_t ke_timestamp_bundle_ptr_ = 0;
|
||||||
std::unique_ptr<xe::threading::HighResolutionTimer> timestamp_timer_;
|
std::unique_ptr<xe::threading::HighResolutionTimer> timestamp_timer_;
|
||||||
|
uint32_t quantum_timer_counter_ = 0;
|
||||||
cpu::backend::GuestTrampolineGroup kernel_trampoline_group_;
|
cpu::backend::GuestTrampolineGroup kernel_trampoline_group_;
|
||||||
// fixed address referenced by dashboards. Data is currently unknown
|
// fixed address referenced by dashboards. Data is currently unknown
|
||||||
uint32_t strange_hardcoded_page_ = 0x8E038634 & (~0xFFFF);
|
uint32_t strange_hardcoded_page_ = 0x8E038634 & (~0xFFFF);
|
||||||
|
|||||||
@@ -1583,8 +1583,6 @@ DECLARE_XBOXKRNL_EXPORT2(KeInitializeDpc, kThreading, kImplemented, kSketchy);
|
|||||||
|
|
||||||
dword_result_t KeInsertQueueDpc_entry(pointer_t<XDPC> dpc, dword_t arg1,
|
dword_result_t KeInsertQueueDpc_entry(pointer_t<XDPC> dpc, dword_t arg1,
|
||||||
dword_t arg2) {
|
dword_t arg2) {
|
||||||
assert_always("DPC does not dispatch yet; going to hang!");
|
|
||||||
|
|
||||||
uint32_t list_entry_ptr = dpc.guest_address() + 4;
|
uint32_t list_entry_ptr = dpc.guest_address() + 4;
|
||||||
|
|
||||||
// Lock dispatcher.
|
// Lock dispatcher.
|
||||||
@@ -1602,9 +1600,43 @@ dword_result_t KeInsertQueueDpc_entry(pointer_t<XDPC> dpc, dword_t arg1,
|
|||||||
|
|
||||||
dpc_list->Insert(list_entry_ptr);
|
dpc_list->Insert(list_entry_ptr);
|
||||||
|
|
||||||
|
// Dispatch the DPC inline on the calling thread. On real hardware DPCs
|
||||||
|
// are deferred to DISPATCH_IRQL on the target processor, but DPC routines
|
||||||
|
// access per-CPU state via r13 (KPCR) so they must run on a thread whose
|
||||||
|
// KPCR is valid for the target CPU. The calling thread's KPCR satisfies
|
||||||
|
// this for the common case (desired_cpu_number == 0, meaning current CPU).
|
||||||
|
// Inline dispatch also avoids latency issues with shared work queues.
|
||||||
|
uint32_t routine = dpc->routine;
|
||||||
|
if (routine) {
|
||||||
|
auto thread = XThread::GetCurrentThread();
|
||||||
|
if (thread) {
|
||||||
|
auto thread_state = thread->thread_state();
|
||||||
|
auto ppc_context = thread_state->context();
|
||||||
|
auto kpcr = ppc_context->TranslateVirtualGPR<X_KPCR*>(ppc_context->r[13]);
|
||||||
|
|
||||||
|
// If we're already inside a DPC (reentrant KeInsertQueueDpc from a DPC
|
||||||
|
// routine), skip the impersonation — we're already at DISPATCH_IRQL.
|
||||||
|
bool already_in_dpc = kpcr->prcb_data.dpc_active != 0;
|
||||||
|
|
||||||
|
DPCImpersonationScope dpc_scope{};
|
||||||
|
if (!already_in_dpc) {
|
||||||
|
kernel_state()->BeginDPCImpersonation(ppc_context, dpc_scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t args[] = {dpc.guest_address(), (uint64_t)dpc->context,
|
||||||
|
(uint64_t)arg1, (uint64_t)arg2};
|
||||||
|
kernel_state()->processor()->Execute(thread_state, routine, args,
|
||||||
|
xe::countof(args));
|
||||||
|
|
||||||
|
if (!already_in_dpc) {
|
||||||
|
kernel_state()->EndDPCImpersonation(ppc_context, dpc_scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
DECLARE_XBOXKRNL_EXPORT2(KeInsertQueueDpc, kThreading, kStub, kSketchy);
|
DECLARE_XBOXKRNL_EXPORT2(KeInsertQueueDpc, kThreading, kImplemented, kSketchy);
|
||||||
|
|
||||||
dword_result_t KeRemoveQueueDpc_entry(pointer_t<XDPC> dpc) {
|
dword_result_t KeRemoveQueueDpc_entry(pointer_t<XDPC> dpc) {
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|||||||
@@ -203,13 +203,21 @@ X_STATUS XObject::Wait(uint32_t wait_reason, uint32_t processor_mode,
|
|||||||
|
|
||||||
auto result =
|
auto result =
|
||||||
xe::threading::Wait(wait_handle, alertable ? true : false, timeout_ms);
|
xe::threading::Wait(wait_handle, alertable ? true : false, timeout_ms);
|
||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case xe::threading::WaitResult::kSuccess:
|
case xe::threading::WaitResult::kSuccess:
|
||||||
|
case xe::threading::WaitResult::kUserCallback: {
|
||||||
|
// Thread actually blocked and woke — reset priority to base.
|
||||||
|
auto current_thread = XThread::GetCurrentThread();
|
||||||
|
if (current_thread) {
|
||||||
|
current_thread->ResetQuantum();
|
||||||
|
}
|
||||||
|
if (result == xe::threading::WaitResult::kSuccess) {
|
||||||
WaitCallback();
|
WaitCallback();
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
case xe::threading::WaitResult::kUserCallback:
|
}
|
||||||
// Or X_STATUS_ALERTED?
|
|
||||||
return X_STATUS_USER_APC;
|
return X_STATUS_USER_APC;
|
||||||
|
}
|
||||||
case xe::threading::WaitResult::kTimeout:
|
case xe::threading::WaitResult::kTimeout:
|
||||||
xe::threading::MaybeYield();
|
xe::threading::MaybeYield();
|
||||||
return X_STATUS_TIMEOUT;
|
return X_STATUS_TIMEOUT;
|
||||||
@@ -231,13 +239,20 @@ X_STATUS XObject::SignalAndWait(XObject* signal_object, XObject* wait_object,
|
|||||||
auto result = xe::threading::SignalAndWait(
|
auto result = xe::threading::SignalAndWait(
|
||||||
signal_object->GetWaitHandle(), wait_object->GetWaitHandle(),
|
signal_object->GetWaitHandle(), wait_object->GetWaitHandle(),
|
||||||
alertable ? true : false, timeout_ms);
|
alertable ? true : false, timeout_ms);
|
||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case xe::threading::WaitResult::kSuccess:
|
case xe::threading::WaitResult::kSuccess:
|
||||||
|
case xe::threading::WaitResult::kUserCallback: {
|
||||||
|
auto current_thread = XThread::GetCurrentThread();
|
||||||
|
if (current_thread) {
|
||||||
|
current_thread->ResetQuantum();
|
||||||
|
}
|
||||||
|
if (result == xe::threading::WaitResult::kSuccess) {
|
||||||
wait_object->WaitCallback();
|
wait_object->WaitCallback();
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
case xe::threading::WaitResult::kUserCallback:
|
}
|
||||||
// Or X_STATUS_ALERTED?
|
|
||||||
return X_STATUS_USER_APC;
|
return X_STATUS_USER_APC;
|
||||||
|
}
|
||||||
case xe::threading::WaitResult::kTimeout:
|
case xe::threading::WaitResult::kTimeout:
|
||||||
xe::threading::MaybeYield();
|
xe::threading::MaybeYield();
|
||||||
return X_STATUS_TIMEOUT;
|
return X_STATUS_TIMEOUT;
|
||||||
@@ -264,25 +279,29 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
|
|||||||
TimeoutTicksToMs(*opt_timeout)))
|
TimeoutTicksToMs(*opt_timeout)))
|
||||||
: std::chrono::milliseconds::max();
|
: std::chrono::milliseconds::max();
|
||||||
|
|
||||||
|
X_STATUS status;
|
||||||
if (wait_type) {
|
if (wait_type) {
|
||||||
auto result = xe::threading::WaitAny(wait_handles, count,
|
auto result = xe::threading::WaitAny(wait_handles, count,
|
||||||
alertable ? true : false, timeout_ms);
|
alertable ? true : false, timeout_ms);
|
||||||
switch (result.first) {
|
switch (result.first) {
|
||||||
case xe::threading::WaitResult::kSuccess:
|
case xe::threading::WaitResult::kSuccess:
|
||||||
objects[result.second]->WaitCallback();
|
objects[result.second]->WaitCallback();
|
||||||
|
status = X_STATUS(result.second);
|
||||||
return X_STATUS(result.second);
|
break;
|
||||||
case xe::threading::WaitResult::kUserCallback:
|
case xe::threading::WaitResult::kUserCallback:
|
||||||
// Or X_STATUS_ALERTED?
|
status = X_STATUS_USER_APC;
|
||||||
return X_STATUS_USER_APC;
|
break;
|
||||||
case xe::threading::WaitResult::kTimeout:
|
case xe::threading::WaitResult::kTimeout:
|
||||||
xe::threading::MaybeYield();
|
xe::threading::MaybeYield();
|
||||||
return X_STATUS_TIMEOUT;
|
status = X_STATUS_TIMEOUT;
|
||||||
default:
|
break;
|
||||||
case xe::threading::WaitResult::kAbandoned:
|
case xe::threading::WaitResult::kAbandoned:
|
||||||
return X_STATUS(X_STATUS_ABANDONED_WAIT_0 + result.second);
|
status = X_STATUS(X_STATUS_ABANDONED_WAIT_0 + result.second);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
case xe::threading::WaitResult::kFailed:
|
case xe::threading::WaitResult::kFailed:
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
status = X_STATUS_UNSUCCESSFUL;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto result = xe::threading::WaitAll(wait_handles, count,
|
auto result = xe::threading::WaitAll(wait_handles, count,
|
||||||
@@ -292,20 +311,32 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects,
|
|||||||
for (uint32_t i = 0; i < count; i++) {
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
objects[i]->WaitCallback();
|
objects[i]->WaitCallback();
|
||||||
}
|
}
|
||||||
|
status = X_STATUS_SUCCESS;
|
||||||
return X_STATUS_SUCCESS;
|
break;
|
||||||
case xe::threading::WaitResult::kUserCallback:
|
case xe::threading::WaitResult::kUserCallback:
|
||||||
// Or X_STATUS_ALERTED?
|
status = X_STATUS_USER_APC;
|
||||||
return X_STATUS_USER_APC;
|
break;
|
||||||
case xe::threading::WaitResult::kTimeout:
|
case xe::threading::WaitResult::kTimeout:
|
||||||
xe::threading::MaybeYield();
|
xe::threading::MaybeYield();
|
||||||
return X_STATUS_TIMEOUT;
|
status = X_STATUS_TIMEOUT;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
case xe::threading::WaitResult::kAbandoned:
|
case xe::threading::WaitResult::kAbandoned:
|
||||||
case xe::threading::WaitResult::kFailed:
|
case xe::threading::WaitResult::kFailed:
|
||||||
return X_STATUS_ABANDONED_WAIT_0;
|
status = X_STATUS_ABANDONED_WAIT_0;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only reset quantum if the thread actually blocked (not on timeout/failure).
|
||||||
|
if (status != X_STATUS_TIMEOUT && status != X_STATUS_UNSUCCESSFUL &&
|
||||||
|
status != X_STATUS_ABANDONED_WAIT_0) {
|
||||||
|
auto current_thread = XThread::GetCurrentThread();
|
||||||
|
if (current_thread) {
|
||||||
|
current_thread->ResetQuantum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* XObject::CreateNative(uint32_t size) {
|
uint8_t* XObject::CreateNative(uint32_t size) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "xenia/base/byte_stream.h"
|
#include "xenia/base/byte_stream.h"
|
||||||
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/profiling.h"
|
#include "xenia/base/profiling.h"
|
||||||
@@ -24,8 +25,9 @@
|
|||||||
#include "xenia/kernel/user_module.h"
|
#include "xenia/kernel/user_module.h"
|
||||||
#include "xenia/kernel/xboxkrnl/xboxkrnl_threading.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");
|
"Ignores game-specified thread priorities.", "Kernel");
|
||||||
|
UPDATE_from_bool(ignore_thread_priorities, 2026, 4, 9, 12, true);
|
||||||
DEFINE_bool(ignore_thread_affinities, true,
|
DEFINE_bool(ignore_thread_affinities, true,
|
||||||
"Ignores game-specified thread affinities.", "Kernel");
|
"Ignores game-specified thread affinities.", "Kernel");
|
||||||
|
|
||||||
@@ -670,28 +672,87 @@ void XThread::RundownAPCs() {
|
|||||||
|
|
||||||
int32_t XThread::QueryPriority() { return thread_->priority(); }
|
int32_t XThread::QueryPriority() { return thread_->priority(); }
|
||||||
|
|
||||||
void XThread::SetPriority(int32_t increment) {
|
// Map Xenon's 0-31 priority range across the available host priority levels.
|
||||||
if (is_guest_thread()) {
|
// Priority 18 (0x12) is the Xenon real-time threshold — threads at or above
|
||||||
guest_object<X_KTHREAD>()->priority = static_cast<uint8_t>(increment);
|
// it don't get quantum decay on real hardware.
|
||||||
}
|
static int32_t GuestPriorityToHost(int32_t guest_priority) {
|
||||||
priority_ = increment;
|
if (guest_priority >= 24) {
|
||||||
int32_t target_priority = 0;
|
return xe::threading::ThreadPriority::kHighest;
|
||||||
if (increment > 0x22) {
|
} else if (guest_priority >= 17) {
|
||||||
target_priority = xe::threading::ThreadPriority::kHighest;
|
return xe::threading::ThreadPriority::kAboveNormal;
|
||||||
} else if (increment > 0x11) {
|
} else if (guest_priority >= 10) {
|
||||||
target_priority = xe::threading::ThreadPriority::kAboveNormal;
|
return xe::threading::ThreadPriority::kNormal;
|
||||||
} else if (increment < -0x22) {
|
} else if (guest_priority >= 5) {
|
||||||
target_priority = xe::threading::ThreadPriority::kLowest;
|
return xe::threading::ThreadPriority::kBelowNormal;
|
||||||
} else if (increment < -0x11) {
|
|
||||||
target_priority = xe::threading::ThreadPriority::kBelowNormal;
|
|
||||||
} else {
|
} 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) {
|
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) {
|
void XThread::SetAffinity(uint32_t affinity) {
|
||||||
SetActiveCpu(GetFakeCpuNumber(affinity));
|
SetActiveCpu(GetFakeCpuNumber(affinity));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -422,6 +422,17 @@ class XThread : public XObject, public cpu::Thread {
|
|||||||
int32_t QueryPriority();
|
int32_t QueryPriority();
|
||||||
void SetPriority(int32_t increment);
|
void SetPriority(int32_t increment);
|
||||||
|
|
||||||
|
// Called periodically (~20ms) by KernelState's timestamp timer to simulate
|
||||||
|
// the Xenon scheduler's quantum-based priority decay for non-real-time
|
||||||
|
// threads (priority < 18). Threads that run for longer than one quantum
|
||||||
|
// (~20ms) have their effective priority decayed toward the base, which
|
||||||
|
// causes them to drop into lower host priority buckets and prevents
|
||||||
|
// starvation.
|
||||||
|
void CheckQuantumAndDecay();
|
||||||
|
// Resets effective priority back to base and restarts the quantum timer.
|
||||||
|
// Called when a thread wakes from a kernel wait.
|
||||||
|
void ResetQuantum();
|
||||||
|
|
||||||
// Xbox thread IDs:
|
// Xbox thread IDs:
|
||||||
// 0 - core 0, thread 0 - user
|
// 0 - core 0, thread 0 - user
|
||||||
// 1 - core 0, thread 1 - user
|
// 1 - core 0, thread 1 - user
|
||||||
@@ -491,7 +502,9 @@ class XThread : public XObject, public cpu::Thread {
|
|||||||
bool main_thread_ = false; // Entry-point thread
|
bool main_thread_ = false; // Entry-point thread
|
||||||
bool running_ = false;
|
bool running_ = false;
|
||||||
|
|
||||||
int32_t priority_ = 0;
|
int32_t priority_ = 0; // current effective priority (may be decayed)
|
||||||
|
int32_t base_priority_ = 0; // priority floor — decay never goes below this
|
||||||
|
uint64_t quantum_start_ms_ = 0; // host uptime (ms) when quantum last reset
|
||||||
|
|
||||||
#if !XE_PLATFORM_WIN32
|
#if !XE_PLATFORM_WIN32
|
||||||
// Condition variable for thread self-suspension.
|
// Condition variable for thread self-suspension.
|
||||||
|
|||||||
Reference in New Issue
Block a user