> That’s not how things work.
Actually, IIRC, this is exactly how they do. More on it below…
You need to stop thinking as the scheduler as a separate unit of execution (which needs
to queue DPCs or whatever).
“Scheduler” is a generic term describing a set of routines that deal with thread scheduling. There are different scheduler-related tasks that may get executed in different contexts, and selecting a thread to run on CPU is just one of them. Some of these tasks are implemented in KeXXX functions, but, IIRC, a _ particular_ task of scheduling a new thread is done only in DPC routine…
When a thread voluntarily yields the CPU with any of KeXXX functions, a new thread is not immediately get scheduled on the CPU. Instead, execution proceeds to an “idle thread” that proceeds low-priority DPCs, because CPU has nothing else to do until next clock tick… Every CPU has its own idle thread,
and the collection of these threads is know as “Idle” process with PID of 0. . When timer interrupt occurs it queues a lowest-priority DPC that will actually select new “non-idle” thread to run on CPU.
The scheduler is not executed by a separate unit or execution but in the context of the thread
which calls one of the wait (Ke) functions.
This is just one possible scenario when scheduler gets entered. In addition to that, it can get entered
from DPC routine (for example,when KeSetEvent() or even KeWaitXXX with zero timeout are called).
It is understandable that it in the latter scenario context switch cannot occur, but, as you must have already understood, switching contexts is just one of scheduler’s tasks…
At that point it obtains the dispatcher lock (hence the ‘raise’ to DISPATCH_LEVEL) and decides
what to do with the current thread, add it to the list of waiters for a dispatcher object and
switch to another thread or continue.
Actually, dispatcher lock acquisition raises IRQL to SYNCH_LEVEL and not to DISPATCH_LEVEL.
On UP system these two are the same, but on MP one SYNCH_LEVEL is above all DIRQLs.
Please note that only relatively small parts of scheduler’s code are executed under protection
of dispatcher spinlock, although practically all its code runs at DISPATCH_LEVEL to ensure that dispatcher does not re-enter itself …
In any case, if it decides to yield execution, it is not going to proceed to the new thread straight away, and, instead, wait for a next clock tick processing an “idle” thread meanwhile…
Anton Bassov