当前上下文 目标上下文
┌───────────────────────┐ ┌───────────────────────┐
│ 寄存器/状态保存区域 │ │ 寄存器/状态恢复区域 │
│ - MXCSR, x87, TEB │ │ - MXCSR, x87, TEB │
│ - EDI, ESI, EBX, EBP│ │ - EDI, ESI, EBX, EBP│
└──────────┬────────────┘ └──────────▲────────────┘
│ │
│ 1. 保存状态到栈 │ 2. 从栈恢复状态
│ 3. EAX=当前栈指针 │ 4. ESP=目标栈指针
│ │
│ 5. 加载目标参数(ECX) │ 6. 恢复EIP(ECX)
│ │
└───────────┐ ┌────────┘
▼ ▼
┌───────────────┐ 切换栈指针 ┌───────────────┐
│ 调用者栈空间 │◄───────────────┤ 目标函数栈空间 │
└───────────────┘ └───────────────┘
低地址 ┌─────────────────────┐
│ fc_mxcsr │ (0x00) SSE控制状态
├─────────────────────┤
│ fc_x87_cw │ (0x04) x87 FPU控制字
├─────────────────────┤
│ fc_strg │ (0x08) FiberLocalStorageData
├─────────────────────┤
│ fc_deallo │ (0x0C) DeallocationStack
├─────────────────────┤
│ limit │ (0x10) 栈限制地址
├─────────────────────┤
│ base │ (0x14) 栈基址
├─────────────────────┤
│ fc_seh │ (0x18) SEH异常链头
├─────────────────────┤
│ EDI │ (0x1C) 寄存器EDI
├─────────────────────┤
│ ESI │ (0x20) 寄存器ESI
├─────────────────────┤
│ EBX │ (0x24) 寄存器EBX
├─────────────────────┤
│ EBP │ (0x28) 寄存器EBP
├─────────────────────┤
│ EIP │ (0x2C) 返回地址(由jmp设置)
├─────────────────────┤
│ 目标上下文指针(to) │ (0x30) 函数参数1
├─────────────────────┤
│ 数据指针(data) │ (0x34) 函数参数2
高地址 └─────────────────────┘
jump_fcontext PROC BOOST_CONTEXT_EXPORT
; === 保存当前上下文 ===
lea esp, [esp-02ch] ; 预留44字节栈空间
; 保存FPU状态
stmxcsr [esp]
fnstcw [esp+04h]
; 保存TEB关键字段
mov edx, fs:[018h] ; 获取TEB地址
mov eax, [edx+010h] ; FiberLocalStorageData
mov [esp+08h], eax
mov eax, [edx+0e0ch] ; DeallocationStack
mov [esp+0ch], eax
mov eax, [edx+08h] ; StackLimit
mov [esp+010h], eax
mov eax, [edx+04h] ; StackBase
mov [esp+014h], eax
mov eax, [edx] ; SEH链头
mov [esp+018h], eax
; 保存通用寄存器
mov [esp+01ch], edi
mov [esp+020h], esi
mov [esp+024h], ebx
mov [esp+028h], ebp
; === 切换到目标上下文 ===
mov eax, esp ; 保存当前上下文指针
mov ecx, [esp+030h] ; 获取目标上下文
mov esp, ecx ; 关键操作:切换栈指针
; === 恢复目标上下文 ===
; 恢复FPU状态
ldmxcsr [esp]
fldcw [esp+04h]
; 恢复TEB字段
mov edx, fs:[018h]
mov ecx, [esp+08h]
mov [edx+010h], ecx ; 恢复FiberLocalStorage
mov ecx, [esp+0ch]
mov [edx+0e0ch], ecx ; 恢复DeallocationStack
mov ecx, [esp+010h]
mov [edx+08h], ecx ; 恢复StackLimit
mov ecx, [esp+014h]
mov [edx+04h], ecx ; 恢复StackBase
mov ecx, [esp+018h]
mov [edx], ecx ; 恢复SEH链头
; 恢复通用寄存器
mov ecx, [esp+02ch] ; 获取目标EIP
mov edi, [esp+01ch]
mov esi, [esp+020h]
mov ebx, [esp+024h]
mov ebp, [esp+028h]
; 清理栈空间并跳转
lea esp, [esp+030h] ; 调整栈指针
mov edx, [eax+034h] ; 获取数据参数
jmp ecx ; 跳转到目标EIP
jump_fcontext ENDP
优化点 | 实现方式 | 性能收益 |
---|---|---|
最小寄存器保存 | 仅保存callee-saved寄存器 | 减少50%保存开销 |
直接栈切换 | MOV ESP, ECX 指令 |
避免内存复制操作 |
无调用返回 | 使用JMP而非CALL/RET | 减少流水线停顿 |
Windows线程环境块(TEB)是每个线程的核心数据结构,位于用户空间但包含系统级信息:
// TEB结构关键字段(部分)
typedef struct _TEB {
NT_TIB NtTib; // 线程信息块
PVOID EnvironmentPointer; // 环境指针
CLIENT_ID ClientId; // 客户端ID
PVOID ActiveRpcHandle; // 活动RPC句柄
PVOID ThreadLocalStoragePointer; // TLS指针
// 关键保存字段
struct _PEB* ProcessEnvironmentBlock; // PEB指针
ULONG LastErrorValue; // 最后错误值
ULONG CountOfOwnedCriticalSections; // 拥有的临界区计数
PVOID CsrClientThread; // CSR客户端线程
// 上下文切换保存的字段
PVOID FiberData; // Fiber本地存储数据 (fs:[0x10])
PVOID DeallocationStack; // 释放栈 (fs:[0xE0C])
PVOID StackLimit; // 栈限制 (fs:[0x08])
PVOID StackBase; // 栈基址 (fs:[0x04])
PVOID ExceptionList; // SEH异常链头 (fs:[0x00])
// ... 其他字段 ...
} TEB, *PTEB;
保存阶段:
切换阶段:
MOV ESP, ECX
实现原子级栈切换恢复阶段:
跳转阶段:
访问机制:
FS:[0x18]
指向当前线程的TEBSEH链处理:
__try {
// 受保护代码
} __except(filter) {
// 异常处理
}
FPU状态优化:
热点指令序列:
mov eax, esp ; 1周期
mov ecx, [esp+30h] ; 3-4周期
mov esp, ecx ; 1周期 ← 最关键的切换点
jmp ecx ; 1-2周期
总开销:6-8 CPU周期
SEH链一致性:
栈溢出防护:
操作 | 传统线程切换 | 上下文切换 | 优势倍数 |
---|---|---|---|
时间开销 | 1000-1500周期 | 50-100周期 | 15-30倍 |
内存开销 | 1-4KB内核栈 | 仅保存寄存器 | 10-100倍 |
特权切换 | 需要(用户/内核) | 纯用户态操作 | 无系统调用 |
缓存影响 | TLB刷新/缓存污染 | 无影响 | 显著降低 |
高性能网络框架:
游戏引擎:
实时数据处理:
// 上下文数据结构(栈上布局)
struct context_data {
uint32_t fc_mxcsr; // SSE 控制状态
uint32_t fc_x87_cw; // x87 FPU 控制字
uint32_t fc_strg; // Fiber 本地存储
uint32_t fc_deallo; // 释放栈指针
uint32_t limit; // 栈限制地址
uint32_t base; // 栈基址
uint32_t fc_seh; // SEH 异常链
uint32_t edi; // EDI 寄存器值
uint32_t esi; // ESI 寄存器值
uint32_t ebx; // EBX 寄存器值
uint32_t ebp; // EBP 寄存器值
uint32_t eip; // 指令指针(返回地址)
};
// 线程环境块(TEB)结构
struct teb {
uint32_t seh_chain; // FS:[0x00] SEH 异常链头
uint32_t stack_base; // FS:[0x04] 栈基址
uint32_t stack_limit; // FS:[0x08] 栈限制
uint32_t sub_system; // ...
uint32_t fiber_data; // FS:[0x10] Fiber 本地存储
uint32_t version; // ...
uint32_t deallocation_stack;// FS:[0xE0C] 释放栈
// ... 其他字段省略
};
// 上下文切换函数
transfer_t jump_fcontext(fcontext_t to, void* data) {
// 获取当前栈指针
uintptr_t stack_ptr = get_stack_pointer();
// === 保存当前上下文 ===
// 在栈上分配空间
stack_ptr -= sizeof(context_data);
context_data* current_ctx = reinterpret_cast<context_data*>(stack_ptr);
// 保存 FPU 状态
current_ctx->fc_mxcsr = read_mxcsr();
current_ctx->fc_x87_cw = read_x87_cw();
// 保存 TEB 关键字段
teb* teb_ptr = get_current_teb(); // FS:[0x18]
current_ctx->fc_strg = teb_ptr->fiber_data;
current_ctx->fc_deallo = teb_ptr->deallocation_stack;
current_ctx->limit = teb_ptr->stack_limit;
current_ctx->base = teb_ptr->stack_base;
current_ctx->fc_seh = teb_ptr->seh_chain;
// 保存寄存器
current_ctx->edi = read_register(EDI);
current_ctx->esi = read_register(ESI);
current_ctx->ebx = read_register(EBX);
current_ctx->ebp = read_register(EBP);
// 设置返回的上下文指针
fcontext_t from_ctx = reinterpret_cast<fcontext_t>(current_ctx);
// === 切换到目标上下文 ===
context_data* target_ctx = reinterpret_cast<context_data*>(to);
set_stack_pointer(reinterpret_cast<uintptr_t>(target_ctx));
// === 恢复目标上下文 ===
// 恢复 FPU 状态
write_mxcsr(target_ctx->fc_mxcsr);
write_x87_cw(target_ctx->fc_x87_cw);
// 恢复 TEB 字段
teb_ptr = get_current_teb();
teb_ptr->fiber_data = target_ctx->fc_strg;
teb_ptr->deallocation_stack = target_ctx->fc_deallo;
teb_ptr->stack_limit = target_ctx->limit;
teb_ptr->stack_base = target_ctx->base;
teb_ptr->seh_chain = target_ctx->fc_seh;
// 恢复寄存器
write_register(EDI, target_ctx->edi);
write_register(ESI, target_ctx->esi);
write_register(EBX, target_ctx->ebx);
write_register(EBP, target_ctx->ebp);
// 获取跳转地址
uint32_t target_eip = target_ctx->eip;
// 清理栈空间
set_stack_pointer(reinterpret_cast<uintptr_t>(target_ctx + 1));
// 准备返回值
transfer_t result;
result.fctx = from_ctx;
result.data = data;
// 跳转到新上下文
asm volatile (
"jmp *%0"
:
: "r" (target_eip)
);
// 此处不会执行
return result;
}
// 使用示例
void example_usage() {
// 初始化新上下文
context_data* new_ctx = create_context(stack_ptr, target_function);
// 执行上下文切换
transfer_t transfer = jump_fcontext(new_ctx, user_data);
// 当切换回来时继续执行
print("Returned with data: %p", transfer.data);
}
这种高效的上下文切换机制为高性能协程、用户态线程和轻量级并发框架提供了坚实基础,特别适合网络服务和游戏引擎等低延迟场景。