进程上下文到底是什么东西?
1. 进程上下文的总体分类
一个进程的上下文主要分为三类:
- 用户级上下文(User-level Context)
- 内核级上下文(Kernel-level Context)
2. 各部分的内容
(1)用户级上下文
这是进程在用户态执行时的信息,主要包括:
- 用户虚拟地址空间(进程可访问的内存布局):
- 代码段(Text Segment) → 存放程序的机器指令(只读)
- 数据段(Data Segment) → 存放全局变量、静态变量
- BSS段(BSS Segment) → 存放未初始化的全局变量和静态变量
- 堆(Heap) → 动态分配(malloc/new)
- 共享库映射区 → 动态库、mmap
- 栈(Stack) → 函数调用、局部变量
- 用户栈内容(函数调用参数、返回地址、局部变量)。
- 用户态寄存器值(如
EAX
、EBX
、EIP
、ESP
等)。- 通用寄存器(AX、BX、CX、DX、RAX、RBX …)。
- 程序计数器 PC / 指令指针 IP(表示下一条将要执行的指令地址)
- 栈指针 SP(当前栈顶位置)
- 标志寄存器 EFLAGS / RFLAGS(条件码、中断允许位等)。
- 段寄存器(CS、DS、SS 等)。
- 浮点寄存器 / SIMD 寄存器(FPU/MMX/SSE/AVX 等,现代 CPU 上必须保存)。
👉 这些保证了进程从用户角度看能“接着原来的地方继续运行”。
(2)内核级上下文
当进程进入内核态(系统调用、中断、异常)时,需要的额外上下文:
- 内核栈(每个线程一个,存放系统调用栈帧、局部变量)。
- 内核态寄存器现场(陷入内核时,CPU 会把部分寄存器压入内核栈)。
- 页表基址寄存器 CR3(指向该进程的页表,用于虚拟内存映射)。
- 进程控制块 PCB / task_struct(操作系统保存的进程元数据):
- 进程 ID、父进程 ID
- 进程状态(就绪、运行、阻塞…)
- 打开的文件描述符表
- 信号处理信息
- 调度优先级、时间片信息
- 内存管理信息(mm_struct,指向代码段、堆、栈等的描述符)
- 线程信息(thread_struct,保存寄存器内容等)
👉 这些是 OS 在调度和管理进程时必须用到的。
3. 进程切换过程
1. 触发条件
进程切换通常由以下中断或事件触发:
- 时钟中断(Timer Interrupt) → 实现时间片轮转
- I/O 外中断完成 → 阻塞进程变就绪
- 内中断/异常 → 除零、非法指令、断点
- 系统调用 → 软件中断(INT 指令)主动进入内核
注意:中断处理程序总是在内核态执行。
2. CPU 响应中断(进入中断处理程序)
- CPU 保存用户态基本上下文
- 自动将以下压入内核栈:
- RIP(下一条指令地址)
- CS(代码段寄存器)
- RFLAGS(标志寄存器)
- RSP(用户栈指针)
- SS(用户栈段寄存器)
- 自动将以下压入内核栈:
- 切换特权级
- CPU 切换到内核态栈(每个进程独立内核栈)
- 段寄存器更新为内核态段
- 跳转到中断描述符表(IDT)指定的中断处理程序入口
3. 中断处理程序(Interrupt Handler)执行
- 保存内核态寄存器
- 保存通用寄存器 RAX-R15、RFLAGS、RIP、RSP 等到 内核栈或上下文结构
- 如使用浮点/SIMD,也可能保存 XMM/YMM/ST 寄存器
- 识别中断来源
- 硬件中断(外中断) → 查询中断号、设备
- 软件中断(系统调用) → 获取系统调用号
- 执行中断处理逻辑
- 时钟中断:
- 更新系统时间
- 调用调度器决定是否切换进程
- I/O 中断:
- 将等待该设备的进程置为就绪
- 异常:
- 处理异常或发送信号给用户进程
- 时钟中断:
4. 调度程序(Scheduler)执行
- 保存当前进程上下文(如果需要切换进程)
- 用户态寄存器、内核栈指针保存到 PCB
- 页表基址 CR3(如果不同进程页表不同)
- 选择下一个进程
- 调度策略(时间片轮转、优先级、多级反馈队列等)
- 更新
current_process
指向新进程 PCB
- 加载新进程上下文
- 恢复用户态寄存器 RAX-R15、RIP、RSP、RFLAGS
- 恢复浮点/SIMD 寄存器(按需)
- 加载页表基址 CR3
- 设置内核栈指针(Kernel Stack Pointer)
5. 返回用户态
- 执行 IRET 或 SYSRET 指令
- RIP、RSP、RFLAGS、CS、SS 全部恢复
- CPU 切换回用户态特权级
- 新进程继续执行
- 如果没有切换进程,则原进程继续执行被中断的指令
- 如果切换了进程,则新进程从上次暂停的地方继续运行
6. 数据结构和寄存器关系
内容 | 存放位置 | 说明 |
---|---|---|
用户态寄存器(RAX-R15、RIP、RSP、RFLAGS) | 内核栈 / PCB 上下文 | 用于恢复用户态执行 |
内核栈寄存器 | PCB | 保存当前内核栈位置 |
FPU/SSE/AVX 寄存器 | PCB(按需) | 浮点或向量寄存器 |
页表基址 CR3 | CPU / PCB | 切换进程时需要切换页表 |
调度信息 | PCB | 优先级、状态等 |
IDT / 中断向量 | 系统全局 | 中断入口地址表 |
7. 总结流程图(文字版)
1 | 用户态进程执行 |
💡 核心理解:
- 中断触发 → CPU 自动压栈 → 内核栈切换 → 中断处理程序
- 中断处理程序 → 保存寄存器 → 执行中断逻辑 → 调度器决定切换
- 切换进程 → 保存当前进程上下文 → 恢复新进程上下文
- IRET 返回用户态 → 新进程继续执行
每个寄存器的意义
好的,我们来完整、系统地列举用户态寄存器,以 x86-64(64 位 CPU)为例,因为这是现代 PC/服务器常用架构。用户态寄存器就是进程在用户态运行时可以访问和使用的 CPU 寄存器,用于存放函数调用信息、中间计算结果、程序计数、栈指针等。
1. 通用寄存器(General Purpose Registers)
x86-64 用户态可以访问的 16 个通用寄存器:
寄存器 | 说明 |
---|---|
RAX | 累加寄存器,函数返回值 |
RBX | 基寄存器,通常保存被调用者保存的数据(callee-saved) |
RCX | 计数寄存器,循环计数 / 函数参数传递 |
RDX | 数据寄存器,函数参数 / I/O 操作 |
RSI | 源索引寄存器,函数参数传递 / 内存操作源地址 |
RDI | 目的索引寄存器,函数参数传递 / 内存操作目标地址 |
RBP | 基址指针寄存器,指向栈帧底部(callee-saved) |
RSP | 栈指针寄存器,指向当前栈顶 |
R8–R15 | 额外通用寄存器(部分被调用者保存或调用者保存) |
x86-64 的函数调用约定(System V AMD64 ABI)规定:
- RBP、RBX、R12–R15 由被调用函数保存(callee-saved)
- RAX、RCX、RDX、RSI、RDI、R8–R11 由调用者保存(caller-saved)
2. 程序计数寄存器
寄存器 | 说明 |
---|---|
RIP | 指令指针 / 程序计数器,指向下一条要执行的指令 |
3. 标志寄存器
寄存器 | 说明 |
---|---|
RFLAGS | 包含条件码(ZF、CF、SF 等)、中断允许标志 IF、方向标志 DF 等 |
4. 段寄存器(用户态可访问)
寄存器 | 说明 |
---|---|
CS | 代码段选择子 |
DS | 数据段选择子 |
ES | 附加段 |
FS | 用户自定义段(常用于线程局部存储 TLS) |
GS | 用户自定义段(TLS 或系统信息) |
SS | 栈段选择子 |
注意:用户态只能访问段寄存器的低特权部分(Selector),不能修改内核段表。
5. 浮点 / SIMD 寄存器(可选,但用户态可用)
- x87 FPU 寄存器:ST0–ST7(浮点栈寄存器)
- SSE 寄存器:XMM0–XMM15(128 位)
- AVX 寄存器:YMM0–YMM15(256 位),AVX-512 扩展还包括 ZMM0–ZMM31(512 位)
这些寄存器通常在用户态执行浮点或向量运算时使用,也属于用户态寄存器,需要在上下文切换时保存。
参考资料
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Doraemon's Blog!
评论