从 Phase 1 初始化到 SMSS 进程启动分析
从 Phase 1 初始化到 SMSS 进程启动分析
概述
本文档描述 ReactOS 内核在 Phase 1 初始化完成后,如何创建并切换到 SMSS.EXE(会话管理器子系统)进程运行。核心机制是利用线程调度器的上下文切换,通过iret指令从 Ring 0 切换到 Ring 3,将控制权交给用户态进程。
1. Phase 1 初始化入口
Phase1Initialization是一个系统线程,由PspInitPhase0在 Phase 0 末尾创建:
KiSystemStartup() └─ KiInitializeKernel() └─ ExpInitializeExecutive() └─ PspInitPhase0() └─ PsCreateSystemThread(Phase1Initialization)当KiIdleLoop通过调度器选中该线程时,执行进入Phase1Initialization([ex/init.c#L2058](file:///d:/reactos/ntoskrnl/ex/init.c#L2058)):
Phase1Initialization(IN PVOID Context){Phase1InitializationDiscard(Context);// 执行初始化核心逻辑MmZeroPageThread();// 完成后进入零页线程}Phase1InitializationDiscard在第 2017 行调用最关键的函数:
ExpLoadInitialProcess(InitBuffer,&ProcessParameters,&Environment);2. ExpLoadInitialProcess — 创建 SMSS 进程
文件:[ex/init.c#L389](file:///d:/reactos/ntoskrnl/ex/init.c#L389)
2.1 准备 ProcessParameters(第 408-527 行)
- 分配
RTL_USER_PROCESS_PARAMETERS结构 - 从
NtInitialUserProcessBuffer读取 SMSS 的映像路径 - 构造命令行、环境变量(Path=, SystemDrive=, SystemRoot=)
2.2 调用 RtlCreateUserProcess(第 557-580 行)
SmssName=ProcessParams->ImagePathName;Status=RtlCreateUserProcess(&SmssName,OBJ_CASE_INSENSITIVE,RtlDeNormalizeProcessParams(ProcessParams),NULL,NULL,NULL,FALSE,NULL,NULL,ProcessInformation);RtlCreateUserProcess内部依次调用:
RtlCreateUserProcess() ├─ NtCreateProcessEx() → PspCreateProcess() 创建 EPROCESS └─ NtCreateThread() → PspCreateThread() 创建主线程(CREATE_SUSPENDED)2.3 恢复线程运行(第 582-596 行)
Status=ZwResumeThread(ProcessInformation->ThreadHandle,NULL);使 SMSS 主线程变为可调度状态。
2.4 等待 SMSS 初始化(第 2019-2033 行)
Timeout.QuadPart=Int32x32To64(5,-10000000);// 5 秒Status=ZwWaitForSingleObject(ProcessInfo->ProcessHandle,FALSE,&Timeout);if(Status==STATUS_SUCCESS)KeBugCheck(SESSION5_INITIALIZATION_FAILED);5 秒超时等待。如果 SMSS 在 5 秒内退出(Status == SUCCESS),触发 BugCheck。
3. PspCreateThread — 线程创建核心
文件:[ps/thread.c#L172](file:///d:/reactos/ntoskrnl/ps/thread.c#L172)
3.1 创建 ETHREAD 对象
Status=ObCreateObject(...,sizeof(ETHREAD),...,(PVOID*)&Thread);3.2 用户线程 vs 系统线程分支
| 用户线程(SMSS) | 系统线程(Phase1) | |
|---|---|---|
SystemRoutine | PspUserThreadStartup | PspSystemThreadStartup |
StartRoutine | NULL | 实际 C 函数 |
StartContext | ThreadContext.Eip | 传入参数 |
| TEB | 有 | 无 |
// 用户线程(第 346-360 行)Thread->StartAddress=KeGetContextPc(ThreadContext);Status=KeInitThread(&Thread->Tcb,NULL,PspUserThreadStartup,// SystemRoutineNULL,// StartRoutine = NULLThread->StartAddress,// StartContext = EIPThreadContext,TebBase,&Process->Pcb);3.3 启动线程
KeStartThread(&Thread->Tcb);// 设置优先级、亲和性KeReadyThread(&Thread->Tcb);// 插入就绪队列 → 可调度4. KiInitializeContextThread — 内核栈上的初始化帧布局
文件:[ke/i386/thrdini.c#L92](file:///d:/reactos/ntoskrnl/ke/i386/thrdini.c#L92)
这是最关键的一步。它在线程的内核栈上布置特殊的初始化帧,使得调度器在第一次上下文切换到该线程时,能够自动进入用户态。
4.1 KUINIT_FRAME 结构(用户线程)
InitialStack (栈顶/最高地址) ↓ ┌─────────────────────────┐ ← 初始栈指针 (最高地址) │ (栈空间) │ ├─────────────────────────┤ │ FX_SAVE_AREA │ FPU/MMX/SSE 保存区 (0x210 字节) ├─────────────────────────┤ │ KTRAP_FRAME │ 陷阱帧 — 保存完整 CPU 上下文 (0x8C 字节) │ ├─ Eip = SMSS 入口 │ │ ├─ SegCs = 0x1B │ ← KGDT_R3_CODE | RPL_MASK │ ├─ SegSs = 0x23 │ ← KGDT_R3_DATA | RPL_MASK │ ├─ HardwareEsp │ 用户栈指针 │ └─ EFlags │ 用户态 EFLAGS ├─────────────────────────┤ │ KSTART_FRAME │ 启动帧 │ ├─ SystemRoutine │ = PspUserThreadStartup │ ├─ StartRoutine │ = NULL │ ├─ StartContext │ = SMSS 入口点 (EIP) │ └─ UserThread │ = TRUE ├─────────────────────────┤ │ KSWITCHFRAME │ 上下文切换帧 │ ├─ RetAddr │ = KiThreadStartup ← 切换后返回地址 │ ├─ ApcBypassDisable │ = TRUE │ └─ ExceptionList │ = EXCEPTION_CHAIN_END └─────────────────────────┘ ← Thread->KernelStack = &CtxSwitchFrame关键设置(第 170-203 行):
// 从 CONTEXT 转换到 KTRAP_FRAMEKeContextToTrapFrame(Context,NULL,TrapFrame,Context->ContextFlags|ContextFlags,UserMode);// 段寄存器设为 Ring 3TrapFrame->HardwareSegSs|=RPL_MASK;// SS = 0x23TrapFrame->SegDs|=RPL_MASK;TrapFrame->SegEs|=RPL_MASK;// CS 在 KeContextToTrapFrame 中已设为 0x1B// CtxSwitchFrame.RetAddr = KiThreadStartupCtxSwitchFrame->RetAddr=KiThreadStartup;CtxSwitchFrame->ApcBypassDisable=TRUE;CtxSwitchFrame->ExceptionList=EXCEPTION_CHAIN_END;// 保存内核栈指针Thread->KernelStack=(PVOID)CtxSwitchFrame;5. 调度器上下文切换 — 首次切换到 SMSS
当KiIdleLoop或调度器选中 SMSS 线程时,执行上下文切换:
5.1 KiSwapContext 汇编([ctxswitch.S](file:///d:/reactos/ntoskrnl/ke/i386/ctxswitch.S))
KiIdleLoop (或 KiDispatchInterrupt) └─ KiSwapContext(APC_LEVEL, OldThread) └─ KiSwapContextInternal └─ KiSwapContextEntry(KSWITCHFRAME, OldThread~) 1. 保存 old_esp → OldThread->KernelStack 2. 处理 FPU 上下文 └─ KiSwitchThreads(OldThread, NewThread->KernelStack) mov esp, NewThread->KernelStack ← ★ 栈切换 # ESP 现在指向 SMSS 的 KSWITCHFRAME KiSwapContextExit ├─ 检查进程切换: OldProcess != NewProcess │ └─ mov cr3, NewProcess->DirectoryTableBase ← 切换地址空间 ├─ TSS.Esp0 = NewThread->InitialStack ├─ TEB 设置 └─ 异常链 恢复非易失寄存器 ret ──→ KiThreadStartup (因为 RetAddr = KiThreadStartup)6. KiThreadStartup — 线程启动桩
文件:[ke/i386/thrdini.c#L63](file:///d:/reactos/ntoskrnl/ke/i386/thrdini.c#L63)
VOID NTAPIKiThreadStartup(VOID){InitFrame=KeGetCurrentThread()->KernelStack;// 拿到 CtxSwitchFrameStartFrame=&InitFrame->StartFrame;TrapFrame=&InitFrame->TrapFrame;KfLowerIrql(APC_LEVEL);// 对于 SMSS: 调用 PspUserThreadStartup(NULL, SMSS_EntryPoint)StartFrame->SystemRoutine(StartFrame->StartRoutine,StartFrame->StartContext);if(!StartFrame->UserThread)KeBugCheck(NO_USER_MODE_CONTEXT);// 通过 iret 切换到用户态KiServiceExit2(TrapFrame);// ---- 永不返回 ----}6.1 PspUserThreadStartup — 排队 LdrInitializeThunk APC
文件:[ps/thread.c#L26](file:///d:/reactos/ntoskrnl/ps/thread.c#L26)
VOID NTAPIPspUserThreadStartup(PKSTART_ROUTINE StartRoutine,PVOID StartContext){KeLowerIrql(PASSIVE_LEVEL);Thread=PsGetCurrentThread();// 通知调试器DbgkCreateThread(Thread,StartContext);// ★ 排队 LdrInitializeThunk 作为用户 APCKiInitializeUserApc(KeGetExceptionFrame(&Thread->Tcb),KeGetTrapFrame(&Thread->Tcb),// KTRAP_FRAMELdrInitializeThunk,// ntdll 入口NULL,PspSystemDllBase,NULL);}LdrInitializeThunk是 ntdll.dll 的用户态初始化函数。它被排队成一个用户 APC(异步过程调用),将在KiServiceExit2→KiCommonExit→KiCheckForApcDelivery中被传递。
7. KiServiceExit2 — iret 到用户态
文件:[ke/i386/traphdlr.c#L189](file:///d:/reactos/ntoskrnl/ke/i386/traphdlr.c#L189)
DECLSPEC_NORETURN VOID FASTCALLKiServiceExit2(IN PKTRAP_FRAME TrapFrame){KiCommonExit(TrapFrame,FALSE);// 关闭中断、传递 APC、恢复异常链// 检查 CS 的 CPL 位if(TrapFrame->SegCs&MODE_MASK)←TRUE(CS=0x1B,CPL=3)// 进入用户态出口路径KiTrapReturn(TrapFrame);}7.1 KiTrapReturn([asmmacro.S](file:///d:/reactos/ntoskrnl/include/internal/i386/asmmacro.S#L313))
KiTrapReturn: lea esp, [ecx + KTRAP_FRAME_EIP] ; ESP 指向 TrapFrame.Eip ; 恢复非易失寄存器 mov ebx, ...; mov esi, ...; mov edi, ...; mov ebp, ... ; 恢复段寄存器 mov ds, ...; mov es, ...; mov gs, ...; mov fs, ... ; 此时栈顶布局 (从低到高): ; [EIP] = LdrInitializeThunk (ntdll 入口) ; [SegCs] = 0x1B (Ring 3 代码段) ; [EFlags] = 用户态 EFLAGS ; [HardwareEsp] = 用户态栈指针 ; [HardwareSegSs] = 0x23 (Ring 3 数据段) iretd ; ↑ 弹出 EIP, CS, EFLAGS, ESP, SS ; CS.RPL = 3 → CPU 从 Ring 0 切换到 Ring 3 ; 开始执行 LdrInitializeThunk8. 最终状态
| 资源 | 值 |
|---|---|
| CS | 0x1B(KGDT_R3_CODE | RPL_MASK) |
| SS | 0x23(KGDT_R3_DATA | RPL_MASK) |
| EIP | LdrInitializeThunk(ntdll 初始化入口) |
| ESP | SMSS 用户栈指针 |
| CR3 | SMSS 进程的页目录(地址空间已切换) |
| TSS.Esp0 | SMSS 线程的InitialStack(下次系统调用入口) |
| PreviousMode | UserMode |
SMSS.EXE 在 ntdll 初始化完成后调用它的main入口点,开始作为会话管理器运行。
9. 完整调用链
Phase1InitializationDiscard() [ex/init.c] └─ ExpLoadInitialProcess() [ex/init.c:389] └─ RtlCreateUserProcess() ├─ NtCreateProcessEx() → PspCreateProcess() [ps/process.c:347] │ └─ EPROCESS 创建 + 地址空间初始化 └─ NtCreateThread() → PspCreateThread() [ps/thread.c:172] └─ KeInitThread() → KiInitializeContextThread() [ke/i386/thrdini.c:92] └─ 布局 KUINIT_FRAME: KTRAP_FRAME: CS=0x1B, EIP=SMSS入口 KSWITCHFRAME.RetAddr = KiThreadStartup Thread->KernelStack = &CtxSwitchFrame └─ KeStartThread() + KeReadyThread() [调度器切换] KiIdleLoop() └─ KiSwapContext() └─ KiSwitchThreads(mov esp, NewThread->KernelStack) └─ KiSwapContextExit (CR3 切换, TSS.Esp0, TEB) └─ ret → KiThreadStartup() [ke/i386/thrdini.c:63] └─ PspUserThreadStartup() [ps/thread.c:26] └─ KiInitializeUserApc(LdrInitializeThunk) [排队 APC] └─ KiServiceExit2(TrapFrame) [ke/i386/traphdlr.c:189] └─ KiCommonExit() └─ KiCheckForApcDelivery → 执行 LdrInitializeThunk └─ KiTrapReturn() └─ iretd └─ ★ CS.RPL=3 → Ring 3 EIP = LdrInitializeThunk ESP = 用户栈 → SMSS.EXE 开始运行关键设计要点
栈上的初始化帧— KUINIT_FRAME 包含 CtxSwitchFrame + StartFrame + TrapFrame,
InitialStack到KernelStack的偏移决定了这些帧的位置iret 的 Ring 切换—
KTRAP_FRAME.SegCs = 0x1B(CPL=3)导致iretd自动从 Ring 0 切换到 Ring 3用户 APC 传递—
PspUserThreadStartup排队LdrInitializeThunk,在KiCommonExit中通过KiCheckForApcDelivery在实际的iret前完成传递地址空间切换—
KiSwapContextExit在切换到新线程时设置 CR3,使 SMSS 运行在自己的地址空间中TSS.Esp0— 设置为
InitialStack,确保 SMSS 在用户态执行系统调用(int 0x2E/sysenter)时,CPU 自动切换到正确的内核栈
