// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // VirtualProcessor.h // // Source file containing the VirtualProcessor declaration. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- #pragma once namespace Concurrency { namespace details { // // TRANSITION: This is temporary until priority based livelock prevention is real. // struct BoostedObject { enum BoostType { BoostTypeScheduleGroupSegment, BoostTypeVirtualProcessor }; enum BoostState { BoostStateDisallowed, BoostStateUnboosted, BoostStateBoosted }; BoostType m_type; BoostedObject *m_pNext; BoostedObject *m_pPrev; LONG m_boostState; bool IsScheduleGroupSegment() const { return m_type == BoostTypeScheduleGroupSegment; } bool IsVirtualProcessor() const { return m_type == BoostTypeVirtualProcessor; } }; // // virtualized hardware thread // /// /// A virtual processor is an abstraction of a hardware thread. However, there may very well be more than one /// virtual processor per hardware thread. The SchedulerPolicy key TargetOversubscriptionFactor determines /// the number of virtual processor per hardware thread, scheduler wide. /// /// /// Virtual processors may be created and destroyed at any time, since resource management (RM) may give or take /// away hardware threads. But as such batches of TargetOversubscriptionFactor virtual processors are created or /// destroyed simultaneously. /// class VirtualProcessor { public: /// /// Indicates the type of availability of a virtual processor. /// enum AvailabilityType { // The virtual processor is claimed and running -- it is not available for claiming AvailabilityClaimed = 0x0, // The virtual processor was marked available and is/will go inactive with nothing running atop it AvailabilityInactive = 0x01, // The virtual processor is available with an idle/sleeping (or soon to be) thread running atop it. AvailabilityIdle = 0x2, // The virtual processor was marked available and is/will deactivate; however -- it is pending a thread. It should // not be awoken unless a thread is ready for it (e.g.: not for LWTs / WSQ items) AvailabilityInactivePendingThread = 0x4, // The virtual processor is available with an idle/sleeping (or soon to be) thread running atop it; however -- it is pending a thread. // This is a state exclusive to a scheduler with a specific scheduling context (e.g.: UMS) AvailabilityIdlePendingThread = 0x8, // Mask for any type of availability (e.g.: if trying to find/claim a vproc) AvailabilityAny = 0xf }; /// /// Represents a claim of exclusive ownership on a virtual processor. /// class ClaimTicket { public: /// /// Constructs a new empty ticket. /// ClaimTicket() : m_type(AvailabilityClaimed) { } /// /// Exercises the ticket. Starts up a new context on the virtual processor or activates the idle one as appropriate. /// bool Exercise(ScheduleGroupSegmentBase *pSegment = NULL) { ASSERT(m_type != AvailabilityClaimed); bool fResult = m_pVirtualProcessor->ExerciseClaim(m_type, pSegment); // // Ensure that a second call to exercise the same ticket asserts. // m_type = AvailabilityClaimed; return fResult; } /// /// Exercises the ticket with a particular context. This is only valid if ExerciseWakesExisting() returns false. Callers should have /// sought virtual processors with specific claim rights. /// bool ExerciseWith(InternalContextBase *pContext); /// /// Informs the caller whether the claim simply wakes an existing thread. /// bool ExerciseWakesExisting() const { return m_type == AvailabilityIdle || m_type == AvailabilityIdlePendingThread; } private: friend class VirtualProcessor; ClaimTicket(AvailabilityType type, VirtualProcessor *pVirtualProcessor) { InitializeTicket(type, pVirtualProcessor); } void InitializeTicket(AvailabilityType type, VirtualProcessor *pVirtualProcessor) { m_type = type; m_pVirtualProcessor = pVirtualProcessor; } AvailabilityType m_type; VirtualProcessor *m_pVirtualProcessor{}; }; /// /// Constructs a virtual processor. /// VirtualProcessor(); /// /// Destroys a virtual processor /// virtual ~VirtualProcessor(); /// /// Returns a scheduler unique identifier for the virtual processor. /// unsigned int GetId() const { return m_id; } /// /// Returns the execution resource id associated with this virtual processor. /// unsigned int GetExecutionResourceId() const { return m_resourceId; } /// /// Returns the mask id for this virtual processor. /// unsigned int GetMaskId() const { return m_maskId; } /// /// Updates tracking on the virtual processor whether it is executing affine work and/or local work. /// /// /// An indication of whether the virtual processor is executing work which has affinity to it or not. /// /// /// An indication of whether the virtual processor is executing work which is local to it or not. /// void UpdateWorkState(bool fAffine, bool fLocal); /// /// Performs a quick check as to whether work which is affine to the virtual processor has arrived. This allows short circuiting of /// expensive searches for affine work in cases where a user does not use any location objects. /// /// /// An indication of whether or not work affine to the virtual processor has arrived since UpdateWorkState was called to indicate that /// the virtual processor began working on non-affine work. /// bool CheckAffinityNotification(); /// /// Returns an indication of whether the virtual processor is (or was last) executing affine work. /// /// /// An indication of whether the virtual processor is (or was last) executing affine work. /// bool ExecutingAffine() { return m_fAffine; } /// /// Attempts to claim exclusive ownership of the virtual processor by resetting the available flag. /// /// /// If true is returned, this will contain the claim ticket used to activate the virtual processor. /// /// /// If specified, indicates the type of availability that we can claim. If the caller is only interested in inactive virtual processors, /// for instance, AvailabilityInactive can be passed. /// /// /// True if it was able to claim the virtual processor, false otherwise. /// bool ClaimExclusiveOwnership(ClaimTicket &ticket, ULONG type = AvailabilityAny, bool updateCounters = true /* GET RID OF THIS */); /// /// Returns the state of the virtual processor's availability prior to the last claim. /// AvailabilityType ClaimantType() const { ASSERT(m_availabilityType == AvailabilityClaimed); return m_claimantType; } /// /// Returns whether the last claimant claimed us while inactive. /// bool ClaimantWasInactive() const { return ((ClaimantType() & (AvailabilityInactive | AvailabilityInactivePendingThread)) != 0); } /// /// Makes a virtual processor available for scheduling work. /// void MakeAvailableForIdle() { MakeAvailable(AvailabilityIdle); } /// /// Makes a virtual processor available for scheduling work. /// void MakeAvailableForInactive() { MakeAvailable(AvailabilityInactive); } /// /// Makes a virtual processor available pending a thread. /// void MakeAvailablePendingThread() { MakeAvailable(AvailabilityInactivePendingThread); } /// /// Returns a pointer to the internal context that is executing on this virtual processor. /// IExecutionContext * GetExecutingContext() { return m_pExecutingContext; } /// /// Returns a pointer to the owning node for the virtual processor. /// SchedulingNode * GetOwningNode() { return m_pOwningNode; } /// /// Returns a pointer to the owning ring for the virtual processor. /// SchedulingRing * GetOwningRing() { return m_pOwningRing; } /// /// Returns a pointer to the owning root for the virtual processor. /// IVirtualProcessorRoot * GetOwningRoot() { return m_pOwningRoot; } /// /// Returns a pointer to the suballocator for the virtual processor. /// SubAllocator * GetCurrentSubAllocator(); /// /// Returns true if the virtual processor is marked as available, false otherwise. /// bool IsAvailable() { return (m_availabilityType != AvailabilityClaimed); } /// /// Returns the type of availability of the virtual processor. /// AvailabilityType TypeOfAvailability() { return m_availabilityType; } /// /// Returns true if the virtual processor is marked for retirement, false otherwise. /// bool IsMarkedForRetirement() { return m_fMarkedForRetirement; } /// /// Activates a virtual processor with the context provided. /// void Activate(IExecutionContext *pContext); /// /// Temporarily deactivates a virtual processor. /// /// /// An indication of which side the awakening occurred from (true -- we activated it, false -- the RM awoke it). /// bool Deactivate(IExecutionContext *pContext); /// /// Invokes the underlying virtual processor root to ensure all tasks are visible. /// void EnsureAllTasksVisible(IExecutionContext * pContext); /// /// Returns the default destination of a SwitchTo(NULL). There is none on a default virtual processor. /// virtual IExecutionContext *GetDefaultDestination() { return NULL; } /// /// Performs a search for work for the given virtual processor. /// bool SearchForWork(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pOriginSegment, bool fLastPass) { return m_searchCtx.Search(pWorkItem, pOriginSegment, fLastPass); } /// /// Performs a search for work for the given virtual processor only allowing certain types of work to be found. /// bool SearchForWork(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pOriginSegment, bool fLastPass, ULONG allowableTypes) { return m_searchCtx.Search(pWorkItem, pOriginSegment, fLastPass, allowableTypes); } /// /// Performs a yielding search for work for the given virtual processor. /// bool SearchForWorkInYield(WorkItem *pWork, ScheduleGroupSegmentBase *pOriginSegment, bool fLastPass) { return m_searchCtx.YieldingSearch(pWork, pOriginSegment, fLastPass); } /// /// Performs a yielding search for work for the given virtual processor only allowing certain types of work to be found. /// bool SearchForWorkInYield(WorkItem *pWorkItem, ScheduleGroupSegmentBase *pOriginSegment, bool fLastPass, ULONG allowableTypes) { return m_searchCtx.YieldingSearch(pWorkItem, pOriginSegment, fLastPass, allowableTypes); } /// /// Stub called in SFW before we search for runnable contexts. /// /// /// A context which should be run. /// virtual InternalContextBase *PreRunnableSearch() { return NULL; } /// /// Called when the context running atop this virtual processor has reached a safe point. /// /// /// An indication of whether the caller should perform a commit. /// bool SafePoint(); /// /// Gets a location object which represents the virtual processor. /// const location& GetLocation() const { return m_location; } // // TRANSITION: This goes with real priority. // void MarkGrabbedPriority() { m_fShortAffine = true; } /// /// Marks the segment as serviced at a particular time mark. /// void ServiceMark(ULONGLONG markTime) { // // Avoid cache contention on this by only writing the service time every so often. We only care about this on granularities of something // like 1/2 seconds anyway -- it's effectively the priority boost time granularity that we care about. // if (TimeSinceServicing(markTime) > 100) m_lastServiceTime = markTime; } /// /// Returns the time delta between the last service time and the passed service time. /// ULONG TimeSinceServicing(ULONGLONG markTime) { return (ULONG)(markTime - m_lastServiceTime); } /// /// Returns a segment from its internal list entry. /// static VirtualProcessor* FromBoostEntry(BoostedObject *pEntry) { return CONTAINING_RECORD(pEntry, VirtualProcessor, m_priorityServiceLink); } protected: // // protected data // // Indicates whether vproc is available to perform work. volatile AvailabilityType m_availabilityType{}; // Indicates how the current claimant acquired the vproc. AvailabilityType m_claimantType{}; // Local caching of realized chores/contexts StructuredWorkStealingQueue m_localRunnableContexts; // The search context which keeps track of where this virtual processor is in a search-for-work regardless of algorithm. WorkSearchContext m_searchCtx; // Owning scheduling node SchedulingNode *m_pOwningNode{}; // Owning ring - ring associated with the owning node SchedulingRing *m_pOwningRing{}; // Owning virtual processor root IVirtualProcessorRoot *m_pOwningRoot{}; // Sub allocator SubAllocator *m_pSubAllocator{}; // Tracks the types of work that the virtual processor is currently operating on. bool m_fAffine{}; bool m_fLocal{}; // Once m_fAffine is clear, we need to post ONE notification to make sure that an affine search happens AFTER // m_fAffine is set. This tracks the need for the ONE search. bool m_fShortAffine{}; // The index that this Virtual Processor is at in its list array int m_listArrayIndex{}; // Statistics data counters unsigned int m_enqueuedTaskCounter{}; unsigned int m_dequeuedTaskCounter{}; // Statistics data checkpoints unsigned int m_enqueuedTaskCheckpoint{}; unsigned int m_dequeuedTaskCheckpoint{}; // The context pushed to startup this virtual processor. InternalContextBase *m_pPushContext{}; // Internal context that is affinitized to and executing on this virtual processor // This is always an InternalContextBase except on UMS in very special circumstances. IExecutionContext * volatile m_pExecutingContext{}; // The context which made the virtual processor available. IExecutionContext * m_pAvailableContext{}; _HyperNonReentrantLock m_lock; // Unique identifier for vprocs within a scheduler. unsigned int m_id{}; // The execution resource id for this virtual processor. unsigned int m_resourceId{}; // The mask id assigned by the scheduler to this virtual processor. unsigned int m_maskId{}; // Stashed location for this virtual processor. location m_location; // The resource mask for this virtual processor. QuickBitSet m_resourceMask; // Flag specifying whether this is a virtual processor created as a result of a call to Oversubscribe. bool m_fOversubscribed{}; // Flag that is set when the virtual processor should remove itself from the scheduler at a yield point, // i.e, either when the context executing on it calls Block or Yield, or when it is in the dispatch loop // looking for work. bool m_fMarkedForRetirement{}; // Whether this virtual processor has reached a safe point in the code // Used to demark when all virtual processors have reached safe points and a list array deletion // can occur bool m_fReachedSafePoint{}; /// /// Causes the virtual processor to remove itself from the scheduler. This is used either when oversubscription /// ends or when the resource manager asks the vproc to retire. /// virtual void Retire(); // The internal context that caused this virtual processor to be created, if this is an oversubscribed vproc. InternalContextBase * m_pOversubscribingContext{}; /// /// Affinitizes an internal context to the virtual processor. /// /// /// The internal context to affinitize. /// _Post_satisfies_(this->m_pExecutingContext == pContext) virtual void Affinitize(InternalContextBase *pContext); /// /// Returns a type-cast of pContext to an InternalContextBase or NULL if it is not. /// virtual InternalContextBase *ToInternalContext(IExecutionContext *pContext); /// /// Initializes the virtual processor. This API is called by the constructor, and when a virtual processor is to /// be re-initialized, when it is pulled of the free pool in the list array. /// /// /// The owning schedule node for this virtual processor /// /// /// The owning IVirtualProcessorRoot /// virtual void Initialize(SchedulingNode *pOwningNode, IVirtualProcessorRoot *pOwningRoot); /// /// Makes a virtual processor available for scheduling work. /// /// /// Indicates whether the routine should update active state for the vproc based upon type or not. /// void MakeAvailable(AvailabilityType type, bool fCanChangeActiveState = true); private: friend class SchedulerBase; friend class ContextBase; friend class InternalContextBase; friend class ThreadInternalContext; friend class ScheduleGroup; friend class FairScheduleGroup; friend class CacheLocalScheduleGroup; friend class SchedulingNode; friend class WorkSearchContext; friend class ClaimTicket; template friend class ListArray; template friend class List; // Links for throttling VirtualProcessor *m_pNext{}; VirtualProcessor *m_pPrev{}; // Intrusive links for list array. SLIST_ENTRY m_listArrayFreeLink{}; // The safe point marker. SafePointMarker m_safePointMarker; // The last time this virtual processor's LRC was serviced. ULONGLONG m_lastServiceTime{}; // // TRANSITION: This is a temporary patch on livelock until we can tie into priority for livelock prevention. // bool m_prioritized{}; // Under m_priorityServiceLink's lock. BoostedObject m_priorityServiceLink{}; /// /// Exercises a claim over the virtual processor. /// bool ExerciseClaim(AvailabilityType type, ScheduleGroupSegmentBase *pSegment, InternalContextBase *pContext = NULL); /// /// Start a worker context executing on this.virtual processor. /// virtual bool StartupWorkerContext(ScheduleGroupSegmentBase* pSegment, InternalContextBase *pContext = NULL); /// /// Oversubscribes the virtual processor by creating a new virtual processor root affinitized to the same /// execution resource as that of the current root /// /// /// A virtual processor that oversubscribes this one. /// virtual VirtualProcessor * Oversubscribe(); /// /// Marks the virtual processor such that it removes itself from the scheduler, once the context it is executing /// reaches a safe yield point. Alternatively, if the context has not started executing yet, it can be retired immediately. /// void MarkForRetirement(); /// /// Steals a context from the local runnables queue of this virtual processor. /// InternalContextBase *StealLocalRunnableContext() { InternalContextBase *pContext = NULL; if (!m_localRunnableContexts.Empty()) { pContext = m_localRunnableContexts.Steal(); } if (pContext != NULL) { #if defined(_DEBUG) ::Concurrency::details::SetContextDebugBits(pContext, CTX_DEBUGBIT_STOLENFROMLOCALRUNNABLECONTEXTS); #endif // _DEBUG } return pContext; } /// /// Pops a runnable context from the local runnables queue of the vproc, if it can find one. /// InternalContextBase *GetLocalRunnableContext() { if (m_localRunnableContexts.Count() > 0) // Is this check worthwhile? Yes, I believe. We'd take a fence to check otherwise. { InternalContextBase *pContext = m_localRunnableContexts.Pop(); #if defined(_DEBUG) ::Concurrency::details::SetContextDebugBits(pContext, CTX_DEBUGBIT_POPPEDFROMLOCALRUNNABLECONTEXTS); #endif // _DEBUG return pContext; } return NULL; } /// /// Resets the count of work coming in. /// /// /// This function is using modulo 2 behavior of unsigned ints to avoid overflow and /// reset problems. For more detail look at ExternalStatistics class in externalcontextbase.h. /// /// /// Previous value of the counter. /// unsigned int GetEnqueuedTaskCount() { unsigned int currentValue = m_enqueuedTaskCounter; unsigned int retVal = currentValue - m_enqueuedTaskCheckpoint; // Update the checkpoint value with the current value m_enqueuedTaskCheckpoint = currentValue; ASSERT(retVal < INT_MAX); return retVal; } /// /// Resets the count of work being done. /// /// /// This function is using modulo 2 behavior of unsigned ints to avoid overflow and /// reset problems. For more detail look at the ExternalStatistics class in externalcontextbase.h. /// /// /// Previous value of the counter. /// unsigned int GetDequeuedTaskCount() { unsigned int currentValue = m_dequeuedTaskCounter; unsigned int retVal = currentValue - m_dequeuedTaskCheckpoint; // Update the checkpoint value with the current value m_dequeuedTaskCheckpoint = currentValue; ASSERT(retVal < INT_MAX); return retVal; } /// /// Send a virtual processor ETW event /// void TraceVirtualProcessorEvent(ConcRT_EventType eventType, UCHAR level, DWORD schedulerId, DWORD vprocId) { if (g_TraceInfo._IsEnabled(level, VirtualProcessorEventFlag)) ThrowVirtualProcessorEvent(eventType, level, schedulerId, vprocId); } /// /// Send a virtual processor ETW event /// static void ThrowVirtualProcessorEvent(ConcRT_EventType eventType, UCHAR level, DWORD schedulerId, DWORD vprocId); }; } // namespace details } // namespace Concurrency