KiIdleLoop 函数分析

📅 2026/7/19 6:48:49
KiIdleLoop 函数分析
KiIdleLoop 函数分析概述KiIdleLoop是 ReactOS 内核空闲线程的主循环。当 CPU 上没有任何可运行的线程时调度器选择空闲线程运行进入此循环。它永不返回是引导 CPU 在完成KiInitializeKernel后进入的最终状态。文件[ke/i386/thrdini.c#L260](file:///d:/reactos/ntoskrnl/ke/i386/thrdini.c#L260)KiSystemStartupBootStack() [kiinit.c:691] └─ KiInitializeKernel() [kiinit.c:433] └─ ExpInitializeExecutive() └─ PsCreateSystemThread(Phase1Initialization) └─ Thread-Priority 0 └─ KiIdleLoop() [thrdini.c:260] └─ 永不返回完整源码DECLSPEC_NORETURN VOIDKiIdleLoop(VOID){PKPRCB PrcbKeGetCurrentPrcb();PKTHREAD OldThread,NewThread;/* Now loop forever */while(TRUE){/* Start of the idle loop: enable interrupts briefly */_enable();YieldProcessor();// PAUSE/HALT 提示YieldProcessor();_disable();/* Check for pending work */if((Prcb-DpcData[0].DpcQueueDepth)||(Prcb-TimerRequest)||(Prcb-DeferredReadyListHead.Next)){/* Clear DPC software interrupt */HalClearSoftwareInterrupt(DISPATCH_LEVEL);/* Process all pending DPCs */KiRetireDpcList(Prcb);}/* Check if a thread is ready to run */if(Prcb-NextThread){_enable();OldThreadPrcb-CurrentThread;// 空闲线程自己NewThreadPrcb-NextThread;// 要切换到的线程Prcb-NextThreadNULL;Prcb-CurrentThreadNewThread;NewThread-StateRunning;KiSwapContext(APC_LEVEL,OldThread);// ← 上下文切换}else{/* No work — let HAL put CPU to sleep */Prcb-PowerState.IdleFunction(Prcb-PowerState);}}}执行流程分析循环前 — 中断短暂使能并暂停_enable(); // sti — 开中断让挂起的中断有机会被处理 YieldProcessor(); // pause 指令 — 提示 CPU 处于自旋等待 YieldProcessor(); _disable(); // cli — 关中断检查共享状态前关中断YieldProcessor()在 x86 上映射为_mm_pausePAUSE 指令作用是提示 CPU 当前处于自旋等待循环优化流水线避免内存顺序违规的惩罚在 Hyper-Threading 场景尤其重要等同于__asm__ __volatile__(pause);短暂的sti使中断在空闲循环顶部能被响应之后cli关中断再检查共享数据结构DPC 队列、定时器请求、就绪线程链表符合关中断访问内核调度数据结构的规范。分支 A有挂起工作 — 处理 DPCif((Prcb-DpcData[0].DpcQueueDepth)||(Prcb-TimerRequest)||(Prcb-DeferredReadyListHead.Next)){HalClearSoftwareInterrupt(DISPATCH_LEVEL);KiRetireDpcList(Prcb);}检查三个条件条件含义DpcQueueDepth当前 CPU 的 DPC 队列中有待处理的延迟过程调用TimerRequest有定时器过期需要更新重计算就绪线程优先级等DeferredReadyListHead.Next有延迟就绪的线程等待被插入调度数据库如果任一条件为真HalClearSoftwareInterrupt(DISPATCH_LEVEL)— 清除 DPC 软件中断请求避免重复触发KiRetireDpcList(Prcb)— 执行所有挂起的 DPC同时处理定时器请求和延迟就绪线程KiRetireDpcList完成后可能已经有线程被加入Prcb-NextThread因此不会直接 fallthrough 到检查 NextThread 的分支而是从头开始下一轮循环。分支 B有待切换线程 — 上下文切换if(Prcb-NextThread){_enable();// 切换前开中断OldThreadPrcb-CurrentThread;// 空闲线程NewThreadPrcb-NextThread;// 要切换到的线程Prcb-NextThreadNULL;// 清空Prcb-CurrentThreadNewThread;NewThread-StateRunning;// 标记为运行中KiSwapContext(APC_LEVEL,OldThread);// ← ★ 上下文切换 ★}关键变量Prcb-CurrentThread— 当前 CPU 正在执行的线程空闲线程Prcb-NextThread— 调度器KiQuantumEnd/KiDispatchInterrupt/KiRetireDpcList等选择的下一个要运行的线程KiSwapContext 调用链KiSwapContext(APC_LEVEL, OldThread) [ctxswitch.S:79] └─ KiSwapContextInternal └─ KiSwapContextEntry(KSWITCHFRAME, OldThread~) ├─ 保存 OldThread-KernelStack ESP └─ KiSwitchThreads(OldThread, NewThread-KernelStack) mov esp, NewThread-KernelStack ← ★ 切换栈 └─ KiSwapContextExit(OldThread) ├─ 检查进程切换 → 切换 CR3 ├─ TSS.Esp0 NewThread-InitialStack ├─ TEB 设置 └─ 异常链 恢复非易失寄存器 ret → KiThreadStartup (新线程的 CtxSwitchFrame.RetAddr)KiSwapContext 返回后KiSwapContext返回时CPU 已经在新线程的栈上。OldThread参数就是当前的空闲线程这意味着当空闲线程下次被调度时KiSwapContext会返回到KiIdleLoop中KiSwapContext的下一行空闲循环继续执行while (TRUE)的下一轮迭代分支 C无工作 — 空闲节能else{Prcb-PowerState.IdleFunction(Prcb-PowerState);}当既无 DPC/定时器待处理也无就绪线程时调用空闲节能函数。PopIdle0[power.c#L501](file:///d:/reactos/ntoskrnl/po/power.c#L501)VOID FASTCALLPopIdle0(IN PPROCESSOR_POWER_STATE PowerState){HalProcessorIdle();}HalProcessorIdle[halx86/pic/processor.c#L46](file:///d:/reactos/hal/halx86/pic/processor.c#L46)VOID NTAPIHalProcessorIdle(VOID){_enable();// sti — 开中断__halt();// hlt — 暂停 CPU 直到下一个外部中断}HLT 指令的作用CPU 暂停执行进入低功耗模式等待下一个外部中断硬件中断或 IPI唤醒中断触发后CPU 继续执行HalProcessorIdle的下一行即返回为什么 PopIdle0 不带中断使能/关闭— 根据注释 “Note the HAL returns with interrupts on”IdleFunction返回时中断已经开启。HalProcessorIdle在执行hlt前通过_enable()开中断hlt后的隐式中断处理完成后控制流返回 IdleLoop 的while (TRUE)顶部重新走_enable()→YieldProcessor()→_disable()流程。与调度器的协作关系中断/异常 │ ▼ KiDispatchInterrupt ← 时钟中断、IPI 等 │ ├─ 选择新线程 → Prcb-NextThread NewThread ├─ 请求 DPC 软件中断 └─ 返回到被中断的线程也可能是空闲线程 │ ▼ (如果是空闲线程且 IRQL 降至 PASSIVE_LEVEL) KiIdleLoop 的下一条指令执行 → 发现 NextThread → 上下文切换调度器不会直接调用KiIdleLoop而是通过设置Prcb-NextThread来间接通知空闲线程进行切换。典型的唤醒路径设备中断 → ISR → 请求 DPC → 软件中断 → KiDispatchInterrupt → 线程被唤醒 → 插入就绪队列 → Prcb-NextThread NewThread → 返回到空闲线程 → KiIdleLoop 发现 NextThread → KiSwapContext第一次上下文切换从 KiIdleLoop 到 Phase1Initialization引导 CPU 的第一次KiSwapContext发生在KiIdleLoop() → Prcb-NextThread Phase1Initialization 线程 (由 KiInitializeKernel → ExpInitializeExecutive 创建) → KiSwapContext(APC_LEVEL, IdleThread) → 切换到 Phase1Initialization 内核栈 → ret → KiThreadStartup → PspSystemThreadStartup → Phase1Initialization()之后 Phase1 初始化线程完成所有初始化工作最终创建 SMSS.EXE而空闲线程继续在KiIdleLoop中循环随时准备切换到新的就绪线程。设计要点总结特性说明永不返回DECLSPEC_NORETURNwhile(TRUE)是 CPU 的最终归宿节能设计无工作时执行HLT指令暂停 CPU等待中断唤醒DPC 处理在空闲上下文中执行 DPC避免占用工作线程时间片中断窗口循环顶部短暂sti让挂起中断有机会执行竞态保护检查共享调度状态时cli关中断微架构优化PAUSE指令提示 Hyper-Threading / 流水线优化SMP 安全KiSwapContext在SYNCH_LEVEL执行#ifdef CONFIG_SMP