进程切换

2.2.2 讲的是决定换谁,这一节讲换的过程本身——保存什么、恢复什么、要花多大代价。

本节的考点高度集中在三组辨析上:调度 vs 切换、进程切换 vs 模式切换、切换开销从哪儿来。

机制

切换要做什么

进程切换的实质是:把当前进程的运行现场原样封存,再把下一个进程封存过的现场原样恢复。

三步:

  1. 保存原进程的上下文——通用寄存器、PC、PSW、栈指针等现场信息写回该进程的 PCB;同时更新 PCB 中的状态、统计信息。
  2. 更新 PCB 并转移队列——把原进程的 PCB 移入就绪队列或某个阻塞队列。
  3. 恢复新进程的上下文——从新进程的 PCB 中取出现场装入各寄存器,并切换地址空间(改页表基址寄存器)。

第 3 步里的”切换地址空间”是开销的大头,见下文。

切换开销从哪儿来

直接开销是保存和恢复那几十个寄存器——这部分其实很小。

真正昂贵的是间接开销:

① 切换页表基址寄存器导致 TLB 全部失效。 新进程的逻辑地址与旧进程毫无关系,TLB里缓存的全部映射瞬间作废。之后新进程每访问一个页都要重新走地址转换、重新填 TLB。

② Cache 命中率骤降。 新进程访问的是完全不同的数据,Cache 里装的还是旧进程的内容,需要一段时间才能”热”起来。

这两条合起来,可能比保存寄存器贵一个数量级。 而这也正好解释了线程为什么”轻”——同进程内的线程切换不换地址空间,TLB 不失效,上述两条间接开销全部避免。

切换只能在内核态完成

进程切换必须在内核态执行,有两层原因:

权限层面:切换要读写 PCB(位于内核空间)、要修改页表基址寄存器、要操作内核栈。这些都是特权操作,用户态执行会直接触发特权指令异常。

逻辑层面:切换的决策依据是就绪队列的状态,而就绪队列是内核数据结构,用户态程序根本看不到它。

由此得到一条常考结论:任何一次进程切换,都必然先经历一次模式切换(从用户态进入内核态)。

边界

调度 ≠ 切换

调度是决策(按算法选出下一个该运行的进程),切换是执行(保存旧现场、恢复新现场)。

先有调度,后有切换。但要注意:调度的结果可能是”还是原来那个进程”——此时不发生切换。

例如时间片用完时重新调度,若就绪队列中没有比它更合适的,它可以继续运行,一次切换都不必做。

所以”发生了调度”不等于”发生了切换”。 这是选择题的常见设问点。

进程切换 vs 模式切换

这是全书最高频的边界之一,与 2.1.4 的结论一致:

模式切换进程切换
换的是什么CPU 的运行模式(用户态↔内核态)正在运行的进程
进程变了吗没变,还是同一个变了
要换地址空间吗不用要(改页表基址寄存器)
TLB 失效否是
开销小大

关系是单向的:进程切换一定伴随模式切换,反过来不成立。

一个进程调用 getpid() 这类系统调用,进内核、取个数、回用户态,全程都是它自己,没有发生任何进程切换。

上下文切换 vs 进程切换

两个词常被混用,但严格说粒度不同:

  • 上下文切换是那个具体动作——保存一份现场、恢复另一份现场。
  • 进程切换是整件事,它包含一次上下文切换,还包含更新 PCB、转移队列、切换地址空间。

调度程序的三部分里那个”上下文切换器”,负责的正是前者。它会执行两次上下文切换:第一次保存原进程现场并装入分派程序的上下文,第二次移出分派程序的上下文并装入新进程的现场。

对照速查

切换三步内容
① 保存原进程上下文现场信息写回 PCB
② 更新 PCB 并转移队列移入就绪队列或阻塞队列
③ 恢复新进程上下文装入寄存器 + 切换地址空间
开销来源大小
直接保存/恢复寄存器小
间接TLB 全部失效大
间接Cache 命中率骤降大
调度切换
性质决策(选谁)执行(怎么换)
一定发生吗—不一定(可能仍是原进程)

考点

  • 调度 ≠ 切换:调度结果可能仍是原进程,此时不发生切换
  • 进程切换一定伴随模式切换,反之不成立
  • 切换开销的大头是 TLB 失效与 Cache 失效,不是保存寄存器
  • 进程切换只能在内核态完成(权限 + 逻辑两层原因)
  • 上下文切换器执行两次上下文切换

链接