Windows中的线程调度

本文介绍了Windows操作系统中的线程调度机制,包括线程优先级、状态转移、时限管理和优先级调度等内容。通过分析线程对象的优先级计算及状态转换过程,帮助读者理解线程在系统中的运行机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 Windows中的线程调度

1  线程优先级

线程优先级分为3种类别:实时类别:16-31;动态类别:1-15;系统类别:0

线程对象得KTHREAD 的BasePriority 和Priority 分别为线程的静态和动态优先级。

KiComputeNewPriority 是计算Priority 值的函数,代码如下:

FORCEINLINE SCHAR KiComputeNewPriority(IN PKTHREAD Thread,IN SCHAR Adjustment)
{
    SCHAR Priority;
    //
    // Compute the new thread priority.
    //

    ASSERT((Thread->PriorityDecrement >= 0) && (Thread->PriorityDecrement <= Thread->Priority));
    ASSERT((Thread->Priority < LOW_REALTIME_PRIORITY) ? TRUE : (Thread->PriorityDecrement == 0));

    Priority = Thread->Priority;
    if (Priority < LOW_REALTIME_PRIORITY) {
        Priority = Priority - Thread->PriorityDecrement - Adjustment;
        if (Priority < Thread->BasePriority) {
            Priority = Thread->BasePriority;
        }

        Thread->PriorityDecrement = 0;
    }

    ASSERT((Thread->BasePriority == 0) || (Priority != 0));

    return Priority;
}

线程的优先级重新计算只针对非实时优先级类别的线程,并且新的优先级不会低于基本优先级。

执行体在创建进程时,调用PspComputeQuantumAndPriority 来计算进程的优先级。

PspComputeQuantumAndPriority 根据EPROCESS对象的PrioriytClass 查表获得内核层优先级定义,即全局变量PspPriorityTable,相关定义如下:

#define PROCESS_PRIORITY_CLASS_UNKNOWN      0

#define PROCESS_PRIORITY_CLASS_IDLE         1

#define PROCESS_PRIORITY_CLASS_NORMAL       2

#define PROCESS_PRIORITY_CLASS_HIGH         3

#define PROCESS_PRIORITY_CLASS_REALTIME     4

#define PROCESS_PRIORITY_CLASS_BELOW_NORMAL 5

#define PROCESS_PRIORITY_CLASS_ABOVE_NORMAL 6

const KPRIORITY PspPriorityTable[PROCESS_PRIORITY_CLASS_ABOVE_NORMAL+1] = {8,4,8,13,24,6,10};

执行体层的优先级经过全局表PspPriorityTable变换后,就成了内核层的0~31优先级值。

带有“KPRIORITY Increment”参数,函数包括KeInsertQueueApc、KePulseEvent、KeSetEvent、KeReleaseMutant、KeReleaseSemaphore、KeSetProcess、KeBoostProirityThread、KeTeminateThread。在Windows中优先级提升的典型情形:

  1. 当一个I/O 操作完成时,正在等待此I/O 的线程有更多的机会被立即执行
  2. 一个线程在等待到事件和信号量以后,其优先级得到提升
  3. 前台线程从等待状态中醒来时,优先级提升
  4. 在平衡集管理器系统线程中,扫描那些已金融就绪状态4s 但尚未被执行的线程,将它们的优先级提升至15
  5. 窗口线程因窗口的活动(比如窗口消息到来)而醒来时,优先级提升

2  线程状态转移

KTHREAD 的State 反映了线程的当前调度状态,类型为KTHREAD_STATE,

typedef enum _KTHREAD_STATE {
    Initialized,
    Ready,
    Running,
    Standby,
    Terminated,
    Waiting,
    Transition,
    DeferredReady,
    GateWait
} KTHREAD_STATE;

线程状态说明:

  1. 已初始化(Initialized): 一个线程对象的内部已初始化,是线程创建过程中的一个内部状态,此时线程尚未加入到进程的线程链表中,也没有启动
  2. 就绪(Ready): 该线程已经准备就绪,等待被调度执行
  3. 运行(Running) : 线程正在运行
  4. 备用(Standby) : 处于备用状态的线程已经被选中作为某个处理器上下一个要运行的线程
  5. 已终止(Terminated): 表示线程已经完成任务,正在进行资源回收
  6. 等待(Waiting) : 表示一个线程正在等待某个条件。
  7. 转移(Transition) : 处于转移状态的线程已经准备好运行,但是它的内核栈不在内存中
  8. 延迟的就绪(DeferredReady) : 处于延迟的就绪状态的线程也已经准备好可以运行了,但是,与就绪状态不同的是,它尚未确定在哪个处理器上运行
  9. 门等待(GateWait) : 线程正在等待一个门对象

Wrk 中的线程状态转移图:

状态转移关键函数KiReadyThread

VOID FASTCALL KiReadyThread(IN PKTHREAD Thread)
{
    PKPROCESS Process;
    //
    // If the thread's process is not in memory, then insert the thread in
    // the process ready queue and inswap the process.
    //

    Process = Thread->ApcState.Process;

    if (Process->State != ProcessInMemory) {
        Thread->State = Ready;
        Thread->ProcessReadyQueue = TRUE;
        InsertTailList(&Process->ReadyListHead, &Thread->WaitListEntry);

        if (Process->State == ProcessOutOfMemory) {
            Process->State = ProcessInTransition;
            InterlockedPushEntrySingleList(&KiProcessInSwapListHead,
                &Process->SwapListEntry);

            KiSetInternalEvent(&KiSwapEvent, KiSwappingThread);
        }
        return;
    }
    else if (Thread->KernelStackResident == FALSE) {
        //
        // The thread's kernel stack is not resident. Increment the process
        // stack count, set the state of the thread to transition, insert
        // the thread in the kernel stack inswap list, and set the kernel
        // stack inswap event.
        //

        ASSERT(Process->StackCount != MAXULONG_PTR);

        Process->StackCount += 1;
        ASSERT(Thread->State != Transition);
        Thread->State = Transition;
        InterlockedPushEntrySingleList(&KiStackInSwapListHead,
            &Thread->SwapListEntry);

        KiSetInternalEvent(&KiSwapEvent, KiSwappingThread);
        return;
    }
    else {
        //
        // Insert the specified thread in the deferred ready list.
        //

        KiInsertDeferredReadyList(Thread);
        return;
    }
}

