selph
selph
发布于 2021-11-01 / 666 阅读
0
0

Windows 线程切换分析--基于ReactOS源码

简介

代码是从ReactOS里扒拉出来的,简单概括一下本文内容:线程调度的工作就是切换核心结构里的线程结构,切换内核栈,切换CR3

  • KiSetPriorityThread函数设置线程优先级,主要就是线程在running状态的时候进行设置,设置完之后会给kprcb填充下一个要执行的线程

  • KiIdleLoop函数检测Kprcb里下一个线程有值,有值就把下一个线程设置为当前线程,并进行调用KiSwapContext函数

  • KiSwapContext函数保存ebp、esi、edi,ebx寄存器,然后调用KiSwapContextInternal函数

  • KiSwapContextInternal函数预留出栈中8字节空间,把内核栈esp给到第一个参数里,跳转到KiSwapContextEntry函数

  • KiSwapContextEntry函数保存老线程的APC、异常链表、内核栈esp等信息到SwitchFrame结构,然后存到老线程的内核栈里,调用KiSwitchThreads进行切换

  • KiSwitchThreads函数切换新线程的内核栈esp,然后调用KiSwapContextExit函数

  • KiSwapContextExit函数切换cr3到新线程,把新线程的switchFrame结构还原到线程结构里,判断APC、DPC之后就返回了

切换线程结构的同时也就把线程上下文一起给切换了,线程上下文的各种信息,都是从Kprcb的CurrentThread的CONTEXT和TRAP_FRAME中获取的,所以只要切换了Kprcb的CurrentThread,也就同时给切换了

KiSetPriorityThread

这里主要关注running状态的线程被设置的处理:

  • 获取下一个核心,拿到kprcb结构
  • 如果运行的核心没变(下一次还在当前核心运行)
    • 保存老的优先级,设置新的优先级
    • 如果新的优先级比老的小,且kprcb里没有下一个线程,则去找一个就绪的线程
    • 设置新线程的状态,并设置为kprcb的下一个线程
VOID
FASTCALL
KiSetPriorityThread(IN PKTHREAD Thread,
                    IN KPRIORITY Priority)
{
    PKPRCB Prcb;
    ULONG Processor;
    BOOLEAN RequestInterrupt = FALSE;
    KPRIORITY OldPriority;
    PKTHREAD NewThread;
    ASSERT((Priority >= 0) && (Priority <= HIGH_PRIORITY));

    /* Check if priority changed */
    // 设置优先级
    if (Thread->Priority != Priority)
    {
        /* Loop priority setting in case we need to start over */
        for (;;)
        {
...
...
            else if (Thread->State == Running)
            {
                /* Get the PRCB for the thread and lock it */
                // 获取Kprcb
                Processor = Thread->NextProcessor;  // 取出下一个核心
                Prcb = KiProcessorBlock[Processor]; // 取出核心的Kprcb
                KiAcquirePrcbLock(Prcb);

                /* Check if we're still the current thread running */
                // 判断当前线程和目标线程Kprcb(每个核心都有一个)是否相等
                if (Thread == Prcb->CurrentThread)
                {
                    /* Get the old priority and update ours */
                    // 保存老的优先级
                    OldPriority = Thread->Priority;
                    // 设置新的优先级
                    Thread->Priority = (SCHAR)Priority;

                    /* Check if there was a change and there's no new thread */
                    // 小于老的优先级,且kprck里没有下一个线程
                    if ((Priority < OldPriority) && !(Prcb->NextThread))
                    {
                        /* Find a new thread */
                        // 寻找新的线程,先从就绪链表里找
                        NewThread = KiSelectReadyThread(Priority + 1, Prcb);
                        if (NewThread)
                        {
                            /* Found a new one, set it on standby */
                            // 设置新的线程状态
                            NewThread->State = Standby;
                            // 设置Kprcb的下一个线程为新线程
                            Prcb->NextThread = NewThread;

                            /* Request an interrupt */
                            // 请求中断
                            RequestInterrupt = TRUE;
                        }
                    }

                    /* Release the lock and check if we need an interrupt */
                    KiReleasePrcbLock(Prcb);
                    if (RequestInterrupt)
                    {
                        /* Check if we're running on another CPU */
                        if (KeGetCurrentProcessorNumber() != Processor)
                        {
                            /* We are, send an IPI */
                            KiIpiSend(AFFINITY_MASK(Processor), IPI_DPC);
                        }
                    }
                }
                else
                {
                    /* Thread changed, release lock and restart */
                    KiReleasePrcbLock(Prcb);
                    continue;
                }
            }
...
            /* If we got here, then thread state was consistent, so bail out */
            break;
        }
    }
}

KiIdleLoop

