一、普通控制流(函数调用)
程序代码示例:
int foo(int x) {
return x + 1;
}
int main() {
int a = 5;
int b = foo(a); // 调用函数 foo
printf("Result: %d\n", b);
return 0;
}
执行过程中的上下文变化:
-
主函数
main()的上下文:- CPU 寄存器(如程序计数器 PC)指向
main()中的指令。 - 栈中保存了
main()的局部变量a和b。 - 其他寄存器(如通用寄存器)可能存储临时数据。
- CPU 寄存器(如程序计数器 PC)指向
-
调用函数
foo():- 保存上下文:
- CPU 会将当前 PC(即
main()中下一条指令的地址,即printf(...)的地址)压入栈,作为返回地址。 - 将
main()的栈帧(包括局部变量a、b等)与foo()的栈帧分离。
- CPU 会将当前 PC(即
- 切换上下文:
- PC 被设置为
foo()的起始地址,开始执行foo()。 foo()的局部变量x被分配在栈中。
- PC 被设置为
- 恢复上下文:
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分配给其他进程。流程如下:
- 保存当前进程上下文:将寄存器、PC等保存到进程控制块(PCB)。
- 恢复目标进程上下文:从目标进程的PCB加载寄存器、PC等。
- 切换执行:CPU开始执行目标进程的指令。
示例
假设进程A正在运行,因等待磁盘I/O被阻塞:
- 操作系统保存进程A的上下文到PCB。
- 选择进程B,恢复其上下文(如PC、寄存器)。
- CPU开始执行进程B的指令。