前面分析了怎么样把一个线程放到延迟就绪队列,接着下来的问题就是这些在就绪队列里的线程什么时候开始运行呢?又是怎么样把就绪队列的线程取出来运行的呢?线程调度的触发事件有以下四种:
1) 线程进入就绪状态,如一个刚创建的线程,或者一个刚结束的线程。
2) 线程的时间片用完。
3) 线程调用系统服务发生等待,或者被系统改变其优先级。
4) 线程改变自己运行的处理器。
先来分析第一种情况,当线程结束时产生的调度。可以从Reactos的API里知道,终止一个线程可以使用API函数TerminateThread,而这个函数就是通过系统调用转换后,调用内核的函数NtTerminateThread,而NtTerminateThread又调用线程结束函数PspTerminateThreadByPointer,紧接着它又调用函数PspExitThread,在这个函数调用内核线程结束函数KeTerminateThread。在内核函数里调用函数KiSwapThread来进行线程调度切换。
第二种情况,就是线程的时间片用完。当每次时间中断后,就会调用时钟处理函数HalpClockInterrupt,最后依次调用下面的函数:
KeUpdateSystemTime 更新系统时钟函数。
HalRequestSoftwareInterrupt 请求软件中断函数。
HalEndSystemInterrupt 处理软件中断结束。
SoftIntHandlerTable2 通过中断表调用KiDispatchInterrupt函数。
KiSwapContextInternal 进行线程的上下文切换,也就是切换线程。
第三种情况,当系统发生等待时,比如调用内核函数KeWaitForSingleObject等待时,就会调用函数KiSwapThread来进行线程切换。
可以看到好几个地方都需要调用函数KiSwapThread来切换线程,其实它就是把延迟就绪队列里的线程选择合适的线程来运行。它的代码如下:
#001 NTSTATUS
#002 FASTCALL
#003 KiSwapThread(IN PKTHREAD CurrentThread,
#004 IN PKPRCB Prcb)
#005 {
#006 BOOLEAN ApcState = FALSE;
#007 KIRQL WaitIrql;
#008 LONG_PTR WaitStatus;
#009 PKTHREAD NextThread;
#010 ASSERT(KeGetCurrentIrql() >= DISPATCH_LEVEL);
#011
获取PRCB的锁。
#012 /* Acquire the PRCB lock */
#013 KiAcquirePrcbLock(Prcb);
#014
获取当前处理器的下一个运行的线程。
#015 /* Get the next thread */
#016 NextThread = Prcb->NextThread;
#017 if (NextThread)
#018 {
如果处理器对象里已经有一个准备好运行的线程,就立即运行它,设置线程的运行状态为运行。
#019 /* Already got a thread, set it up */
#020 Prcb->NextThread = NULL;
#021 Prcb->CurrentThread = NextThread;
#022 NextThread->State = Running;
#023 }
#024 else
#025 {
当前处理器对象里没有就绪线程,就从延迟队列里查找到合适的线程来运行。
#026 /* Try to find a ready thread */
#027 NextThread = KiSelectReadyThread(0, Prcb);
#028 if (NextThread)
#029 {
找到了可以运行的线程,设置这个线程为运行状态。
#030 /* Switch to it */
#031 Prcb->CurrentThread = NextThread;
#032 NextThread->State = Running;
#033 }
#034 else
#035 {
如果线程延迟就绪队列里没有可以运行的线程,就设置当前CPU为空闲状态。
#036 /* Set the idle summary */
#037 InterlockedOr((PLONG)&KiIdleSummary, Prcb->SetMember);
#038
选择处理器缺省的空闲线程来运行。
#039 /* Schedule the idle thread */
#040 NextThread = Prcb->IdleThread;
#041 Prcb->CurrentThread = NextThread;
#042 NextThread->State = Running;
#043 }
#044 }
#045
释放处理器锁。
#046 /* Sanity check and release the PRCB */
#047 ASSERT(CurrentThread != Prcb->IdleThread);
#048 KiReleasePrcbLock(Prcb);
#049
保存当前的IRQL。
#050 /* Save the wait IRQL */
#051 WaitIrql = CurrentThread->WaitIrql;
#052
更新下一个运行线程的内存空间。
#053 /* REACTOS Mm Hack of Doom */
#054 MiSyncForContextSwitch(NextThread);
#055
调用函数KiSwapContext来切换线程的运行环境。
#056 /* Swap contexts */
#057 ApcState = KiSwapContext(CurrentThread, NextThread);
#058
#059 /* Get the wait status */
#060 WaitStatus = CurrentThread->WaitStatus;
#061
检查是否需要进行异步调用。
#062 /* Check if we need to deliver APCs */
#063 if (ApcState)
#064 {
#065 /* Lower to APC_LEVEL */
#066 KeLowerIrql(APC_LEVEL);
#067
#068 /* Deliver APCs */
#069 KiDeliverApc(KernelMode, NULL, NULL);
#070 ASSERT(WaitIrql == 0);
#071 }
#072
设置为低的优先级。
#073 /* Lower IRQL back to what it was and return the wait status */
#074 KeLowerIrql(WaitIrql);
#075 return WaitStatus;
#076 }