检查kprcb里的下一个线程,来调度执行:

  • 当kprcb里有下一个线程了
  • 获取当前线程结构和新线程结构
  • 将kprcb里的当前线程设置为下一个线程,下一个线程设置为空
  • 设置新线程状态为Running
  • 切换IRQL等级,调用KiSwapContext函数(下面会分析)
VOID
FASTCALL
KiIdleLoop(VOID)
{
    // 获取Kprcb
    PKPRCB Prcb = KeGetCurrentPrcb();
    // 线程结构
    PKTHREAD OldThread, NewThread;

    /* Initialize the idle loop: disable interrupts */
    _enable();
    YieldProcessor();
    YieldProcessor();
    _disable();

    /* Now loop forever */
    while (TRUE)// 无限循环
    {
        /* Check for pending timers, pending DPCs, or pending ready threads */
        if ((Prcb->DpcData[0].DpcQueueDepth) || //挂起定时器
            (Prcb->TimerRequest) ||             // 定时器
            (Prcb->DeferredReadyListHead.Next)) // 就绪链表
        {
            
            /* Quiesce the DPC software interrupt */
            // 清空一个软中断
            HalClearSoftwareInterrupt(DISPATCH_LEVEL);

            /* Handle it */
            // 处理
            KiRetireDpcList(Prcb);
        }

        /* Check if a new thread is scheduled for execution */
        // 检查新的线程需要调度执行
        if (Prcb->NextThread)
        {
            /* Enable interupts */
            // 开中断
            _enable();

            /* Capture current thread data */
            // 获取当前线程数据
            OldThread = Prcb->CurrentThread;
            NewThread = Prcb->NextThread;
            
            /* Set new thread data */
            // 设置新的线程,线程切换
            Prcb->NextThread = NULL;
            Prcb->CurrentThread = NewThread;

            /* The thread is now running */
            // 新的线程设置为运行
            NewThread->State = Running;

            /* Do the swap at SYNCH_LEVEL */
            // IRQL等级切换
            KfRaiseIrql(SYNCH_LEVEL);

            /* Switch away from the idle thread */
            // 从idle线程切换出来
            KiSwapContext(APC_LEVEL, OldThread);

            /* Go back to DISPATCH_LEVEL */
            // 设置IRLQL等级
            KeLowerIrql(DISPATCH_LEVEL);

            /* We are back in the idle thread -- disable interrupts again */
            _enable();
            YieldProcessor();
            YieldProcessor();
            _disable();
        }
        else
        {
            /* Continue staying idle. Note the HAL returns with interrupts on */
            // 把代码停留再IDLE线程里
            Prcb->PowerState.IdleFunction(&Prcb->PowerState);
        }
    }
}

KiSwapContext

保存寄存器环境,调用KiSwapContextInternal函数(下面会分析)

BOOLEAN
_declspec(naked)
FASTCALL
KiSwapContext(
    IN KIRQL WaitIrql,
    IN PKTHREAD CurrentThread
){ _asm{
//PUBLIC @KiSwapContext@8
//@KiSwapContext@8:
    /* Save 4 registers */
    sub esp, 4 * 4

    /* Save all the non-volatile ones */
    // 寄存器保存
    mov [esp+12], ebx
    mov [esp+8], esi
    mov [esp+4], edi
    mov [esp+0], ebp

    /* Get the wait IRQL */
    // 获取等待IRQL
    or dl, cl

    /* Do the swap with the registers correctly setup */
    // 调用API进行内部交换
    call KiSwapContextInternal//@0

    /* Return the registers */
    // 还原寄存器
    mov ebp, [esp+0]
    mov edi, [esp+4]
    mov esi, [esp+8]
    mov ebx, [esp+12]

    /* Clean stack */
    add esp, 4 * 4
    ret
}}

KiSwapContextInternal

将esp保存到了第一个参数里,跳转到了KiSwapContextEntry

_declspec(naked) KiSwapContextInternal(){ _asm{
//PUBLIC @KiSwapContextInternal@0
//@KiSwapContextInternal@0:
    /* Build switch frame */
    // 在某个函数最后面进行了设置
    sub esp, 2 * 4
    mov ecx, esp	// esp存到第一个参数里了
    jmp KiSwapContextEntry//@8
}}

KiSwapContextEntry

保存老线程的APC、异常链表、内核栈等信息到SwitchFrame结构,然后存到老线程的内核栈里,调用KiSwitchThreads进行切换

  • 保存了SwitchFrame的APC和异常链表
  • 自增上下文切换总数
  • 拿到新的和老的线程结构
  • 将老的线程内核栈设置为SwitchFrame
  • 调用KiSwitchThreads进行线程切换
typedef struct _KSWITCHFRAME
{
    PVOID ExceptionList;
    BOOLEAN ApcBypassDisable;
    PVOID RetAddr;
} KSWITCHFRAME, *PKSWITCHFRAME;

