📄 umsfreevirtualprocessorroot.cpp
字号:
// First compare with 0, then 1.
//
ASSERT(m_activationFence == 0 || m_activationFence == 1);
}
if (newVal == 0)
{
//
// Reduce the subscription level on the core while the root is suspended. The count is used by dynamic resource management
// to tell which cores allocated to a scheduler are unused, so that they can be temporarily repurposed.
//
if (m_fWokenByScheduler)
{
GetSchedulerProxy()->DecrementCoreSubscription(GetExecutionResource());
}
if (pProxy != NULL)
{
m_fWokenByScheduler = pProxy->Deactivate();
}
else
{
m_fWokenByScheduler = InternalDeactivate();
}
if (m_fWokenByScheduler)
{
//
// This is a 'real' activation by the scheduler.
//
GetSchedulerProxy()->IncrementCoreSubscription(GetExecutionResource());
}
}
else
{
ASSERT(newVal == 1);
//
// The activation for this deactivation came in early, so we return early here without making a kernel transition.
//
}
return m_fWokenByScheduler;
}
/// <summary>
/// Forces all data in the memory heirarchy of one processor to be visible to all other processors.
/// </summary>
/// <param name="pContext">
/// The context which is currently being dispatched by this root.
/// </param>
void UMSFreeVirtualProcessorRoot::EnsureAllTasksVisible(Concurrency::IExecutionContext *pContext)
{
bool fPrimary = OnPrimary();
if (pContext == NULL || (fPrimary && pContext != m_pSchedulingContext))
{
throw std::invalid_argument("pContext");
}
if (m_pExecutingProxy == NULL && !fPrimary)
{
throw invalid_operation();
}
//
// Note that if pProxy is NULL at the end of this, we cannot touch m_pExecutingContext other than comparisons. No fields may be
// touched. It may already be gone and freed.
//
UMSFreeThreadProxy *pProxy = NULL;
IThreadProxy *pProxyIf = pContext->GetProxy();
if (pProxyIf != this)
pProxy = static_cast<UMSFreeThreadProxy *> (pContext->GetProxy());
if (!fPrimary)
{
//
// We expect this call to be made from within a dispatch loop or the primary.
//
if (pProxy != NULL && (m_pExecutingProxy != pProxy || UMSThreadProxy::GetCurrent() != static_cast<UMSThreadProxy *>(pProxy)))
{
throw invalid_operation();
}
//
// We must be in a critical region on the **SCHEDULER SIDE** prior to calling this
//
CORE_ASSERT(pProxy == NULL || pProxy->GetCriticalRegionType() != OutsideCriticalRegion);
}
GetSchedulerProxy()->GetResourceManager()->FlushStoreBuffers();
}
/// <summary>
/// Performs a deactivation of the virtual processor. This is always called on the primary. A user thread which deactivates must defer the
/// call to the primary to perform the blocking action.
/// </summary>
bool UMSFreeVirtualProcessorRoot::InternalDeactivate()
{
CORE_ASSERT(OnPrimary());
HANDLE hObjects[3];
int count = 2;
//
// Wait for either an activation (m_hBlock) or a completion (the other two events). Signal back the
// context the reason it awoke.
//
hObjects[0] = m_hBlock;
hObjects[1] = SchedulerProxy()->GetCompletionListEvent();
hObjects[2] = SchedulerProxy()->GetTransferListEvent();
if (hObjects[2] != NULL) count++;
CORE_ASSERT(count <= (sizeof(hObjects) / sizeof(HANDLE)));
DWORD result;
for(;;)
{
result = WaitForMultipleObjects(count, hObjects, FALSE, INFINITE);
int index = result - WAIT_OBJECT_0;
if (index == 1)
{
CORE_ASSERT(index < count);
CORE_ASSERT(hObjects[index] == SchedulerProxy()->GetCompletionListEvent());
// Completion list events should be handled by RM
SchedulerProxy()->SweepCompletionList();
// Go back to the loop and wait.
continue;
}
// The scheduler needs to be notified for both activation and transfer list events
break;
}
return (result == WAIT_OBJECT_0);
}
/// <summary>
/// Marks a particular UMS thread proxy as running atop this UMS virtual processor root.
/// </summary>
/// <param name="pProxy">
/// The proxy which is to run atop this virtual processor root.
/// </param>
/// <param name="fCriticalReexecute">
/// Is the affinitization due to a critical execution happening on the same vproc.
/// </param>
void UMSFreeVirtualProcessorRoot::Affinitize(UMSFreeThreadProxy *pProxy, bool fCriticalReexecute)
{
//
// See FreeVirtualProcessorRoot::Affinitize. There is an additional detail here: it's entirely possible that a UMS thread blocks
// and comes back on the completion list before the primary has a chance to do anything (like resetting m_pRoot, etc...). Another
// primary might pick this up off the completion list before we even *REALIZE* it's blocked. We must maintain consistent state. Until
// the original primary has had the chance to clear m_pRoot and do everything necessary to maintain state, prevent the switching to
// pProxy.
//
// Critical reexecution need not "spin until blocked" as we've never set the blocked flag. We must *COMPLETELY MASK* such blocking from
// above layers.
//
if (!fCriticalReexecute)
pProxy->SpinUntilBlocked();
RVPMTRACE(MTRACE_EVT_AFFINITIZED, pProxy, this, NULL);
if (pProxy->m_pLastRoot != this)
{
#if defined(_DEBUG)
//
// If we were last running on a different vproc, we had better not be flagged as critically blocked!
//
CORE_ASSERT((pProxy->m_UMSDebugBits & UMS_DEBUGBIT_CRITICALBLOCK) == 0);
#endif // _DEBUG
//
// Normally, we will inherit affinity from the primary as UT(u)/KT(p) pair. If pProxy enters the kernel, however, the directed switch
// happens and we run atop KT(u). This does not have the same affinity. While the original switch elides the scheduler and runs atop the same
// core, there are two potential problems:
//
// - If the thread quantum expires while KT(u) is in the kernel and the NT scheduler reschedules KT(u), it will reschedule with the affinity
// of KT(u) not that picked up off KT(p).
//
// - If KT(u) does not block in the kernel, the UMS system will let UT(u) ride out on KT(u) as an optimization for threads making repeated non-blocking
// calls into the kernel. Again, if there is no thread quantum expiration, no problem exists. If the thread quantum does expire, however, the
// same issue exists as above -- the NT scheduler will reschedule UT(u)/KT(u) with the affinity of KT(u) not that picked up off KT(p).
//
// Ideally, we'd like to have the same affinity for KT(u) as KT(p). Making a call to change the affinity here, however, is a performance hit for
// user mode context switching -- we will enter the kernel for a user mode switch. SetAffinity avoids the kernel transition if the affinity is the
// same.
//
HardwareAffinity newAffinity = SchedulerProxy()->GetNodeAffinity(GetNodeId());
pProxy->SetAffinity(newAffinity);
#if defined(_DEBUG)
//
// Validate that all the affinities are what we think they are. If we switch to a UT that doesn't overlap our affinity mask, there's an absolutely
// enormous performance penalty in Win7.
//
HardwareAffinity realPrimaryAffinity(GetCurrentThread());
HardwareAffinity expectedPrimaryAffinity = SchedulerProxy()->GetNodeAffinity(GetNodeId());
HardwareAffinity realUTAffinity(pProxy->GetThreadHandle());
CORE_ASSERT(expectedPrimaryAffinity == realPrimaryAffinity);
CORE_ASSERT(newAffinity == realPrimaryAffinity);
CORE_ASSERT(newAffinity == realUTAffinity);
#endif // _DEBUG
}
pProxy->PrepareToRun(this);
m_pExecutingProxy = pProxy;
}
/// <summary>
/// Executes the specified proxy. This can only be called from the primary thread!
/// </summary>
/// <param name="pProxy">
/// The thread proxy to execute.
/// </param>
/// <param name="fromSchedulingContext">
/// Whether the switch is happening as a result of a SwitchTo from the scheduling context. On failure, we do not recursively reinvoke
/// the scheduling context, we simply return -- indicating failure.
/// </param>
/// <param name="fCriticalBlockAndExecute">
/// An indication as to whether the execution was due to the result of a critical block and subsequent execute.
/// </param>
/// <returns>
/// This does *NOT* return if execution is successful. Any return indicates failure.
/// </returns>
void UMSFreeVirtualProcessorRoot::Execute(UMSFreeThreadProxy *pProxy, bool fromSchedulingContext, bool fCriticalBlockAndExecute)
{
//
// *NOTE*:
// This is the *ONLY* function which should call ExecuteUmsThread!
//
RVPMTRACE(MTRACE_EVT_EXECUTE, pProxy, this, NULL);
CORE_ASSERT(OnPrimary());
Affinitize(pProxy, fCriticalBlockAndExecute);
//
// If we blocked during a critical region and performed a critical block and execute, we must not play with the messaging block. It's entirely
// possible that someone page faulted in the critical region during UMSThreadProxy::InternalSwitchTo where the messaging block was being filled in
// or during UmsThreadYield before we get back to the primary. Altering the messaging block in that case will corrupt the thread proxy and lead
// to issues down the line. We can safely reaffinitize because it will restore exact state, but we cannot touch the messaging block.
//
if (!fCriticalBlockAndExecute)
pProxy->m_yieldAction = UMSThreadProxy::ActionNone;
bool fCritical = pProxy->GetCriticalRegionType() != OutsideCriticalRegion;
#if defined(_DEBUG)
bool fSuspended = pProxy->IsSuspended();
bool fTerminated = pProxy->IsTerminated();
DWORD oldDebugBits = pProxy->m_UMSDebugBits;
DWORD oldLastExecuteError = pProxy->m_UMSLastExecuteError;
pProxy->m_UMSDebugBits = 0;
pProxy->m_UMSLastExecuteError = 0;
#endif // _DEBUG
int spinCount = NUMBER_OF_EXECUTE_SPINS;
for(;;)
{
CORE_ASSERT(spinCount > 0);
UMS::ExecuteUmsThread(pProxy->GetUMSContext());
//
// If g_InfiniteSpinOnExecuteFailure is set, we want special debuggability around single stepping this code and we *FOREVER* spin waiting
// for pProxy to become unsuspended. This will allow us to single step around this code without triggering context switches
//
#if defined(_DEBUG)
if (g_InfiniteSpinOnExecuteFailure)
{
continue;
}
#else // _DEBUG
if (--spinCount == 0)
{
//
// There's absolutely no point in handing something to the poller that's not suspended. It's likely a KAPC is firing and handing to the
// poller will just play thread debounce since there's no API to query for kernel locks.
//
if (pProxy->IsSuspended())
break;
else
spinCount = NUMBER_OF_EXECUTE_SPINS;
}
#endif // _DEBUG
YieldProcessor();
}
//
// If we returned from ExecuteUmsThread, the thread couldn't execute. It might have been suspended since last going on the completion list or
// yielding or it might be running an APC due to a GetThreadContext, etc... The obvious thought here is to unwind and fail the SwitchTo call.
// The unfortunate reality is that from the scheduler's perspective, pPreviousProxy might be runnable (it might have yielded) and another virtual
// processor might be trying to SwitchTo that. In this case, it will spin until NotifyBlocked is called. The obvious thought here is that one
// might just cascade failures and set some special signal to that thread in the event of failure. The reality here is that ExecuteUmsThread
// does *NOT RETURN* on success. The stack of the primary is immediately snapped back to some RTL frame for UMS. Hence, in the success case,
// we need to NotifyBlocked **BEFORE** calling ExecuteUmsThread. This means that another virtual processor might **ALREADY** be
// running pPreviousProxy. We cannot unwind back to return failure from the SwitchTo.
//
// In order to deal with this situation, we take an alternate tact. If the failure happens, we treat it as if the SwitchTo succeeded and then immediately
// blocked on the next instruction triggering a return to primary. We will reinvoke the scheduling context in order to make a decision.
//
#if defined(_DEBUG)
pProxy->m_UMSLastExecuteError = GetLastError();
pProxy->m_UMSDebugBits |= UMS_DEBUGBIT_HANDEDTOPOLLER |
(fSuspended ? UMS_DEBUGBIT_EXECUTEFAILURESUSPENDED : 0) |
(fTerminated ? UMS_DEBUGBIT_EXECUTEFAILURETERMINATED : 0);
RVPMTRACE(MTRACE_EVT_EXECUTEFAIL, pProxy, this, pProxy->m_UMSDebugBits);
#endif // _DEBUG
//
// The second unfortunate reality is that once ExecuteUmsThread fails for a particular UMS context, it will *NEVER* come back on
// the completion list. From UMS's perspective, the USched is now responsible for polling until the end of time whether this particular context
// is not suspended.
//
SchedulerProxy()->PollForCompletion(pProxy);
if (!fromSchedulingContext || fCritical)
HandleBlocking(pProxy, true);
else
pProxy->NotifyBlocked(false);
}
/// <summary>
/// Performs a critical blocking of the primary until a specific UT appears on the completion list. The specified UT must
/// be in a critical region! This can only be called from the primary thread!
/// </summary>
void UMSFreeVirtualProcessorRoot::CriticalBlockAndExecute(UMSFreeThreadProxy *pProxy)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -