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)

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 可抢占 CurrentThreadNextThread
IRQL 保护 所有调度状态修改在 >= DISPATCH_LEVEL IRQL 下完成
相关推荐
caimouse43 分钟前
mm学习笔记_04:VAD树算法与地址空间分配
笔记·学习·算法·reactos
caimouse14 小时前
WINLOGON 初始化与作用分析
reactos
caimouse1 天前
ReactOS.exe 安装程序分析
reactos
caimouse2 天前
Phase1Initialization 函数分析
单片机·嵌入式硬件·reactos
caimouse4 天前
ReactOS bootcd.iso 引导到桌面显示过程分析
reactos
caimouse4 天前
ReactOS BootCD 光盘布局完整深度分析
单片机·reactos
caimouse4 天前
KiSwitchToBootStack 函数分析
reactos
caimouse4 天前
KiSystemStartupBootStack 函数分析
开发语言·reactos
caimouse7 天前
ReactOS Development Notes
reactos