VOID
FASTCALL
KiSwapContextEntry(IN PKSWITCHFRAME SwitchFrame,
                   IN ULONG_PTR OldThreadAndApcFlag)
{
    PKIPCR Pcr = (PKIPCR)KeGetPcr();
    PKTHREAD OldThread, NewThread;

    /* Save APC bypass disable */
    // 保存了SwitchFrame的APC和异常链表
    SwitchFrame->ApcBypassDisable = OldThreadAndApcFlag & 3;
    SwitchFrame->ExceptionList = Pcr->NtTib.ExceptionList;

    /* Increase context switch count and check if tracing is enabled */
    // 自增上下文切换总数
    Pcr->ContextSwitches++;
    if (Pcr->PerfGlobalGroupMask)
    {
        /* We don't support this yet on x86 either */
        DPRINT1("WMI Tracing not supported\n");
        ASSERT(FALSE);
    }

    /* Get thread pointers */
    // 拿到新的老的线程
    OldThread = (PKTHREAD)(OldThreadAndApcFlag & ~3);
    NewThread = Pcr->PrcbData.CurrentThread;

    /* Get the old thread and set its kernel stack */
    // 老线程的内核栈 = SwitchFrame 建立旧的线程的内核栈
    OldThread->KernelStack = SwitchFrame;

    /* Do the switch */
    // 进行切换
    KiSwitchThreads(OldThread, NewThread->KernelStack);
}

KiSwitchThreads

切换内核栈esp,然后调用KiSwapContextExit

VOID
_declspec(naked)
FASTCALL
KiSwitchThreads(
    IN PKTHREAD OldThread,
    IN PKTHREAD NewThread
){ _asm{
//PUBLIC @KiSwitchThreads@8
//@KiSwitchThreads@8:
    /* Load the new kernel stack and switch OS to new thread */
    // 加载新的内核栈
    mov esp, edx    // 切换内核栈,KernelStack是线程切换的时候用来保存esp的
    call KiSwapContextExit//@8
    
    /* Now we're on the new thread. Return to the caller to restore registers */
    add esp, 2 * 4
    ret
}}

KiSwapContextExit

做最后的切换:页表切换

  • 老线程是参数传入的,新线程是kprcb获取的
  • 根据线程获取进程,判断是否是同进程切换
  • 非同进程线程切换,切换新的页表,同进程线程切换则不进行操作
  • 新线程的上下文切换次数自增
  • 加载异常链表到Kpcr
  • 判断DPC是否有效,无效就蓝屏
  • 判断APC是否启用,启用就返回TRUE
BOOLEAN
FASTCALL
KiSwapContextExit(IN PKTHREAD OldThread,
                  IN PKSWITCHFRAME SwitchFrame)
{
    PKIPCR Pcr = (PKIPCR)KeGetPcr();
    PKPROCESS OldProcess, NewProcess;   // 两个进程
    PKTHREAD NewThread;                 // 新的线程
    ARM_TTB_REGISTER TtbRegister;

    /* We are on the new thread stack now */
    // 获取新线程
    NewThread = Pcr->PrcbData.CurrentThread;

    /* Now we are the new thread. Check if it's in a new process */
    // 获取新老进程,可能是同进程切换,也可能使非同进程切换
    OldProcess = OldThread->ApcState.Process;
    NewProcess = NewThread->ApcState.Process;
    if (OldProcess != NewProcess)// 非同进程
    {
        // 切换cr3
        // 获取新的页表基址
        TtbRegister.AsUlong = NewProcess->DirectoryTableBase[0];
        ASSERT(TtbRegister.Reserved == 0);
        KeArmTranslationTableRegisterSet(TtbRegister);
    }

    /* Increase thread context switches */
    // 新线程的上下文切换次数自增
    NewThread->ContextSwitches++;

    /* Load data from switch frame */
    // 加载异常链表到Kpcr
    Pcr->NtTib.ExceptionList = SwitchFrame->ExceptionList;

    /* DPCs shouldn't be active */
    // 判断DPC是否有效
    if (Pcr->PrcbData.DpcRoutineActive)
    {
        // 蓝屏函数
        /* Crash the machine */
        KeBugCheckEx(ATTEMPTED_SWITCH_FROM_DPC,
                     (ULONG_PTR)OldThread,
                     (ULONG_PTR)NewThread,
                     (ULONG_PTR)OldThread->InitialStack,
                     0);
    }
    // 判断APC状态
    /* Kernel APCs may be pending */
    if (NewThread->ApcState.KernelApcPending)
    {
        // APC是否开启
        /* Are APCs enabled? */
        if (!NewThread->SpecialApcDisable)
        {
            // 发起APC执行
            /* Request APC delivery */
            if (SwitchFrame->ApcBypassDisable) HalRequestSoftwareInterrupt(APC_LEVEL);
            return TRUE;
        }
    }

    /* Return */
    return FALSE;
}

评论