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)
c
typedef struct _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_DATA DpcData[2]; // DPC 队列([0]=普通, [1]=高优)
PVOID DpcStack; // DPC 专用的内核栈
ULONG DpcCount; // DPC 计数
volatile BOOLEAN QuantumEnd; // 时间片结束标志
/* 定时器 */
volatile ULONG 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]; // 锁队列
volatile UCHAR IdleSchedule; // 空闲调度标志
struct _ETHREAD *ParentThread; // 父线程
... // 更多处理器特定字段
} KPRCB, *PKPRCB;
1.2 调度相关字段速查
| 字段 | 类型 | 线程状态 | 说明 |
|---|---|---|---|
IdleThread |
PKTHREAD |
Running | 空闲线程------CPU 无事可做时运行它 |
CurrentThread |
PKTHREAD |
Running | 当前线程------现在正在执行的线程 |
NextThread |
PKTHREAD |
Standby | 下一个线程------已被选出,等待切换 |
DispatcherReadyListHead[0..31] |
LIST_ENTRY[32] |
Ready | 就绪队列------每个优先级一个双向链表 |
DeferredReadyListHead |
SINGLE_LIST_ENTRY |
DeferredReady | 延迟就绪队列------SMP 暂存 |
ReadySummary |
ULONG |
--- | 就绪位图------哪几个优先级有就绪线程 |
DpcData[2] |
KDPC_DATA |
--- | DPC 队列------待处理的延迟过程调用 |
TimerRequest |
ULONG |
--- | 定时器标志------有定时器已到期 |
QuantumEnd |
BOOLEAN |
--- | 时间片耗尽------当前线程时间片用完 |
PowerState.IdleFunction |
函数指针 | --- | 空闲节能函数------无工作时调用 |
2. KPRCB 与 PCR 的关系
KPRCB 不独立存在,它内嵌在 KIPCR 结构中,通过 FS 段寄存器直接访问。
2.1 KIPCR 结构
c
typedef struct _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; // 当前 IRQL
ULONG 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 访问宏
c
// 直接从 FS 段读取 Prcb 指针(i386)
FORCEINLINE PKPRCB KeGetCurrentPrcb(VOID)
{
return (PKPRCB)(ULONG_PTR)__readfsdword(FIELD_OFFSET(KPCR, Prcb));
}
// 获取 PCR 自身
FORCEINLINE PKPCR KeGetPcr(VOID)
{
return (PKPCR)(ULONG_PTR)__readfsdword(FIELD_OFFSET(KPCR, SelfPcr));
}
在汇编层面,KeGetCurrentPrcb() 等于 mov eax, fs:[0x30]。
2.4 初始化
c
// kiinit.c:298-303
Pcr->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:
c
// kiinit.c:851
KiProcessorBlock[ProcessorNumber] = Pcr->Prcb;
3. 线程状态迁移
KPRCB 字段与线程状态机一一对应:
KeStartThread
│
▼
┌──────────┐ KiDeferredReadyThread ┌──────────┐
│ Init │ ──────────────────────────▶ │ Standby │
└──────────┘ │ NextThread│
└─────┬────┘
│ 上下文切换
▼
┌──────────┐ ┌──────────┐
│ Ready │ ◀───────────────────── │ Running │
│ 就绪队列 │ 时间片到/被抢占 │ CurrentThr│
└──────────┘ └─────┬────┘
▲ │ 等待(I/O, 锁等)
│ ▼
│ ┌──────────┐
└─────────────────────────────────│ Waiting │
等待完成 → KiUnwaitThread └──────────┘
| PRCB 字段 | 对应状态 | 含义 |
|---|---|---|
IdleThread |
Running |
特殊线程,CPU 空闲时运行 |
CurrentThread |
Running |
正在执行的线程 |
NextThread |
Standby |
已选定、等待切换的线程(无竞争状态) |
DispatcherReadyListHead[] |
Ready |
就绪可调度,等待被选出 |
DeferredReadyListHead |
DeferredReady |
暂存待处理的就绪线程(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
c
VOID FASTCALL KiDeferredReadyThread(PKTHREAD Thread)
{
// 1. 选目标 CPU
Processor = KiSelectNextProcessor(Thread);
Prcb = KiProcessorBlock[Processor];
// 2. 能否直接设为 NextThread(空闲 CPU)?
if (KiIdleSummary) {
KiIdleSummary = 0;
Thread->State = Standby;
Prcb->NextThread = Thread; ← ★ 设为 NextThread
return; → 空闲循环会捡起它
}
// 3. 能否抢占现有 NextThread?
if (Prcb->NextThread &&
OldPriority > Prcb->NextThread->Priority) {
// 新线程抢占 NextThread
Prcb->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; ← ★ 设为 NextThread
return;
}
// 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 已经被消耗(切换到新线程)且当前线程也需切换时:
c
// 从就绪队列选一个线程
FORCEINLINE PKTHREAD KiSelectReadyThread(KPRIORITY Priority, PKPRCB Prcb)
{
// 找出比 Priority 更高的最高优先级位
PrioritySet = Prcb->ReadySummary >> Priority;
if (!PrioritySet) return NULL; ← 没有可运行的线程
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);
return Thread;
}
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 可抢占 CurrentThread 或 NextThread |
| IRQL 保护 | 所有调度状态修改在 >= DISPATCH_LEVEL IRQL 下完成 |