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 下完成
相关推荐
caimouse14 天前
TCP/IP 协议驱动 (tcpip.sys) 分析
网络协议·tcp/ip·reactos
caimouse16 天前
MM 学习笔记 09:进程会话与工作集
笔记·学习·reactos
caimouse16 天前
MM 学习笔记 10:页面文件与系统加载器
笔记·学习·reactos
caimouse17 天前
WINE IE 与 wine_gecko 架构分析
架构·reactos
caimouse21 天前
Chrome V49 (49.0.2623.112) 在 ReactOS 上运行所需的系统依赖
chrome·reactos
caimouse21 天前
Windows 内存管理概念详解
reactos
caimouse21 天前
MM 学习笔记 01:miarm.h 核心头文件分析
笔记·学习·reactos
caimouse21 天前
Chrome V49 SxS 加载问题分析与修复
chrome·reactos
caimouse21 天前
mm学习笔记_04:VAD树算法与地址空间分配
笔记·学习·算法·reactos
caimouse22 天前
WINLOGON 初始化与作用分析
reactos