xref: /reactos/ntoskrnl/ke/thrdschd.c (revision 5140a990)
1 /*
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)
7  */
8 
9 /* INCLUDES ******************************************************************/
10 
11 #include <ntoskrnl.h>
12 #define NDEBUG
13 #include <debug.h>
14 
15 #ifdef _WIN64
16 # define InterlockedOrSetMember(Destination, SetMember) \
17     InterlockedOr64((PLONG64)Destination, SetMember);
18 #else
19 # define InterlockedOrSetMember(Destination, SetMember) \
20     InterlockedOr((PLONG)Destination, SetMember);
21 #endif
22 
23 /* GLOBALS *******************************************************************/
24 
25 KAFFINITY KiIdleSummary;
26 KAFFINITY KiIdleSMTSummary;
27 
28 /* FUNCTIONS *****************************************************************/
29 
30 PKTHREAD
31 FASTCALL
32 KiIdleSchedule(IN PKPRCB Prcb)
33 {
34     /* FIXME: TODO */
35     ASSERTMSG("SMP: Not yet implemented\n", FALSE);
36     return NULL;
37 }
38 
39 VOID
40 FASTCALL
41 KiProcessDeferredReadyList(IN PKPRCB Prcb)
42 {
43     PSINGLE_LIST_ENTRY ListEntry;
44     PKTHREAD Thread;
45 
46     /* Make sure there is something on the ready list */
47     ASSERT(Prcb->DeferredReadyListHead.Next != NULL);
48 
49     /* Get the first entry and clear the list */
50     ListEntry = Prcb->DeferredReadyListHead.Next;
51     Prcb->DeferredReadyListHead.Next = NULL;
52 
53     /* Start processing loop */
54     do
55     {
56         /* Get the thread and advance to the next entry */
57         Thread = CONTAINING_RECORD(ListEntry, KTHREAD, SwapListEntry);
58         ListEntry = ListEntry->Next;
59 
60         /* Make the thread ready */
61         KiDeferredReadyThread(Thread);
62     } while (ListEntry != NULL);
63 
64     /* Make sure the ready list is still empty */
65     ASSERT(Prcb->DeferredReadyListHead.Next == NULL);
66 }
67 
68 VOID
69 FASTCALL
70 KiQueueReadyThread(IN PKTHREAD Thread,
71                    IN PKPRCB Prcb)
72 {
73     /* Call the macro. We keep the API for compatibility with ASM code */
74     KxQueueReadyThread(Thread, Prcb);
75 }
76 
77 VOID
78 FASTCALL
79 KiDeferredReadyThread(IN PKTHREAD Thread)
80 {
81     PKPRCB Prcb;
82     BOOLEAN Preempted;
83     ULONG Processor = 0;
84     KPRIORITY OldPriority;
85     PKTHREAD NextThread;
86 
87     /* Sanity checks */
88     ASSERT(Thread->State == DeferredReady);
89     ASSERT((Thread->Priority >= 0) && (Thread->Priority <= HIGH_PRIORITY));
90 
91     /* Check if we have any adjusts to do */
92     if (Thread->AdjustReason == AdjustBoost)
93     {
94         /* Lock the thread */
95         KiAcquireThreadLock(Thread);
96 
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))
101         {
102             /* Calculate the new priority based on the adjust increment */
103             OldPriority = min(Thread->AdjustIncrement + 1,
104                               LOW_REALTIME_PRIORITY - 3);
105 
106             /* Make sure we're not decreasing outside of the priority range */
107             ASSERT((Thread->PriorityDecrement >= 0) &&
108                    (Thread->PriorityDecrement <= Thread->Priority));
109 
110             /* Calculate the new priority decrement based on the boost */
111             Thread->PriorityDecrement += ((SCHAR)OldPriority - Thread->Priority);
112 
113             /* Again verify that this decrement is valid */
114             ASSERT((Thread->PriorityDecrement >= 0) &&
115                    (Thread->PriorityDecrement <= OldPriority));
116 
117             /* Set the new priority */
118             Thread->Priority = (SCHAR)OldPriority;
119         }
120 
121         /* We need 4 quanta, make sure we have them, then decrease by one */
122         if (Thread->Quantum < 4) Thread->Quantum = 4;
123         Thread->Quantum--;
124 
125         /* Make sure the priority is still valid */
126         ASSERT((Thread->Priority >= 0) && (Thread->Priority <= HIGH_PRIORITY));
127 
128         /* Release the lock and clear the adjust reason */
129         KiReleaseThreadLock(Thread);
130         Thread->AdjustReason = AdjustNone;
131     }
132     else if (Thread->AdjustReason == AdjustUnwait)
133     {
134         /* Acquire the thread lock and check if this is a real-time thread */
135         KiAcquireThreadLock(Thread);
136         if (Thread->Priority < LOW_REALTIME_PRIORITY)
137         {
138             /* It's not real time, but is it time critical? */
139             if (Thread->BasePriority >= (LOW_REALTIME_PRIORITY - 2))
140             {
141                 /* It is, so simply reset its quantum */
142                 Thread->Quantum = Thread->QuantumReset;
143             }
144             else
145             {
146                 /* Has the priority been adjusted previously? */
147                 if (!(Thread->PriorityDecrement) && (Thread->AdjustIncrement))
148                 {
149                     /* Yes, reset its quantum */
150                     Thread->Quantum = Thread->QuantumReset;
151                 }
152 
153                 /* Wait code already handles quantum adjustment during APCs */
154                 if (Thread->WaitStatus != STATUS_KERNEL_APC)
155                 {
156                     /* Decrease the quantum by one and check if we're out */
157                     if (--Thread->Quantum <= 0)
158                     {
159                         /* We are, reset the quantum and get a new priority */
160                         Thread->Quantum = Thread->QuantumReset;
161                         Thread->Priority = KiComputeNewPriority(Thread, 1);
162                     }
163                 }
164             }
165 
166             /* Now check if we have no decrement and boosts are enabled */
167             if (!(Thread->PriorityDecrement) && !(Thread->DisableBoost))
168             {
169                 /* Make sure we have an increment */
170                 ASSERT(Thread->AdjustIncrement >= 0);
171 
172                 /* Calculate the new priority after the increment */
173                 OldPriority = Thread->BasePriority + Thread->AdjustIncrement;
174 
175                 /* Check if this is a foreground process */
176                 if (CONTAINING_RECORD(Thread->ApcState.Process, EPROCESS, Pcb)->
177                     Vm.Flags.MemoryPriority == MEMORY_PRIORITY_FOREGROUND)
178                 {
179                     /* Apply the foreground boost */
180                     OldPriority += PsPrioritySeparation;
181                 }
182 
183                 /* Check if this new priority is higher */
184                 if (OldPriority > Thread->Priority)
185                 {
186                     /* Make sure we don't go into the real time range */
187                     if (OldPriority >= LOW_REALTIME_PRIORITY)
188                     {
189                         /* Normalize it back down one notch */
190                         OldPriority = LOW_REALTIME_PRIORITY - 1;
191                     }
192 
193                     /* Check if the priority is higher then the boosted base */
194                     if (OldPriority > (Thread->BasePriority +
195                                        Thread->AdjustIncrement))
196                     {
197                         /* Setup a priority decrement to nullify the boost  */
198                         Thread->PriorityDecrement = ((SCHAR)OldPriority -
199                                                     Thread->BasePriority -
200                                                     Thread->AdjustIncrement);
201                     }
202 
203                     /* Make sure that the priority decrement is valid */
204                     ASSERT((Thread->PriorityDecrement >= 0) &&
205                            (Thread->PriorityDecrement <= OldPriority));
206 
207                     /* Set this new priority */
208                     Thread->Priority = (SCHAR)OldPriority;
209                 }
210             }
211         }
212         else
213         {
214             /* It's a real-time thread, so just reset its quantum */
215             Thread->Quantum = Thread->QuantumReset;
216         }
217 
218         /* Make sure the priority makes sense */
219         ASSERT((Thread->Priority >= 0) && (Thread->Priority <= HIGH_PRIORITY));
220 
221         /* Release the thread lock and reset the adjust reason */
222         KiReleaseThreadLock(Thread);
223         Thread->AdjustReason = AdjustNone;
224     }
225 
226     /* Clear thread preemption status and save current values */
227     Preempted = Thread->Preempted;
228     OldPriority = Thread->Priority;
229     Thread->Preempted = FALSE;
230 
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);
235 
236     /* Check if we have an idle summary */
237     if (KiIdleSummary)
238     {
239         /* Clear it and set this thread as the next one */
240         KiIdleSummary = 0;
241         Thread->State = Standby;
242         Prcb->NextThread = Thread;
243 
244         /* Unlock the PRCB and return */
245         KiReleasePrcbLock(Prcb);
246         return;
247     }
248 
249     /* Set the CPU number */
250     Thread->NextProcessor = (UCHAR)Processor;
251 
252     /* Get the next scheduled thread */
253     NextThread = Prcb->NextThread;
254     if (NextThread)
255     {
256         /* Sanity check */
257         ASSERT(NextThread->State == Standby);
258 
259         /* Check if priority changed */
260         if (OldPriority > NextThread->Priority)
261         {
262             /* Preempt the thread */
263             NextThread->Preempted = TRUE;
264 
265             /* Put this one as the next one */
266             Thread->State = Standby;
267             Prcb->NextThread = Thread;
268 
269             /* Set it in deferred ready mode */
270             NextThread->State = DeferredReady;
271             NextThread->DeferredProcessor = Prcb->Number;
272             KiReleasePrcbLock(Prcb);
273             KiDeferredReadyThread(NextThread);
274             return;
275         }
276     }
277     else
278     {
279         /* Set the next thread as the current thread */
280         NextThread = Prcb->CurrentThread;
281         if (OldPriority > NextThread->Priority)
282         {
283             /* Preempt it if it's already running */
284             if (NextThread->State == Running) NextThread->Preempted = TRUE;
285 
286             /* Set the thread on standby and as the next thread */
287             Thread->State = Standby;
288             Prcb->NextThread = Thread;
289 
290             /* Release the lock */
291             KiReleasePrcbLock(Prcb);
292 
293             /* Check if we're running on another CPU */
294             if (KeGetCurrentProcessorNumber() != Thread->NextProcessor)
295             {
296                 /* We are, send an IPI */
297                 KiIpiSend(AFFINITY_MASK(Thread->NextProcessor), IPI_DPC);
298             }
299             return;
300         }
301     }
302 
303     /* Sanity check */
304     ASSERT((OldPriority >= 0) && (OldPriority <= HIGH_PRIORITY));
305 
306     /* Set this thread as ready */
307     Thread->State = Ready;
308     Thread->WaitTime = KeTickCount.LowPart;
309 
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);
315 
316     /* Update the ready summary */
317     Prcb->ReadySummary |= PRIORITY_MASK(OldPriority);
318 
319     /* Sanity check */
320     ASSERT(OldPriority == Thread->Priority);
321 
322     /* Release the lock */
323     KiReleasePrcbLock(Prcb);
324 }
325 
326 PKTHREAD
327 FASTCALL
328 KiSelectNextThread(IN PKPRCB Prcb)
329 {
330     PKTHREAD Thread;
331 
332     /* Select a ready thread */
333     Thread = KiSelectReadyThread(0, Prcb);
334     if (!Thread)
335     {
336         /* Didn't find any, get the current idle thread */
337         Thread = Prcb->IdleThread;
338 
339         /* Enable idle scheduling */
340         InterlockedOrSetMember(&KiIdleSummary, Prcb->SetMember);
341         Prcb->IdleSchedule = TRUE;
342 
343         /* FIXME: SMT support */
344         ASSERTMSG("SMP: Not yet implemented\n", FALSE);
345     }
346 
347     /* Sanity checks and return the thread */
348     ASSERT(Thread != NULL);
349     ASSERT((Thread->BasePriority == 0) || (Thread->Priority != 0));
350     return Thread;
351 }
352 
353 LONG_PTR
354 FASTCALL
355 KiSwapThread(IN PKTHREAD CurrentThread,
356              IN PKPRCB Prcb)
357 {
358     BOOLEAN ApcState = FALSE;
359     KIRQL WaitIrql;
360     LONG_PTR WaitStatus;
361     PKTHREAD NextThread;
362     ASSERT(KeGetCurrentIrql() >= DISPATCH_LEVEL);
363 
364     /* Acquire the PRCB lock */
365     KiAcquirePrcbLock(Prcb);
366 
367     /* Get the next thread */
368     NextThread = Prcb->NextThread;
369     if (NextThread)
370     {
371         /* Already got a thread, set it up */
372         Prcb->NextThread = NULL;
373         Prcb->CurrentThread = NextThread;
374         NextThread->State = Running;
375     }
376     else
377     {
378         /* Try to find a ready thread */
379         NextThread = KiSelectReadyThread(0, Prcb);
380         if (NextThread)
381         {
382             /* Switch to it */
383             Prcb->CurrentThread = NextThread;
384             NextThread->State = Running;
385         }
386         else
387         {
388             /* Set the idle summary */
389             InterlockedOrSetMember(&KiIdleSummary, Prcb->SetMember);
390 
391             /* Schedule the idle thread */
392             NextThread = Prcb->IdleThread;
393             Prcb->CurrentThread = NextThread;
394             NextThread->State = Running;
395         }
396     }
397 
398     /* Sanity check and release the PRCB */
399     ASSERT(CurrentThread != Prcb->IdleThread);
400     KiReleasePrcbLock(Prcb);
401 
402     /* Save the wait IRQL */
403     WaitIrql = CurrentThread->WaitIrql;
404 
405     /* Swap contexts */
406     ApcState = KiSwapContext(WaitIrql, CurrentThread);
407 
408     /* Get the wait status */
409     WaitStatus = CurrentThread->WaitStatus;
410 
411     /* Check if we need to deliver APCs */
412     if (ApcState)
413     {
414         /* Lower to APC_LEVEL */
415         KeLowerIrql(APC_LEVEL);
416 
417         /* Deliver APCs */
418         KiDeliverApc(KernelMode, NULL, NULL);
419         ASSERT(WaitIrql == 0);
420     }
421 
422     /* Lower IRQL back to what it was and return the wait status */
423     KeLowerIrql(WaitIrql);
424     return WaitStatus;
425 }
426 
427 VOID
428 NTAPI
429 KiReadyThread(IN PKTHREAD Thread)
430 {
431     IN PKPROCESS Process = Thread->ApcState.Process;
432 
433     /* Check if the process is paged out */
434     if (Process->State != ProcessInMemory)
435     {
436         /* We don't page out processes in ROS */
437         ASSERT(FALSE);
438     }
439     else if (!Thread->KernelStackResident)
440     {
441         /* Increase the stack count */
442         ASSERT(Process->StackCount != MAXULONG_PTR);
443         Process->StackCount++;
444 
445         /* Set the thread to transition */
446         ASSERT(Thread->State != Transition);
447         Thread->State = Transition;
448 
449         /* The stack is always resident in ROS */
450         ASSERT(FALSE);
451     }
452     else
453     {
454         /* Insert the thread on the deferred ready list */
455         KiInsertDeferredReadyList(Thread);
456     }
457 }
458 
459 VOID
460 NTAPI
461 KiAdjustQuantumThread(IN PKTHREAD Thread)
462 {
463     PKPRCB Prcb = KeGetCurrentPrcb();
464     PKTHREAD NextThread;
465 
466     /* Acquire thread and PRCB lock */
467     KiAcquireThreadLock(Thread);
468     KiAcquirePrcbLock(Prcb);
469 
470     /* Don't adjust for RT threads */
471     if ((Thread->Priority < LOW_REALTIME_PRIORITY) &&
472         (Thread->BasePriority < (LOW_REALTIME_PRIORITY - 2)))
473     {
474         /* Decrease Quantum by one and see if we've ran out */
475         if (--Thread->Quantum <= 0)
476         {
477             /* Return quantum */
478             Thread->Quantum = Thread->QuantumReset;
479 
480             /* Calculate new Priority */
481             Thread->Priority = KiComputeNewPriority(Thread, 1);
482 
483             /* Check if there's no next thread scheduled */
484             if (!Prcb->NextThread)
485             {
486                 /* Select a ready thread and check if we found one */
487                 NextThread = KiSelectReadyThread(Thread->Priority, Prcb);
488                 if (NextThread)
489                 {
490                     /* Set it on standby and switch to it */
491                     NextThread->State = Standby;
492                     Prcb->NextThread = NextThread;
493                 }
494             }
495             else
496             {
497                 /* This thread can be preempted again */
498                 Thread->Preempted = FALSE;
499             }
500         }
501     }
502 
503     /* Release locks */
504     KiReleasePrcbLock(Prcb);
505     KiReleaseThreadLock(Thread);
506     KiExitDispatcher(Thread->WaitIrql);
507 }
508 
509 VOID
510 FASTCALL
511 KiSetPriorityThread(IN PKTHREAD Thread,
512                     IN KPRIORITY Priority)
513 {
514     PKPRCB Prcb;
515     ULONG Processor;
516     BOOLEAN RequestInterrupt = FALSE;
517     KPRIORITY OldPriority;
518     PKTHREAD NewThread;
519     ASSERT((Priority >= 0) && (Priority <= HIGH_PRIORITY));
520 
521     /* Check if priority changed */
522     if (Thread->Priority != Priority)
523     {
524         /* Loop priority setting in case we need to start over */
525         for (;;)
526         {
527             /* Choose action based on thread's state */
528             if (Thread->State == Ready)
529             {
530                 /* Make sure we're not on the ready queue */
531                 if (!Thread->ProcessReadyQueue)
532                 {
533                     /* Get the PRCB for the thread and lock it */
534                     Processor = Thread->NextProcessor;
535                     Prcb = KiProcessorBlock[Processor];
536                     KiAcquirePrcbLock(Prcb);
537 
538                     /* Make sure the thread is still ready and on this CPU */
539                     if ((Thread->State == Ready) &&
540                         (Thread->NextProcessor == Prcb->Number))
541                     {
542                         /* Sanity check */
543                         ASSERT((Prcb->ReadySummary &
544                                 PRIORITY_MASK(Thread->Priority)));
545 
546                         /* Remove it from the current queue */
547                         if (RemoveEntryList(&Thread->WaitListEntry))
548                         {
549                             /* Update the ready summary */
550                             Prcb->ReadySummary ^= PRIORITY_MASK(Thread->
551                                                                 Priority);
552                         }
553 
554                         /* Update priority */
555                         Thread->Priority = (SCHAR)Priority;
556 
557                         /* Re-insert it at its current priority */
558                         KiInsertDeferredReadyList(Thread);
559 
560                         /* Release the PRCB Lock */
561                         KiReleasePrcbLock(Prcb);
562                     }
563                     else
564                     {
565                         /* Release the lock and loop again */
566                         KiReleasePrcbLock(Prcb);
567                         continue;
568                     }
569                 }
570                 else
571                 {
572                     /* It's already on the ready queue, just update priority */
573                     Thread->Priority = (SCHAR)Priority;
574                 }
575             }
576             else if (Thread->State == Standby)
577             {
578                 /* Get the PRCB for the thread and lock it */
579                 Processor = Thread->NextProcessor;
580                 Prcb = KiProcessorBlock[Processor];
581                 KiAcquirePrcbLock(Prcb);
582 
583                 /* Check if we're still the next thread to run */
584                 if (Thread == Prcb->NextThread)
585                 {
586                     /* Get the old priority and update ours */
587                     OldPriority = Thread->Priority;
588                     Thread->Priority = (SCHAR)Priority;
589 
590                     /* Check if there was a change */
591                     if (Priority < OldPriority)
592                     {
593                         /* Find a new thread */
594                         NewThread = KiSelectReadyThread(Priority + 1, Prcb);
595                         if (NewThread)
596                         {
597                             /* Found a new one, set it on standby */
598                             NewThread->State = Standby;
599                             Prcb->NextThread = NewThread;
600 
601                             /* Dispatch our thread */
602                             KiInsertDeferredReadyList(Thread);
603                         }
604                     }
605 
606                     /* Release the PRCB lock */
607                     KiReleasePrcbLock(Prcb);
608                 }
609                 else
610                 {
611                     /* Release the lock and try again */
612                     KiReleasePrcbLock(Prcb);
613                     continue;
614                 }
615             }
616             else if (Thread->State == Running)
617             {
618                 /* Get the PRCB for the thread and lock it */
619                 Processor = Thread->NextProcessor;
620                 Prcb = KiProcessorBlock[Processor];
621                 KiAcquirePrcbLock(Prcb);
622 
623                 /* Check if we're still the current thread running */
624                 if (Thread == Prcb->CurrentThread)
625                 {
626                     /* Get the old priority and update ours */
627                     OldPriority = Thread->Priority;
628                     Thread->Priority = (SCHAR)Priority;
629 
630                     /* Check if there was a change and there's no new thread */
631                     if ((Priority < OldPriority) && !(Prcb->NextThread))
632                     {
633                         /* Find a new thread */
634                         NewThread = KiSelectReadyThread(Priority + 1, Prcb);
635                         if (NewThread)
636                         {
637                             /* Found a new one, set it on standby */
638                             NewThread->State = Standby;
639                             Prcb->NextThread = NewThread;
640 
641                             /* Request an interrupt */
642                             RequestInterrupt = TRUE;
643                         }
644                     }
645 
646                     /* Release the lock and check if we need an interrupt */
647                     KiReleasePrcbLock(Prcb);
648                     if (RequestInterrupt)
649                     {
650                         /* Check if we're running on another CPU */
651                         if (KeGetCurrentProcessorNumber() != Processor)
652                         {
653                             /* We are, send an IPI */
654                             KiIpiSend(AFFINITY_MASK(Processor), IPI_DPC);
655                         }
656                     }
657                 }
658                 else
659                 {
660                     /* Thread changed, release lock and restart */
661                     KiReleasePrcbLock(Prcb);
662                     continue;
663                 }
664             }
665             else if (Thread->State == DeferredReady)
666             {
667                 /* FIXME: TODO */
668                 DPRINT1("Deferred state not yet supported\n");
669                 ASSERT(FALSE);
670             }
671             else
672             {
673                 /* Any other state, just change priority */
674                 Thread->Priority = (SCHAR)Priority;
675             }
676 
677             /* If we got here, then thread state was consistent, so bail out */
678             break;
679         }
680     }
681 }
682 
683 KAFFINITY
684 FASTCALL
685 KiSetAffinityThread(IN PKTHREAD Thread,
686                     IN KAFFINITY Affinity)
687 {
688     KAFFINITY OldAffinity;
689 
690     /* Get the current affinity */
691     OldAffinity = Thread->UserAffinity;
692 
693     /* Make sure that the affinity is valid */
694     if (((Affinity & Thread->ApcState.Process->Affinity) != (Affinity)) ||
695         (!Affinity))
696     {
697         /* Bugcheck the system */
698         KeBugCheck(INVALID_AFFINITY_SET);
699     }
700 
701     /* Update the new affinity */
702     Thread->UserAffinity = Affinity;
703 
704     /* Check if system affinity is disabled */
705     if (!Thread->SystemAffinityActive)
706     {
707 #ifdef CONFIG_SMP
708         /* FIXME: TODO */
709         DPRINT1("Affinity support disabled!\n");
710 #endif
711     }
712 
713     /* Return the old affinity */
714     return OldAffinity;
715 }
716 
717 //
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.
724 //
725 // See http://research.microsoft.com/en-us/collaboration/global/asia-pacific/
726 //     programs/trk_case4_process-thread_management.pdf
727 //
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).
730 //
731 #ifdef _M_IX86
732 #define KiGetCurrentReadySummary() __readfsdword(FIELD_OFFSET(KIPCR, PrcbData.ReadySummary))
733 #elif _M_AMD64
734 #define KiGetCurrentReadySummary() __readgsdword(FIELD_OFFSET(KIPCR, Prcb.ReadySummary))
735 #else
736 #define KiGetCurrentReadySummary() KeGetCurrentPrcb()->ReadySummary
737 #endif
738 
739 /*
740  * @implemented
741  */
742 NTSTATUS
743 NTAPI
744 NtYieldExecution(VOID)
745 {
746     NTSTATUS Status;
747     KIRQL OldIrql;
748     PKPRCB Prcb;
749     PKTHREAD Thread, NextThread;
750 
751     /* NB: No instructions (other than entry code) should preceed this line */
752 
753     /* Fail if there's no ready summary */
754     if (!KiGetCurrentReadySummary()) return STATUS_NO_YIELD_PERFORMED;
755 
756     /* Now get the current thread, set the status... */
757     Status = STATUS_NO_YIELD_PERFORMED;
758     Thread = KeGetCurrentThread();
759 
760     /* Raise IRQL to synch and get the KPRCB now */
761     OldIrql = KeRaiseIrqlToSynchLevel();
762     Prcb = KeGetCurrentPrcb();
763 
764     /* Now check if there's still a ready summary */
765     if (Prcb->ReadySummary)
766     {
767         /* Acquire thread and PRCB lock */
768         KiAcquireThreadLock(Thread);
769         KiAcquirePrcbLock(Prcb);
770 
771         /* Find a new thread to run if none was selected */
772         if (!Prcb->NextThread) Prcb->NextThread = KiSelectReadyThread(1, Prcb);
773 
774         /* Make sure we still have a next thread to schedule */
775         NextThread = Prcb->NextThread;
776         if (NextThread)
777         {
778             /* Reset quantum and recalculate priority */
779             Thread->Quantum = Thread->QuantumReset;
780             Thread->Priority = KiComputeNewPriority(Thread, 1);
781 
782             /* Release the thread lock */
783             KiReleaseThreadLock(Thread);
784 
785             /* Set context swap busy */
786             KiSetThreadSwapBusy(Thread);
787 
788             /* Set the new thread as running */
789             Prcb->NextThread = NULL;
790             Prcb->CurrentThread = NextThread;
791             NextThread->State = Running;
792 
793             /* Setup a yield wait and queue the thread */
794             Thread->WaitReason = WrYieldExecution;
795             KxQueueReadyThread(Thread, Prcb);
796 
797             /* Make it wait at APC_LEVEL */
798             Thread->WaitIrql = APC_LEVEL;
799 
800             /* Sanity check */
801             ASSERT(OldIrql <= DISPATCH_LEVEL);
802 
803             /* Swap to new thread */
804             KiSwapContext(APC_LEVEL, Thread);
805             Status = STATUS_SUCCESS;
806         }
807         else
808         {
809             /* Release the PRCB and thread lock */
810             KiReleasePrcbLock(Prcb);
811             KiReleaseThreadLock(Thread);
812         }
813     }
814 
815     /* Lower IRQL and return */
816     KeLowerIrql(OldIrql);
817     return Status;
818 }
819