
KPRCB 结构分析与调度流程概述KPRCBKernel Processor Control Block处理器控制块是 ReactOS 内核中每个 CPU 核心一个的核心数据结构。它保存了处理器相关的所有调度状态当前运行的线程、下一个待运行的线程、空闲线程、就绪队列、DPC 队列、定时器状态、电源状态等。调度器的所有操作都围绕 KPRCB 展开。1. KPRCB 结构体定义1.1 完整结构i386文件[sdk/include/ndk/i386/ketypes.h](file:///d:/reactos/sdk/include/ndk/i386/ketypes.h)typedefstruct_KPRCB{/* 版本信息 */USHORT MinorVersion;USHORT MajorVersion;/* ★ 调度核心字段 */struct_KTHREAD*CurrentThread;// 当前正在此 CPU 上执行的线程struct_KTHREAD*NextThread;// 下一个待运行的线程Standby 状态struct_KTHREAD*IdleThread;// 空闲线程无工作时的默认线程/* 处理器标识 */UCHAR Number;// 处理器编号 (0, 1, 2, ...)UCHAR Reserved;USHORT BuildType;KAFFINITY SetMember;// 本处理器的 Affinity 位/* CPU 特性 */CCHAR CpuType;CCHAR CpuID;union{...}CpuStep;// CPU 步进/特性标志struct_KTHREAD*NpxThread;// 当前拥有 FPU 上下文的线程/* DPC 调度 */struct_KDPC_DATADpcData[2];// DPC 队列[0]普通, [1]高优PVOID DpcStack;// DPC 专用的内核栈ULONG DpcCount;// DPC 计数volatileBOOLEAN QuantumEnd;// 时间片结束标志/* 定时器 */volatileULONG TimerRequest;// 定时器请求标志非零表示有定时器到期ULONG TimerHand;// 定时器处理句柄/* 调度数据库 */LIST_ENTRY WaitListHead[5];// 等待线程链表头ULONG ReadySummary;// 就绪位图摘要32 位, 每位对应一个优先级ULONG QueueIndex;LIST_ENTRY DispatcherReadyListHead[32];// ★ 每优先级一个就绪链表SINGLE_LIST_ENTRY DeferredReadyListHead;// SMP 延迟就绪链表/* 电源管理 */PROCESSOR_POWER_STATE PowerState;// 处理器电源状态含 IdleFunction/* 其他 */PVOID DmaAdapter;// DMA 适配器ULONG NumberPadPagesForCheck;// 安全检查页面数KSPIN_LOCK_QUEUE LockQueue[LockQueueMaximumLock];// 锁队列volatileUCHAR IdleSchedule;// 空闲调度标志struct_ETHREAD*ParentThread;// 父线程...// 更多处理器特定字段}KPRCB,*PKPRCB;1.2 调度相关字段速查字段类型线程状态说明IdleThreadPKTHREADRunning空闲线程——CPU 无事可做时运行它CurrentThreadPKTHREADRunning当前线程——现在正在执行的线程NextThreadPKTHREADStandby下一个线程——已被选出等待切换DispatcherReadyListHead[0..31]LIST_ENTRY[32]Ready就绪队列——每个优先级一个双向链表DeferredReadyListHeadSINGLE_LIST_ENTRYDeferredReady延迟就绪队列——SMP 暂存ReadySummaryULONG—就绪位图——哪几个优先级有就绪线程DpcData[2]KDPC_DATA—DPC 队列——待处理的延迟过程调用TimerRequestULONG—定时器标志——有定时器已到期QuantumEndBOOLEAN—时间片耗尽——当前线程时间片用完PowerState.IdleFunction函数指针—空闲节能函数——无工作时调用2. KPRCB 与 PCR 的关系KPRCB 不独立存在它内嵌在 KIPCR 结构中通过 FS 段寄存器直接访问。2.1 KIPCR 结构typedefstruct_KIPCR{union{NT_TIB NtTib;// 线程信息块struct{struct_EXCEPTION_REGISTRATION_RECORD*Used_ExceptionList;PVOID Used_StackBase;PVOID PerfGlobalGroupMask;PVOID TssCopy;ULONG ContextSwitches;KAFFINITY SetMemberCopy;PVOID Used_Self;};};struct_KPCR*SelfPcr;// 自引用指针 → 指向 PCR 自身struct_KPRCB*Prcb;// 指向 PrcbData 的快捷指针KIRQL Irql;// 当前 IRQLULONG IRR;// 中断请求寄存器ULONG IrrActive;ULONG IDR;...KPRCB PrcbData;// ★ 内嵌的完整 KPRCB}KIPCR,*PKIPCR;2.2 地址映射0xFFDFF000 ┌──────────────────┐ ← KIP0PCRADDRESS │ NT_TIB │ FS:0x00 ├──────────────────┤ │ SelfPcr │ FS:0x1C ├──────────────────┤ │ Prcb → 指向 ...│ FS:0x30 ← KeGetPcr()-Prcb ├──────────────────┤ │ Irql │ FS:0x34 ├──────────────────┤ │ ... │ ├──────────────────┤ │ PrcbData │ ← KPRCB 从这里开始 │ ├─ CurrentThread│ FS:0x48 │ ├─ NextThread │ FS:0x4C │ ├─ IdleThread │ FS:0x50 │ ├─ ... │ └──────────────────┘2.3 访问宏// 直接从 FS 段读取 Prcb 指针i386FORCEINLINE PKPRCBKeGetCurrentPrcb(VOID){return(PKPRCB)(ULONG_PTR)__readfsdword(FIELD_OFFSET(KPCR,Prcb));}// 获取 PCR 自身FORCEINLINE PKPCRKeGetPcr(VOID){return(PKPCR)(ULONG_PTR)__readfsdword(FIELD_OFFSET(KPCR,SelfPcr));}在汇编层面KeGetCurrentPrcb()等于mov eax, fs:[0x30]。2.4 初始化// kiinit.c:298-303Pcr-PrcbData.CurrentThreadIdleThread;// 设为空闲线程Pcr-PrcbData.NextThreadNULL;// 无下一个线程Pcr-PrcbData.IdleThreadIdleThread;// 空闲线程Pcr-SelfPcr(PKPCR)Pcr;// 自引用Pcr-PrcbPcr-PrcbData;// Prcb 快捷指针Pcr-IrqlHIGH_LEVEL;// 初始 IRQLKiProcessorBlock[]是全局数组将 CPU 编号映射到其 PRCB// kiinit.c:851KiProcessorBlock[ProcessorNumber]Pcr-Prcb;3. 线程状态迁移KPRCB 字段与线程状态机一一对应KeStartThread │ ▼ ┌──────────┐ KiDeferredReadyThread ┌──────────┐ │ Init │ ──────────────────────────▶ │ Standby │ └──────────┘ │ NextThread│ └─────┬────┘ │ 上下文切换 ▼ ┌──────────┐ ┌──────────┐ │ Ready │ ◀───────────────────── │ Running │ │ 就绪队列 │ 时间片到/被抢占 │ CurrentThr│ └──────────┘ └─────┬────┘ ▲ │ 等待I/O, 锁等 │ ▼ │ ┌──────────┐ └─────────────────────────────────│ Waiting │ 等待完成 → KiUnwaitThread └──────────┘PRCB 字段对应状态含义IdleThreadRunning特殊线程CPU 空闲时运行CurrentThreadRunning正在执行的线程NextThreadStandby已选定、等待切换的线程无竞争状态DispatcherReadyListHead[]Ready就绪可调度等待被选出DeferredReadyListHeadDeferredReady暂存待处理的就绪线程SMP4. 调度数据流4.1 线程变为就绪KiReadyThreadKiUnwaitThread(Thread) ← 等待完成I/O、锁、超时 └─ KiReadyThread(Thread) └─ KiInsertDeferredReadyList(Thread) ┌─── UP 系统 ───→ KiDeferredReadyThread(Thread) 直接处理 │ KiInsertDeferred--- ┤ │ └─── SMP 系统 ──→ PushEntryList(DeferredReadyListHead) Thread-State DeferredReady ★ 等调度点再批量处理4.2 选出 NextThreadKiDeferredReadyThreadVOID FASTCALLKiDeferredReadyThread(PKTHREAD Thread){// 1. 选目标 CPUProcessorKiSelectNextProcessor(Thread);PrcbKiProcessorBlock[Processor];// 2. 能否直接设为 NextThread空闲 CPUif(KiIdleSummary){KiIdleSummary0;Thread-StateStandby;Prcb-NextThreadThread;← ★ 设为 NextThreadreturn;→ 空闲循环会捡起它}// 3. 能否抢占现有 NextThreadif(Prcb-NextThreadOldPriorityPrcb-NextThread-Priority){// 新线程抢占 NextThreadPrcb-NextThread-StateDeferredReady;Prcb-NextThreadThread;← ★ 替换 NextThread// 被踢出的旧线程重新处理KiDeferredReadyThread(Prcb-NextThread);return;}// 4. 能否抢占 CurrentThreadif(OldPriorityPrcb-CurrentThread-Priority){if(Prcb-CurrentThread-StateRunning)Prcb-CurrentThread-PreemptedTRUE;Prcb-NextThreadThread;← ★ 设为 NextThreadreturn;}// 5. 不能抢占 → 入就绪队列Thread-StateReady;InsertTailList(Prcb-DispatcherReadyListHead[Priority],Thread-WaitListEntry);Prcb-ReadySummary|(1Priority);← ★ 更新位图}三个不同的入队列路径KiDeferredReadyThread ├─ (CPU 空闲) → NextThread, StateStandby ├─ (可抢占) → NextThread (替换), StateStandby └─ (不可抢占) → DispatcherReadyList StateReady4.3 消耗 NextThreadKiSelectReadyThread当NextThread已经被消耗切换到新线程且当前线程也需切换时// 从就绪队列选一个线程FORCEINLINE PKTHREADKiSelectReadyThread(KPRIORITY Priority,PKPRCB Prcb){// 找出比 Priority 更高的最高优先级位PrioritySetPrcb-ReadySummaryPriority;if(!PrioritySet)returnNULL;← 没有可运行的线程BitScanReverse(HighPriority,PrioritySet);← 找最高位 HighPriorityPriority;// 从该优先级链表头部取出一个线程ListEntryPrcb-DispatcherReadyListHead[HighPriority].Flink;ThreadCONTAINING_RECORD(ListEntry,KTHREAD,WaitListEntry);RemoveEntryList(Thread-WaitListEntry);// 如果链表空了清除 Summary 中对应位if(IsListEmpty(Prcb-DispatcherReadyListHead[HighPriority]))Prcb-ReadySummary^(1HighPriority);returnThread;}5. 调度切入点的完整流程5.1 KiIdleLoop空闲 → 工作线程KiIdleLoop() ↓ 检查 DPC/定时器/延迟就绪 KiRetireDpcList() ← 处理挂起的 DPC可能唤醒线程 ↓ if (Prcb-NextThread) ← 有线程被设为 NextThread │ Old IdleThread │ New NextThread │ Prcb-NextThread NULL │ Prcb-CurrentThread New │ New-State Running │ KiSwapContext(APC_LEVEL, IdleThread) ← ★ 上下文切换 │ 切到工作线程后IdleThread 被保存 │ (下次 IdleThread 被调度时从 KiSwapContext 返回继续循环) │ └─ else PopIdle0() → HalProcessorIdle() → hlt ← ★ 暂停 CPU5.2 KiDispatchInterrupt中断驱动的调度DPC 中断 / 调度软件中断的入口。可能发生在任何线程运行中硬件中断 → ISR → 请求 DPC → HalRequestSoftwareInterrupt(DISPATCH_LEVEL) ↓ KiDispatchInterrupt() (DPC 中断处理程序) │ ├─ KiRetireDpcListInDpcStack() ← 处理 DPC │ ├─ if (Prcb-QuantumEnd) │ KiQuantumEnd() ← 处理时间片耗尽 │ └─ else if (Prcb-NextThread) ← 有更高优先级线程就绪 ├─ NextThread-State Running ├─ OldThread-WaitReason WrDispatchInt ├─ KxQueueReadyThread(OldThread) ← 当前线程回就绪队列 └─ KiSwapContext(APC_LEVEL, OldThread) ← ★ 切换5.3 KiQuantumEnd时间片耗尽KiQuantumEnd() │ ├─ Thread-Quantum Thread-QuantumReset ← 重置时间片 ├─ Thread-Priority KiComputeNewPriority() ← 重新计算优先级 │ ├─ if (!Prcb-NextThread) │ Next KiSelectReadyThread(Thread-Priority, Prcb) │ if (Next) Prcb-NextThread Next │ └─ if (Prcb-NextThread) ├─ NextThread-State Running ├─ OldThread-WaitReason WrQuantumEnd ├─ KxQueueReadyThread(OldThread) ← 当前线程回就绪队列 └─ KiSwapContext(APC_LEVEL, OldThread) ← ★ 切换5.4 KiSwapThread线程主动等待KeWaitForSingleObject / KeWaitForMultipleObjects / KeDelayExecutionThread └─ KiSwapThread(WaitTime, WaitReason) │ ├─ Prcb-NextThread 非空 │ Yes: NextThread→Running, 当前线程→Waiting │ No: 选一个就绪的或 IdleThread │ (若选 IdleThreadPrcb-WaitListHead 记录等待者) │ └─ KiSwapContext(APC_LEVEL, OldThread) ← ★ 切换5.5 NtYieldExecution主动让出NtYieldExecution() | 使当前线程主动让出 CPU ├─ if (!Prcb-NextThread) ← 没有待切换线程 │ Next KiSelectReadyThread(1, Prcb) ← 找一个就绪线程 │ if (!Next) return STATUS_NO_YIELD_PERFORMED ← 无人可让 │ Next-State Standby │ Prcb-NextThread Next │ ├─ NextThread-State Running ├─ KeSetPriorityThread(OldThread, 1) ← 降低优先级 ├─ CurrentThread NextThread └─ KiSwapContext(APC_LEVEL, OldThread) ← ★ 切换6. 线程切换的核心KiSwapContext所有调度切入点最终都调用KiSwapContext完成实际的上下文切换KiSwapContext(APC_LEVEL, OldThread) │ ├─ KiSwapContextInternal │ └─ KiSwapContextEntry(SwitchFrame, OldThread) │ ├─ pushad ← 保存所有通用寄存器到旧线程栈 │ ├─ mov [OldThread-KernelStack], ESP ← 保存旧线程的栈指针 │ ├─ NPX/FPU 状态处理 │ └─ KiSwitchThreads(OldThread, NewThread-KernelStack) │ ├─ mov ESP, [NewThread-KernelStack] ← ★ ESP 切换 │ └─ KiSwapContextExit(OldThread) │ ├─ 进程切换 → mov CR3, [NewProcess-DirBase] │ ├─ TSS.Esp0 NewThread-InitialStack │ ├─ TEB 设置 │ └─ 异常链 │ 恢复非易失寄存器 (popad) │ ret → 回到新线程的调用者 │ └─ (KiSwapContext 在新线程的上下文中返回)SwitchFrame 在栈上的位置KiSwapContextEntry 的 KSWITCHFRAME 保存在旧线程栈上 ┌──────────────────────┐ OldThread-KernelStack → │ pushad 的寄存器 │ ├──────────────────────┤ │ KiSwapContextInternal │ │ 的局部变量 │ └──────────────────────┘ 以后 KiSwapContext 返回到 OldThread 时 mov ESP, [OldThread-KernelStack] popad → 恢复所有寄存器 ret → 返回到 KiIdleLoop / KiQuantumEnd / ...7. 初始化与各字段生命周期阶段 IdleThread CurrentThread NextThread DispatcherReadyList ────────────────────────── ──────────── ─────────────── ───────────── ────────────────────── KiSystemStartup (BSP) NULL NULL NULL Empty └─ PoInitializePrcb() └─ KeInitSystem() └─ KeInitializeThread(Idle) └─ Pcr-PrcbData ... IdleThread IdleThread CurrentThread IdleThread NextThread NULL KeReadyThread(Phase1) → Phase1Thread Phase1→Ready KiSystemStartupBootStack └─ KiIdleLoop() KiSwapContext() IdleThread→Idle Phase1→Running NextThreadNULL KiDispatchInterrupt(时钟IRQ) KiRetireDpcList() KiQuantumEnd() KiSelectReadyThread() NextThread选出的线程 KiSwapContext(APC, Old) CurrentThreadNextThread NextThreadNULL → 正常调度循环8. 设计要点总结概念说明每 CPU 一个 PRCB通过 FS 段i386或 GS 段amd640 开销访问无需加锁NextThread 是唯一的无锁窗口在关中断 持有 PRCB 锁下赋值减少调度延迟三个级别的就绪DeferredReadyList暂存→ Ready就绪队列→ Standby待切换位图加速ReadySummary使调度器可在 O(1) 内找到最高优先级线程空闲线程特殊IdleThread无就绪队列唯一路径是通过IdleSummary → NextThread抢占式调度KiDeferredReadyThread可抢占CurrentThread或NextThreadIRQL 保护所有调度状态修改在 DISPATCH_LEVELIRQL 下完成