29.Windows线程切换之被动切换(KiDispatchInterrupt)

目录

前言

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
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>