0.3 控制流上下文详细解释

122 阅读3分钟

一、普通控制流(函数调用)

程序代码示例:

int foo(int x) {
    return x + 1;
}

int main() {
    int a = 5;
    int b = foo(a);  // 调用函数 foo
    printf("Result: %d\n", b);
    return 0;
}

执行过程中的上下文变化:

  1. 主函数 main() 的上下文:

    • CPU 寄存器(如程序计数器 PC)指向 main() 中的指令。
    • 栈中保存了 main() 的局部变量 ab
    • 其他寄存器(如通用寄存器)可能存储临时数据。
  2. 调用函数 foo()

    • 保存上下文:
      • CPU 会将当前 PC(即 main() 中下一条指令的地址,即 printf(...) 的地址)压入栈,作为返回地址。
      • main() 的栈帧(包括局部变量 ab 等)与 foo() 的栈帧分离。
    • 切换上下文:
      • PC 被设置为 foo() 的起始地址,开始执行 foo()
      • foo() 的局部变量 x 被分配在栈中。
    • 恢复上下文:
      • foo() 执行完成后,PC 从栈中弹出,恢复到 main()printf(...) 指令。
      • 栈帧切换回 main(),程序继续执行。

二、异常控制流的三类:中断、异常、陷入

操作系统需要处理三类异常控制流,它们分别由不同的触发原因、处理方式和目的驱动。

2.1 中断(Interrupt)

定义

中断是由外部设备(如键盘、磁盘、时钟)触发的异步事件。例如:

  • 用户按下键盘(触发键盘中断)
  • 磁盘读写完成(触发I/O中断)
  • 定时器到期(触发时钟中断)

示例

假设程序正在执行 main(),此时用户按下键盘:

  • CPU暂停 main(),保存其上下文。
  • 跳转到键盘中断处理程序(如读取按键数据)。
  • 处理完成后,恢复 main() 上下文,程序继续执行。

2.2 异常(Exception)

定义

异常是CPU内部事件(如程序错误、硬件故障)触发的同步事件。例如:

  • 除以零(Zero Division)
  • 非法指令(如执行不存在的指令)
  • 缺页异常(访问未加载的内存页)

示例

程序执行 int a = 5 / 0 时触发除零异常:

  • CPU暂停程序,保存PC(指向除零指令)。
  • 跳转到除零异常处理程序(如打印错误信息并终止程序)。
  • 若异常不可恢复,程序被强制终止。

2.3 陷入(Trap)

定义

陷入是程序主动请求操作系统服务的同步事件,通常通过系统调用(System Call)触发。例如:

  • 调用 read() 读取文件
  • 调用 fork() 创建子进程

示例

程序调用 printf("Hello World")

  • 程序执行 syscall 指令(陷入内核)。
  • CPU保存用户态上下文,跳转到内核的 printf 实现。
  • 系统调用完成后,恢复用户态上下文,程序继续执行。

三、进程与上下文切换

3.1 什么是进程?

进程是程序的一次动态执行过程,由操作系统管理。从应用程序视角看,进程提供了“幻觉”:程序独占系统资源(如CPU、内存)。而操作系统通过进程上下文切换实现多任务并发执行。

进程上下文

进程上下文包括:

  • 内存中的代码和数据(如栈、堆)
  • 当前执行的指令位置(PC)
  • 通用寄存器的值
  • 打开的文件描述符、信号等虚拟资源

3.2 上下文切换的实现

当一个进程因事件(如等待I/O)无法运行时,操作系统通过上下文切换将CPU分配给其他进程。流程如下:

  1. 保存当前进程上下文:将寄存器、PC等保存到进程控制块(PCB)。
  2. 恢复目标进程上下文:从目标进程的PCB加载寄存器、PC等。
  3. 切换执行:CPU开始执行目标进程的指令。

示例

假设进程A正在运行,因等待磁盘I/O被阻塞:

  • 操作系统保存进程A的上下文到PCB。
  • 选择进程B,恢复其上下文(如PC、寄存器)。
  • CPU开始执行进程B的指令。