2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/ke/thrdschd.c
5 * PURPOSE: Kernel Thread Scheduler (Affinity, Priority, Scheduling)
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
9 /* INCLUDES ******************************************************************/
16 # define InterlockedOrSetMember(Destination, SetMember) \
17 InterlockedOr64((PLONG64)Destination, SetMember);
19 # define InterlockedOrSetMember(Destination, SetMember) \
20 InterlockedOr((PLONG)Destination, SetMember);
23 /* GLOBALS *******************************************************************/
25 ULONG_PTR KiIdleSummary
;
26 ULONG_PTR KiIdleSMTSummary
;
28 /* FUNCTIONS *****************************************************************/
32 KiIdleSchedule(IN PKPRCB Prcb
)
35 ASSERTMSG("SMP: Not yet implemented\n", FALSE
);
41 KiProcessDeferredReadyList(IN PKPRCB Prcb
)
43 PSINGLE_LIST_ENTRY ListEntry
;
46 /* Make sure there is something on the ready list */
47 ASSERT(Prcb
->DeferredReadyListHead
.Next
!= NULL
);
49 /* Get the first entry and clear the list */
50 ListEntry
= Prcb
->DeferredReadyListHead
.Next
;
51 Prcb
->DeferredReadyListHead
.Next
= NULL
;
53 /* Start processing loop */
56 /* Get the thread and advance to the next entry */
57 Thread
= CONTAINING_RECORD(ListEntry
, KTHREAD
, SwapListEntry
);
58 ListEntry
= ListEntry
->Next
;
60 /* Make the thread ready */
61 KiDeferredReadyThread(Thread
);
62 } while (ListEntry
!= NULL
);
64 /* Make sure the ready list is still empty */
65 ASSERT(Prcb
->DeferredReadyListHead
.Next
== NULL
);
70 KiQueueReadyThread(IN PKTHREAD Thread
,
73 /* Call the macro. We keep the API for compatibility with ASM code */
74 KxQueueReadyThread(Thread
, Prcb
);
79 KiDeferredReadyThread(IN PKTHREAD Thread
)
84 KPRIORITY OldPriority
;
88 ASSERT(Thread
->State
== DeferredReady
);
89 ASSERT((Thread
->Priority
>= 0) && (Thread
->Priority
<= HIGH_PRIORITY
));
91 /* Check if we have any adjusts to do */
92 if (Thread
->AdjustReason
== AdjustBoost
)
95 KiAcquireThreadLock(Thread
);
97 /* Check if the priority is low enough to qualify for boosting */
98 if ((Thread
->Priority
<= Thread
->AdjustIncrement
) &&
99 (Thread
->Priority
< (LOW_REALTIME_PRIORITY
- 3)) &&
100 !(Thread
->DisableBoost
))
102 /* Calculate the new priority based on the adjust increment */
103 OldPriority
= min(Thread
->AdjustIncrement
+ 1,
104 LOW_REALTIME_PRIORITY
- 3);
106 /* Make sure we're not decreasing outside of the priority range */
107 ASSERT((Thread
->PriorityDecrement
>= 0) &&
108 (Thread
->PriorityDecrement
<= Thread
->Priority
));
110 /* Calculate the new priority decrement based on the boost */
111 Thread
->PriorityDecrement
+= ((SCHAR
)OldPriority
- Thread
->Priority
);
113 /* Again verify that this decrement is valid */
114 ASSERT((Thread
->PriorityDecrement
>= 0) &&
115 (Thread
->PriorityDecrement
<= OldPriority
));
117 /* Set the new priority */
118 Thread
->Priority
= (SCHAR
)OldPriority
;
121 /* We need 4 quanta, make sure we have them, then decrease by one */
122 if (Thread
->Quantum
< 4) Thread
->Quantum
= 4;
125 /* Make sure the priority is still valid */
126 ASSERT((Thread
->Priority
>= 0) && (Thread
->Priority
<= HIGH_PRIORITY
));
128 /* Release the lock and clear the adjust reason */
129 KiReleaseThreadLock(Thread
);
130 Thread
->AdjustReason
= AdjustNone
;
132 else if (Thread
->AdjustReason
== AdjustUnwait
)
134 /* Acquire the thread lock and check if this is a real-time thread */
135 KiAcquireThreadLock(Thread
);
136 if (Thread
->Priority
< LOW_REALTIME_PRIORITY
)
138 /* It's not real time, but is it time critical? */
139 if (Thread
->BasePriority
>= (LOW_REALTIME_PRIORITY
- 2))
141 /* It is, so simply reset its quantum */
142 Thread
->Quantum
= Thread
->QuantumReset
;
146 /* Has the priority been adjusted previously? */
147 if (!(Thread
->PriorityDecrement
) && (Thread
->AdjustIncrement
))
149 /* Yes, reset its quantum */
150 Thread
->Quantum
= Thread
->QuantumReset
;
153 /* Wait code already handles quantum adjustment during APCs */
154 if (Thread
->WaitStatus
!= STATUS_KERNEL_APC
)
156 /* Decrease the quantum by one and check if we're out */
157 if (--Thread
->Quantum
<= 0)
159 /* We are, reset the quantum and get a new priority */
160 Thread
->Quantum
= Thread
->QuantumReset
;
161 Thread
->Priority
= KiComputeNewPriority(Thread
, 1);
166 /* Now check if we have no decrement and boosts are enabled */
167 if (!(Thread
->PriorityDecrement
) && !(Thread
->DisableBoost
))
169 /* Make sure we have an increment */
170 ASSERT(Thread
->AdjustIncrement
>= 0);
172 /* Calculate the new priority after the increment */
173 OldPriority
= Thread
->BasePriority
+ Thread
->AdjustIncrement
;
175 /* Check if this is a foreground process */
176 if (CONTAINING_RECORD(Thread
->ApcState
.Process
, EPROCESS
, Pcb
)->
177 Vm
.Flags
.MemoryPriority
== MEMORY_PRIORITY_FOREGROUND
)
179 /* Apply the foreground boost */
180 OldPriority
+= PsPrioritySeparation
;
183 /* Check if this new priority is higher */
184 if (OldPriority
> Thread
->Priority
)
186 /* Make sure we don't go into the real time range */
187 if (OldPriority
>= LOW_REALTIME_PRIORITY
)
189 /* Normalize it back down one notch */
190 OldPriority
= LOW_REALTIME_PRIORITY
- 1;
193 /* Check if the priority is higher then the boosted base */
194 if (OldPriority
> (Thread
->BasePriority
+
195 Thread
->AdjustIncrement
))
197 /* Setup a priority decrement to nullify the boost */
198 Thread
->PriorityDecrement
= ((SCHAR
)OldPriority
-
199 Thread
->BasePriority
-
200 Thread
->AdjustIncrement
);
203 /* Make sure that the priority decrement is valid */
204 ASSERT((Thread
->PriorityDecrement
>= 0) &&
205 (Thread
->PriorityDecrement
<= OldPriority
));
207 /* Set this new priority */
208 Thread
->Priority
= (SCHAR
)OldPriority
;
214 /* It's a real-time thread, so just reset its quantum */
215 Thread
->Quantum
= Thread
->QuantumReset
;
218 /* Make sure the priority makes sense */
219 ASSERT((Thread
->Priority
>= 0) && (Thread
->Priority
<= HIGH_PRIORITY
));
221 /* Release the thread lock and reset the adjust reason */
222 KiReleaseThreadLock(Thread
);
223 Thread
->AdjustReason
= AdjustNone
;
226 /* Clear thread preemption status and save current values */
227 Preempted
= Thread
->Preempted
;
228 OldPriority
= Thread
->Priority
;
229 Thread
->Preempted
= FALSE
;
231 /* Queue the thread on CPU 0 and get the PRCB and lock it */
232 Thread
->NextProcessor
= 0;
233 Prcb
= KiProcessorBlock
[0];
234 KiAcquirePrcbLock(Prcb
);
236 /* Check if we have an idle summary */
239 /* Clear it and set this thread as the next one */
241 Thread
->State
= Standby
;
242 Prcb
->NextThread
= Thread
;
244 /* Unlock the PRCB and return */
245 KiReleasePrcbLock(Prcb
);
249 /* Set the CPU number */
250 Thread
->NextProcessor
= (UCHAR
)Processor
;
252 /* Get the next scheduled thread */
253 NextThread
= Prcb
->NextThread
;
257 ASSERT(NextThread
->State
== Standby
);
259 /* Check if priority changed */
260 if (OldPriority
> NextThread
->Priority
)
262 /* Preempt the thread */
263 NextThread
->Preempted
= TRUE
;
265 /* Put this one as the next one */
266 Thread
->State
= Standby
;
267 Prcb
->NextThread
= Thread
;
269 /* Set it in deferred ready mode */
270 NextThread
->State
= DeferredReady
;
271 NextThread
->DeferredProcessor
= Prcb
->Number
;
272 KiReleasePrcbLock(Prcb
);
273 KiDeferredReadyThread(NextThread
);
279 /* Set the next thread as the current thread */
280 NextThread
= Prcb
->CurrentThread
;
281 if (OldPriority
> NextThread
->Priority
)
283 /* Preempt it if it's already running */
284 if (NextThread
->State
== Running
) NextThread
->Preempted
= TRUE
;
286 /* Set the thread on standby and as the next thread */
287 Thread
->State
= Standby
;
288 Prcb
->NextThread
= Thread
;
290 /* Release the lock */
291 KiReleasePrcbLock(Prcb
);
293 /* Check if we're running on another CPU */
294 if (KeGetCurrentProcessorNumber() != Thread
->NextProcessor
)
296 /* We are, send an IPI */
297 KiIpiSend(AFFINITY_MASK(Thread
->NextProcessor
), IPI_DPC
);
304 ASSERT((OldPriority
>= 0) && (OldPriority
<= HIGH_PRIORITY
));
306 /* Set this thread as ready */
307 Thread
->State
= Ready
;
308 Thread
->WaitTime
= KeTickCount
.LowPart
;
310 /* Insert this thread in the appropriate order */
311 Preempted
? InsertHeadList(&Prcb
->DispatcherReadyListHead
[OldPriority
],
312 &Thread
->WaitListEntry
) :
313 InsertTailList(&Prcb
->DispatcherReadyListHead
[OldPriority
],
314 &Thread
->WaitListEntry
);
316 /* Update the ready summary */
317 Prcb
->ReadySummary
|= PRIORITY_MASK(OldPriority
);
320 ASSERT(OldPriority
== Thread
->Priority
);
322 /* Release the lock */
323 KiReleasePrcbLock(Prcb
);
328 KiSelectNextThread(IN PKPRCB Prcb
)
332 /* Select a ready thread */
333 Thread
= KiSelectReadyThread(0, Prcb
);
336 /* Didn't find any, get the current idle thread */
337 Thread
= Prcb
->IdleThread
;
339 /* Enable idle scheduling */
340 InterlockedOrSetMember(&KiIdleSummary
, Prcb
->SetMember
);
341 Prcb
->IdleSchedule
= TRUE
;
343 /* FIXME: SMT support */
344 ASSERTMSG("SMP: Not yet implemented\n", FALSE
);
347 /* Sanity checks and return the thread */
348 ASSERT(Thread
!= NULL
);
349 ASSERT((Thread
->BasePriority
== 0) || (Thread
->Priority
!= 0));
355 KiSwapThread(IN PKTHREAD CurrentThread
,
358 BOOLEAN ApcState
= FALSE
;
362 ASSERT(KeGetCurrentIrql() >= DISPATCH_LEVEL
);
364 /* Acquire the PRCB lock */
365 KiAcquirePrcbLock(Prcb
);
367 /* Get the next thread */
368 NextThread
= Prcb
->NextThread
;
371 /* Already got a thread, set it up */
372 Prcb
->NextThread
= NULL
;
373 Prcb
->CurrentThread
= NextThread
;
374 NextThread
->State
= Running
;
378 /* Try to find a ready thread */
379 NextThread
= KiSelectReadyThread(0, Prcb
);
383 Prcb
->CurrentThread
= NextThread
;
384 NextThread
->State
= Running
;
388 /* Set the idle summary */
389 InterlockedOrSetMember(&KiIdleSummary
, Prcb
->SetMember
);
391 /* Schedule the idle thread */
392 NextThread
= Prcb
->IdleThread
;
393 Prcb
->CurrentThread
= NextThread
;
394 NextThread
->State
= Running
;
398 /* Sanity check and release the PRCB */
399 ASSERT(CurrentThread
!= Prcb
->IdleThread
);
400 KiReleasePrcbLock(Prcb
);
402 /* Save the wait IRQL */
403 WaitIrql
= CurrentThread
->WaitIrql
;
406 ApcState
= KiSwapContext(WaitIrql
, CurrentThread
);
408 /* Get the wait status */
409 WaitStatus
= CurrentThread
->WaitStatus
;
411 /* Check if we need to deliver APCs */
414 /* Lower to APC_LEVEL */
415 KeLowerIrql(APC_LEVEL
);
418 KiDeliverApc(KernelMode
, NULL
, NULL
);
419 ASSERT(WaitIrql
== 0);
422 /* Lower IRQL back to what it was and return the wait status */
423 KeLowerIrql(WaitIrql
);
429 KiReadyThread(IN PKTHREAD Thread
)
431 IN PKPROCESS Process
= Thread
->ApcState
.Process
;
433 /* Check if the process is paged out */
434 if (Process
->State
!= ProcessInMemory
)
436 /* We don't page out processes in ROS */
439 else if (!Thread
->KernelStackResident
)
441 /* Increase the stack count */
442 ASSERT(Process
->StackCount
!= MAXULONG_PTR
);
443 Process
->StackCount
++;
445 /* Set the thread to transition */
446 ASSERT(Thread
->State
!= Transition
);
447 Thread
->State
= Transition
;
449 /* The stack is always resident in ROS */
454 /* Insert the thread on the deferred ready list */
455 KiInsertDeferredReadyList(Thread
);
461 KiAdjustQuantumThread(IN PKTHREAD Thread
)
463 PKPRCB Prcb
= KeGetCurrentPrcb();
466 /* Acquire thread and PRCB lock */
467 KiAcquireThreadLock(Thread
);
468 KiAcquirePrcbLock(Prcb
);
470 /* Don't adjust for RT threads */
471 if ((Thread
->Priority
< LOW_REALTIME_PRIORITY
) &&
472 (Thread
->BasePriority
< (LOW_REALTIME_PRIORITY
- 2)))
474 /* Decrease Quantum by one and see if we've ran out */
475 if (--Thread
->Quantum
<= 0)
478 Thread
->Quantum
= Thread
->QuantumReset
;
480 /* Calculate new Priority */
481 Thread
->Priority
= KiComputeNewPriority(Thread
, 1);
483 /* Check if there's no next thread scheduled */
484 if (!Prcb
->NextThread
)
486 /* Select a ready thread and check if we found one */
487 NextThread
= KiSelectReadyThread(Thread
->Priority
, Prcb
);
490 /* Set it on standby and switch to it */
491 NextThread
->State
= Standby
;
492 Prcb
->NextThread
= NextThread
;
497 /* This thread can be preempted again */
498 Thread
->Preempted
= FALSE
;
504 KiReleasePrcbLock(Prcb
);
505 KiReleaseThreadLock(Thread
);
506 KiExitDispatcher(Thread
->WaitIrql
);
511 KiSetPriorityThread(IN PKTHREAD Thread
,
512 IN KPRIORITY Priority
)
516 BOOLEAN RequestInterrupt
= FALSE
;
517 KPRIORITY OldPriority
;
519 ASSERT((Priority
>= 0) && (Priority
<= HIGH_PRIORITY
));
521 /* Check if priority changed */
522 if (Thread
->Priority
!= Priority
)
524 /* Loop priority setting in case we need to start over */
527 /* Choose action based on thread's state */
528 if (Thread
->State
== Ready
)
530 /* Make sure we're not on the ready queue */
531 if (!Thread
->ProcessReadyQueue
)
533 /* Get the PRCB for the thread and lock it */
534 Processor
= Thread
->NextProcessor
;
535 Prcb
= KiProcessorBlock
[Processor
];
536 KiAcquirePrcbLock(Prcb
);
538 /* Make sure the thread is still ready and on this CPU */
539 if ((Thread
->State
== Ready
) &&
540 (Thread
->NextProcessor
== Prcb
->Number
))
543 ASSERT((Prcb
->ReadySummary
&
544 PRIORITY_MASK(Thread
->Priority
)));
546 /* Remove it from the current queue */
547 if (RemoveEntryList(&Thread
->WaitListEntry
))
549 /* Update the ready summary */
550 Prcb
->ReadySummary
^= PRIORITY_MASK(Thread
->
554 /* Update priority */
555 Thread
->Priority
= (SCHAR
)Priority
;
557 /* Re-insert it at its current priority */
558 KiInsertDeferredReadyList(Thread
);
560 /* Release the PRCB Lock */
561 KiReleasePrcbLock(Prcb
);
565 /* Release the lock and loop again */
566 KiReleasePrcbLock(Prcb
);
572 /* It's already on the ready queue, just update priority */
573 Thread
->Priority
= (SCHAR
)Priority
;
576 else if (Thread
->State
== Standby
)
578 /* Get the PRCB for the thread and lock it */
579 Processor
= Thread
->NextProcessor
;
580 Prcb
= KiProcessorBlock
[Processor
];
581 KiAcquirePrcbLock(Prcb
);
583 /* Check if we're still the next thread to run */
584 if (Thread
== Prcb
->NextThread
)
586 /* Get the old priority and update ours */
587 OldPriority
= Thread
->Priority
;
588 Thread
->Priority
= (SCHAR
)Priority
;
590 /* Check if there was a change */
591 if (Priority
< OldPriority
)
593 /* Find a new thread */
594 NewThread
= KiSelectReadyThread(Priority
+ 1, Prcb
);
597 /* Found a new one, set it on standby */
598 NewThread
->State
= Standby
;
599 Prcb
->NextThread
= NewThread
;
601 /* Dispatch our thread */
602 KiInsertDeferredReadyList(Thread
);
606 /* Release the PRCB lock */
607 KiReleasePrcbLock(Prcb
);
611 /* Release the lock and try again */
612 KiReleasePrcbLock(Prcb
);
616 else if (Thread
->State
== Running
)
618 /* Get the PRCB for the thread and lock it */
619 Processor
= Thread
->NextProcessor
;
620 Prcb
= KiProcessorBlock
[Processor
];
621 KiAcquirePrcbLock(Prcb
);
623 /* Check if we're still the current thread running */
624 if (Thread
== Prcb
->CurrentThread
)
626 /* Get the old priority and update ours */
627 OldPriority
= Thread
->Priority
;
628 Thread
->Priority
= (SCHAR
)Priority
;
630 /* Check if there was a change and there's no new thread */
631 if ((Priority
< OldPriority
) && !(Prcb
->NextThread
))
633 /* Find a new thread */
634 NewThread
= KiSelectReadyThread(Priority
+ 1, Prcb
);
637 /* Found a new one, set it on standby */
638 NewThread
->State
= Standby
;
639 Prcb
->NextThread
= NewThread
;
641 /* Request an interrupt */
642 RequestInterrupt
= TRUE
;
646 /* Release the lock and check if we need an interrupt */
647 KiReleasePrcbLock(Prcb
);
648 if (RequestInterrupt
)
650 /* Check if we're running on another CPU */
651 if (KeGetCurrentProcessorNumber() != Processor
)
653 /* We are, send an IPI */
654 KiIpiSend(AFFINITY_MASK(Processor
), IPI_DPC
);
660 /* Thread changed, release lock and restart */
661 KiReleasePrcbLock(Prcb
);
665 else if (Thread
->State
== DeferredReady
)
668 DPRINT1("Deferred state not yet supported\n");
673 /* Any other state, just change priority */
674 Thread
->Priority
= (SCHAR
)Priority
;
677 /* If we got here, then thread state was consistent, so bail out */
685 KiSetAffinityThread(IN PKTHREAD Thread
,
686 IN KAFFINITY Affinity
)
688 KAFFINITY OldAffinity
;
690 /* Get the current affinity */
691 OldAffinity
= Thread
->UserAffinity
;
693 /* Make sure that the affinity is valid */
694 if (((Affinity
& Thread
->ApcState
.Process
->Affinity
) != (Affinity
)) ||
697 /* Bugcheck the system */
698 KeBugCheck(INVALID_AFFINITY_SET
);
701 /* Update the new affinity */
702 Thread
->UserAffinity
= Affinity
;
704 /* Check if system affinity is disabled */
705 if (!Thread
->SystemAffinityActive
)
709 DPRINT1("Affinity support disabled!\n");
713 /* Return the old affinity */
718 // This macro exists because NtYieldExecution locklessly attempts to read from
719 // the KPRCB's ready summary, and the usual way of going through KeGetCurrentPrcb
720 // would require getting fs:1C first (or gs), and then doing another dereference.
721 // In an attempt to minimize the amount of instructions and potential race/tear
722 // that could happen, Windows seems to define this as a macro that directly acceses
723 // the ready summary through a single fs: read by going through the KPCR's PrcbData.
725 // See http://research.microsoft.com/en-us/collaboration/global/asia-pacific/
726 // programs/trk_case4_process-thread_management.pdf
728 // We need this per-arch because sometimes it's Prcb and sometimes PrcbData, and
729 // because on x86 it's FS, and on x64 it's GS (not sure what it is on ARM/PPC).
732 #define KiGetCurrentReadySummary() __readfsdword(FIELD_OFFSET(KIPCR, PrcbData.ReadySummary))
734 #define KiGetCurrentReadySummary() __readgsdword(FIELD_OFFSET(KIPCR, Prcb.ReadySummary))
736 #define KiGetCurrentReadySummary() KeGetCurrentPrcb()->ReadySummary
744 NtYieldExecution(VOID
)
749 PKTHREAD Thread
, NextThread
;
751 /* NB: No instructions (other than entry code) should preceed this line */
753 /* Fail if there's no ready summary */
754 if (!KiGetCurrentReadySummary()) return STATUS_NO_YIELD_PERFORMED
;
756 /* Now get the current thread, set the status... */
757 Status
= STATUS_NO_YIELD_PERFORMED
;
758 Thread
= KeGetCurrentThread();
760 /* Raise IRQL to synch and get the KPRCB now */
761 OldIrql
= KeRaiseIrqlToSynchLevel();
762 Prcb
= KeGetCurrentPrcb();
764 /* Now check if there's still a ready summary */
765 if (Prcb
->ReadySummary
)
767 /* Acquire thread and PRCB lock */
768 KiAcquireThreadLock(Thread
);
769 KiAcquirePrcbLock(Prcb
);
771 /* Find a new thread to run if none was selected */
772 if (!Prcb
->NextThread
) Prcb
->NextThread
= KiSelectReadyThread(1, Prcb
);
774 /* Make sure we still have a next thread to schedule */
775 NextThread
= Prcb
->NextThread
;
778 /* Reset quantum and recalculate priority */
779 Thread
->Quantum
= Thread
->QuantumReset
;
780 Thread
->Priority
= KiComputeNewPriority(Thread
, 1);
782 /* Release the thread lock */
783 KiReleaseThreadLock(Thread
);
785 /* Set context swap busy */
786 KiSetThreadSwapBusy(Thread
);
788 /* Set the new thread as running */
789 Prcb
->NextThread
= NULL
;
790 Prcb
->CurrentThread
= NextThread
;
791 NextThread
->State
= Running
;
793 /* Setup a yield wait and queue the thread */
794 Thread
->WaitReason
= WrYieldExecution
;
795 KxQueueReadyThread(Thread
, Prcb
);
797 /* Make it wait at APC_LEVEL */
798 Thread
->WaitIrql
= APC_LEVEL
;
801 ASSERT(OldIrql
<= DISPATCH_LEVEL
);
803 /* Swap to new thread */
804 KiSwapContext(APC_LEVEL
, Thread
);
805 Status
= STATUS_SUCCESS
;
809 /* Release the PRCB and thread lock */
810 KiReleasePrcbLock(Prcb
);
811 KiReleaseThreadLock(Thread
);
815 /* Lower IRQL and return */
816 KeLowerIrql(OldIrql
);