在wrk 中,KiReadyThread 函数在以下情况下被调用:

  1. 当一个线程被接触等待时,见KiUnwaitThread 函数
  2. 当一个内核队列对象(KQUEUE) 中有新的成员插入并且满足必要的条件时,见KiInsertQueue 函数
  3. 当一个线程被附载(attch)到一个新的进程中时,见KiAttachProcess 函数
  4. 在KeSetEventBoostPriority 函数,如同第一种情形
  5. 在处理环绕进程链表时,见KiInSwapProcess
  6. 在处理换出进程链表时,见KiOutSwapProcess
  7. 在创建线程的函数(PspCreateThread)时,见KeReadThread

延迟的就绪状态的线程到执行,首先KiReadyThread 调用KiInsertDeferredReadyList 把一个线程插入到当前处理器PRCB的延迟就绪表中。调度器获得控制权,KiProcessDeferredReadyList 遍历当前处理器的延迟就绪链表,对每个线程调用KiDeferredReadyThread ,变成就绪或备用状态

一个状态或者备用状态的线程如何被选出来运行。首先,先了解处理器的全局控制块数据结构KPRCB,

typedef struct _KPRCB {

    ......

    struct _KTHREAD *CurrentThread;
    struct _KTHREAD *NextThread;
    struct _KTHREAD *IdleThread;
    volatile BOOLEAN IdleSchedule;      
    ULONG ReadySummary;
    ULONG QueueIndex;
    LIST_ENTRY DispatcherReadyListHead[MAXIMUM_PRIORITY];
    SINGLE_LIST_ENTRY DeferredReadyListHead;
    .......

} KPRCB, *PKPRCB, *RESTRICTED_POINTER PRKPRCB;

KPRCB 中,CurrentThread,NextThread,IdleThread 分别指向该处理器上当前正在运行的线程(运行状态),下一个要运行的线程(备用状态),空闲线程(完成初始化后的)。

DispatcherReadyListHead 数组中的线程如何转移到NextThread 或CurrentThread 中。在wrk 中,KiSelectReadyThread,KiFindReadyThread 实现转移。

在wrk 中,以下一些调度点上,一个线程有机会变成运行状态:

  1. 在处理器的空闲循环中,若处理器的空闲调度标志(即KPRCB的IdleSchedule)被置为TRUE,则调用KiIdleSchedule。如果处理器上的备用线程(NextThread)不为空,则让备用线程运行,否则调用KiSelectReadyThread 找到要运行的线程。如果当前处理器找不到合适的线程,则cong 其他处理器上寻找适合在当处理器上运行的线程(通过调用KiFindReadyThread)。
  2. 调度点KiSwapThread
  3. 当前线程的时限已用完,调度点KiQuantumEnd
  4. 一个线程主动放弃执行权,调度点NtYieldExecution
  5. 线程的优先级、亲和性发生变化或时限调整时,同样会导致线程状态发生变化。包括KiSetPriorityThread、KeRevertToUserAffinityThread、KeSetSystemAffinityThread、KiAdjustQuanturnThread。

一个线程在自身运行条件满足的情况下的状态转移图:

在wrk 中等待时通过KeDelayExecutionThread、KeWaitForSingleObject 、

KeWaitForMultipleObjects 函数来实现。

线程在等待时的状态转移图:

3  时限管理

线程对象KTHREAD :Quantum 记录了该线程的当前时限中还剩下多少时间单位,QuantumReset 记录了这个线程的时限重置值,即一个完整时限的时间单位。

4  优先级调度和环境切换

串联Windows 的优先级调度算法。首先,系统维护了一个全局的处理器数组KiProcessorBlock,其中每个元素对应于一个处理器的KPRCB对象。其次,全局变量KiIdleSummary 记录了哪些处理器当前是空闲的。

线程调度的数据结构和全局信息:

一个线程主动放弃执行权,会在内核函数中调用KiSwapThread 函数。

KiSwapThread --> KiSwapContext --> SwapContext 来完成线程切换。

KiSwapThread 进行线程调度的可能性:

  1. 线程自愿进入等待状态,即KeDelayExcutionThread、KeWaitForSingleObject、KeWaitForMultipleObjects
  2. 线程进入门等待状态,即KeWaitForGate
  3. 一个线程终止了,即KiSwapThread
  4. KeRemoveQueue
  5. 一个线程附载到一个进程的地址空间时,即KiAttachProcess

一个线程被迫放弃执行权:时限用完或者线程被抢占。线程被抢占发生在KiDeferredReadyThread 函数中。时限用完之间发生在时钟中断中,即KiDispatchInterrupt 。

线程抢占和时限用完都发生在KiDispatchInterrupt 函数中。线程抢占情形,KiDispatchInterrupt 调用SwapContext 执行线程切换;时限用完情形;调用

KindQuantumEnd。

线程切换的各种情况,(a)代表了一个线程主动放弃执行权得情形,(b)表示一个线程被抢占或时限用完的情形。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值