目录
前言
CPU时钟中断(HalpHpetClockInterrupt)
KiDispatchInterrupt
前言
在分析Windows线程主动切换时得知调用API时会触发线程切换,假设当前线程不调用API,操作系统如果实现线程切换呢?
- 异常 (缺页异常)...
- 中断 (时钟中断)...
CPU时钟中断(HalpHpetClockInterrupt)
Windbg输入!IDT 获取时钟中断函数
Windbg输入 bp HalpHpetClockInterrupt断点查看相关数据
时钟中断发生时IRQL等级1Fh.
Windbg输入kv获取调用堆栈,可以看出已经不再nt模块了.
时钟中断调用流程(Win7 x86 不同系统版本都会存在差异)如下:
HalpHpetClockInterrupt(hal) —— KeUpdateSystemTimeAssis(nt) —— KeUpdateSystemTime(nt) —— KeUpdateRunTime(nt) —— HalRequestSoftwareInterrupt(hal) —— KfLowerIrql(hal) —— HalpCheckForSoftwareInterrupt(hal) —— HalpDispatchSoftwareInterrupt(hal) —— KiDispatchInterrupt(nt)
KiDispatchInterrupt最终也会调用SwapContext来切换线程.
至此得知线程切换机制:
- 主动调用API函数
- 时钟中断
- 异常处理
KiDispatchInterrupt
首先判断时间碎片是否到期
到期跳转执行KiQuantumEnd,此函数会重设时间碎片,并切换线程.
如果时间碎片未到期会继续判断当前核有无下个执行线程,有的话也会触发线程切换.
参考WRK实现:
VOID
KiQuantumEnd (
VOID
)
/*++
Routine Description:
This function is called when a quantum end event occurs on the current
processor. Its function is to determine whether the thread priority should
be decremented and whether a redispatch of the processor should occur.
N.B. This function is called at DISPATCH level and returns at DISPATCH
level.
Arguments:
None.
Return Value:
None.
--*/
{
PKPRCB Prcb;
PKPROCESS Process;
PRKTHREAD Thread;
PRKTHREAD NewThread;
//
// If DPC thread activation is requested, then set the DPC event.
//
Prcb = KeGetCurrentPrcb();
Thread = KeGetCurrentThread();
if (InterlockedExchange(&Prcb->DpcSetEventRequest, FALSE) == TRUE) {
KeSetEvent(&Prcb->DpcEvent, 0, FALSE);
}
//
// Raise IRQL to SYNCH level, acquire the thread lock, and acquire the
// PRCB lock.
//
// If the quantum has expired for the current thread, then update its
// quantum and priority.
//
KeRaiseIrqlToSynchLevel();
KiAcquireThreadLock(Thread);
KiAcquirePrcbLock(Prcb);
if (Thread->Quantum <= 0) { //判断时间碎片
//
// If quantum runout is disabled for the thread's process and
// the thread is running at a realtime priority, then set the
// thread quantum to the highest value and do not round robin
// at the thread's priority level. Otherwise, reset the thread
// quantum and decay the thread's priority as appropriate.
//
Process = Thread->ApcState.Process;
if ((Process->DisableQuantum != FALSE) &&
(Thread->Priority >= LOW_REALTIME_PRIORITY)) { //优先级
Thread->Quantum = MAXCHAR; //重新赋值时间碎片
} else {
Thread->Quantum = Thread->QuantumReset; //线程时间碎片默认值
//
// Compute the new thread priority and attempt to reschedule the
// current processor.
//
// N.B. The new priority will never be greater than the previous
// priority.
//
Thread->Priority = KiComputeNewPriority(Thread, 1); //调整线程优先级
if (Prcb->NextThread == NULL) {
if ((NewThread = KiSelectReadyThread(Thread->Priority, Prcb)) != NULL) {
NewThread->State = Standby;
Prcb->NextThread = NewThread;
}
} else {
Thread->Preempted = FALSE; //抢占
}
}
}
//
// Release the thread lock.
//
// If a thread was scheduled for execution on the current processor, then
// acquire the PRCB lock, set the current thread to the new thread, set
// next thread to NULL, set the thread state to running, release the PRCB
// lock, set the wait reason, ready the old thread, and swap context to
// the new thread.
//
KiReleaseThreadLock(Thread);
if (Prcb->NextThread != NULL) {
KiSetContextSwapBusy(Thread);
NewThread = Prcb->NextThread;
Prcb->NextThread = NULL;
Prcb->CurrentThread = NewThread;
NewThread->State = Running;
Thread->WaitReason = WrQuantumEnd;
KxQueueReadyThread(Thread, Prcb);
Thread->WaitIrql = APC_LEVEL;
KiSwapContext(Thread, NewThread);
} else {
KiReleasePrcbLock(Prcb);
}
//
// Lower IRQL to DISPATCH level and return.
//
KeLowerIrql(DISPATCH_LEVEL);
return;
}
并没有内核解析出来全面,但是核心处理还在,首先判断是否碎片是否到期,到期通过线程优先级以及是否关闭时间碎片来决定重设时间碎片大小,如果时间碎片未到期通过判断当前核是否有下个执行线程来决定是否切换线程.
线程切换的三种情况:
- 当前线程主动调用API: API函数 KiSwapThread KiSwapContext SwapContext
- 当前线程时间片到期: KiDispatchInterrupt KiQuantumEnd SwapContext
- 有备用线程(KPCR.PrcbData.NextThread) KiDispatchInterrupt SwapContext