KPRCB 结构分析与调度流程

KPRCB 结构分析与调度流程

概述

KPRCB(Kernel 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_DATADPC 队列——待处理的延迟过程调用
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 指针(i386)FORCEINLINE 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.CurrentThread=IdleThread;// 设为空闲线程Pcr->PrcbData.NextThread=NULL;// 无下一个线程Pcr->PrcbData.IdleThread=IdleThread;// 空闲线程Pcr->SelfPcr=(PKPCR)Pcr;// 自引用Pcr->Prcb=&Pcr->PrcbData;// Prcb 快捷指针Pcr->Irql=HIGH_LEVEL;// 初始 IRQL

KiProcessorBlock[]是全局数组,将 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暂存待处理的就绪线程(SMP)

4. 调度数据流

4.1 线程变为就绪:KiReadyThread

KiUnwaitThread(Thread) ← 等待完成(I/O、锁、超时) └─ KiReadyThread(Thread) └─ KiInsertDeferredReadyList(Thread) ┌─── UP 系统 ───→ KiDeferredReadyThread(Thread) 直接处理 │ KiInsertDeferred--- ┤ │ └─── SMP 系统 ──→ PushEntryList(DeferredReadyListHead) Thread->State = DeferredReady ★ 等调度点再批量处理

4.2 选出 NextThread:KiDeferredReadyThread

VOID FASTCALLKiDeferredReadyThread(PKTHREAD Thread){// 1. 选目标 CPUProcessor=KiSelectNextProcessor(Thread);Prcb=KiProcessorBlock[Processor];// 2. 能否直接设为 NextThread(空闲 CPU)?if(KiIdleSummary){KiIdleSummary=0;Thread->State=Standby;Prcb->NextThread=Thread;← ★ 设为 NextThreadreturn;→ 空闲循环会捡起它}// 3. 能否抢占现有 NextThread?if(Prcb->NextThread&&OldPriority>Prcb->NextThread->Priority){// 新线程抢占 NextThreadPrcb->NextThread->State=DeferredReady;Prcb->NextThread=Thread;← ★ 替换 NextThread// 被踢出的旧线程重新处理KiDeferredReadyThread(Prcb->NextThread);return;}// 4. 能否抢占 CurrentThread?if(OldPriority>Prcb->CurrentThread->Priority){if(Prcb->CurrentThread->State==Running)Prcb->CurrentThread->Preempted=TRUE;Prcb->NextThread=Thread;← ★ 设为 NextThreadreturn;}// 5. 不能抢占 → 入就绪队列Thread->State=Ready;InsertTailList(&Prcb->DispatcherReadyListHead[Priority],&Thread->WaitListEntry);Prcb->ReadySummary|=(1<<Priority);← ★ 更新位图}

三个不同的"入队列"路径:

KiDeferredReadyThread ├─ (CPU 空闲) → NextThread, State=Standby ├─ (可抢占) → NextThread (替换), State=Standby └─ (不可抢占) → DispatcherReadyList State=Ready

4.3 消耗 NextThread:KiSelectReadyThread

NextThread已经被消耗(切换到新线程)且当前线程也需切换时:

// 从就绪队列选一个线程FORCEINLINE PKTHREADKiSelectReadyThread(KPRIORITY Priority,PKPRCB Prcb){// 找出比 Priority 更高的最高优先级位PrioritySet=Prcb->ReadySummary>>Priority;if(!PrioritySet)returnNULL;← 没有可运行的线程BitScanReverse(&HighPriority,PrioritySet);← 找最高位 HighPriority+=Priority;// 从该优先级链表头部取出一个线程ListEntry=Prcb->DispatcherReadyListHead[HighPriority].Flink;Thread=CONTAINING_RECORD(ListEntry,KTHREAD,WaitListEntry);RemoveEntryList(&Thread->WaitListEntry);// 如果链表空了,清除 Summary 中对应位if(IsListEmpty(&Prcb->DispatcherReadyListHead[HighPriority]))Prcb->ReadySummary^=(1<<HighPriority);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 ← ★ 暂停 CPU

5.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 │ (若选 IdleThread,Prcb->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 NextThread=NULL KiDispatchInterrupt(时钟IRQ) KiRetireDpcList() KiQuantumEnd() KiSelectReadyThread() NextThread=选出的线程 KiSwapContext(APC, Old) CurrentThread=NextThread NextThread=NULL → 正常调度循环

8. 设计要点总结

概念说明
每 CPU 一个 PRCB通过 FS 段(i386)或 GS 段(amd64)0 开销访问,无需加锁
NextThread 是唯一的"无锁窗口"在关中断 + 持有 PRCB 锁下赋值,减少调度延迟
三个级别的就绪DeferredReadyList(暂存)→ Ready(就绪队列)→ Standby(待切换)
位图加速ReadySummary使调度器可在 O(1) 内找到最高优先级线程
空闲线程特殊IdleThread无就绪队列,唯一路径是通过IdleSummary → NextThread
抢占式调度KiDeferredReadyThread可抢占CurrentThreadNextThread
IRQL 保护所有调度状态修改在>= DISPATCH_LEVELIRQL 下完成