
目录
[1.1 硬件上下文(进程切换核心)](#1.1 硬件上下文(进程切换核心))
[1.2 软件上下文](#1.2 软件上下文)
[三、进程切换底层原理 switch_to](#三、进程切换底层原理 switch_to)
[进程切换 VS 同进程内线程切换对比](#进程切换 VS 同进程内线程切换对比)
[五、自愿上下文切换 & 非自愿上下文切换](#五、自愿上下文切换 & 非自愿上下文切换)
[六、查看上下文切换的 Linux 实操命令](#六、查看上下文切换的 Linux 实操命令)
[1. vmstat 全局查看系统上下文切换](#1. vmstat 全局查看系统上下文切换)
[2. pidstat 观测单个进程的上下文切换](#2. pidstat 观测单个进程的上下文切换)
[7.1 上下文切换 ≠ 系统调用](#7.1 上下文切换 ≠ 系统调用)
[7.2 进程切换发生在内核态,不能在用户态完成](#7.2 进程切换发生在内核态,不能在用户态完成)
[Q2:switch_to 函数主要做了哪些工作?](#Q2:switch_to 函数主要做了哪些工作?)
一、进程上下文
进程上下文,是进程能够继续运行所需要的全部运行环境信息,分为硬件上下文 和软件上下文两大块。
1.1 硬件上下文(进程切换核心)
CPU 内部所有寄存器里面保存的数据,就是硬件上下文:通用寄存器、ebp、esp、程序计数器 PC(eip/x86_64 下 rip)、标志寄存器、段寄存器。
- PC(程序计数器):保存下一条要执行指令的地址。
- 当进程被从 CPU 剥离,硬件上下文会完整存入该进程 PCB(task_struct);当进程再次被调度上 CPU,从 PCB 读取硬件上下文,回填到 CPU 寄存器,进程从上次暂停的位置继续运行,不会从头执行。

参考一下Linux内核0.11代码
Linux内核调度队列
1.2 软件上下文
存放在 PCB 内,描述进程资源的软件层面信息:虚拟地址空间、页表、文件描述符表、信号状态、进程优先级、进程组、会话信息。
一句话总结:进程上下文 = 硬件现场 + 进程所有资源描述信息。
二、触发进程切换的场景
进程切换只会发生在内核态,一般在中断、系统调用返回内核态时检查是否需要调度。
- 时间片耗尽(最常见,非自愿切换) Linux 给就绪队列 R 状态进程分配时间片,进程在 CPU 运行,时间片用完,时钟硬件中断触发。内核进入中断处理,中断收尾阶段触发调度,换下当前进程,挑选新进程。
- 进程主动阻塞,自愿放弃 CPU(自愿切换) 进程调用
sleep()、read()、wait()这类阻塞系统调用,需要等待 IO、等待资源。进程主动让出 CPU,状态由 R 变为 S/D,进入等待队列,触发进程切换。 - 抢占式调度,高优先级进程就绪 Linux2.6 引入抢占式内核。低优先级进程正在 CPU 运行,此时更高优先级进程变为就绪 R 状态,内核可以抢占 CPU,强制换下低优先级进程,运行高优先级进程。
- 硬件中断处理结束 硬件外设触发中断,内核进入中断处理流程。中断处理完毕,准备返回用户态之前,内核会检查是否有就绪更高优先级进程,满足条件就触发进程切换。
重要区分:中断 ≠ 进程切换。中断只是处理硬件事件;只有中断返回前调度器判定需要更换进程,才执行切换。
三、进程切换底层原理 switch_to
Linux 内核真正完成硬件上下文切换的底层函数 switch_to,是汇编函数,无法用纯 C 语言实现。C 语言执行本身依赖寄存器,一旦开始切换寄存器,C 语言栈环境失效。 函数接收两个 task_struct 指针:当前运行进程、即将要调度的下一个进程。
switch_to 内部执行完整三步:
- 保存当前进程硬件上下文 把 CPU 全部通用寄存器、栈寄存器、PC 指令地址,保存到当前进程 PCB 内预留的上下文存储区域。保存完成,当前进程暂停运行。
- 切换内核栈 + 修改 CR3 寄存器,更换页表 CR3 寄存器存放当前进程页表的物理地址,MMU 内存管理单元依靠 CR3 完成虚拟地址到物理地址的转换。 不同进程拥有独立虚拟地址空间,页表不同,修改 CR3,就切换地址空间。
进程切换和线程切换最核心差别:同一进程内线程共享一套页表,线程切换不需要修改 CR3,开销小很多。
- 加载新进程硬件上下文 从新进程 PCB 中读取之前保存的寄存器数据,回填到 CPU 寄存器,恢复 PC 值。CPU 从 PC 指向的地址继续执行新进程代码。
四、进程切换的开销(重点)
开销分为直接开销、间接开销,间接开销往往更大。
- 直接开销 寄存器保存与恢复、内核栈切换、修改 CR3 页表,也就是
switch_to本身执行消耗的 CPU 周期。 - 间接开销(缓存失效,最大损耗) CPU L1/L2/L3 高速缓存,缓存当前进程指令与数据。当切换到另一个完全独立的进程,缓存内的数据基本失效。新进程的数据需要重新从内存加载进缓存,缓存命中率大幅下降,带来巨大性能损耗。
工程结论:大量频繁进程切换,CPU 大量时间消耗在切换本身,业务代码能拿到的 CPU 时间变少,系统吞吐量下降。
进程切换 VS 同进程内线程切换对比
表格
| 对比项 | 进程切换 | 同一进程内线程切换 |
|---|---|---|
| CR3 页表寄存器 | 需要修改,更换独立地址空间 | 不修改 CR3,共享页表 |
| CPU 高速缓存 | 缓存大概率失效 | 缓存数据大部分保留 |
| 切换开销 | 开销很大 | 开销很小 |
五、自愿上下文切换 & 非自愿上下文切换
使用pidstat -w命令可以观测这两类切换,是服务器性能排查核心指标。
- 自愿上下文切换 cswch 进程主动放弃 CPU。例如调用 sleep、等待磁盘 IO、等待管道数据,主动进入阻塞状态。属于正常现象,代表进程在等资源。
- 非自愿上下文切换 nvcswch 进程不是主动让出 CPU。时间片耗尽、被高优先级进程抢占,被强制剥夺 CPU。
非自愿切换数值持续很高,代表 CPU 负载压力大,就绪队列进程太多,互相争抢 CPU。
六、查看上下文切换的 Linux 实操命令
1. vmstat 全局查看系统上下文切换
vmstat 1
cs列:context switch,每秒全局上下文切换次数in列:每秒硬件中断次数
2. pidstat 观测单个进程的上下文切换
pidstat -w 1
- cswch/s:每秒自愿上下文切换次数
- nvcswch/s:每秒非自愿上下文切换次数
七、易混淆概念辨析
7.1 上下文切换 ≠ 系统调用
系统调用:用户态陷入内核态,在内核执行内核代码。发生系统调用,不一定触发进程切换。进程依旧占用 CPU,只是从用户态切换到内核态执行代码,进程本身没有被换下 CPU。 只有调度器选择更换另一个进程,才会执行进程上下文切换。
7.2 进程切换发生在内核态,不能在用户态完成
用户态权限受限,没有权限修改 CR3 寄存器、直接操作硬件寄存器。 完整流程:
- 用户态运行 → 中断 / 系统调用触发,进入内核态
- 内核调度器选出下一个待运行进程
- 调用
switch_to执行硬件上下文切换 - 新进程内核态返回自己的用户态,继续运行
八、面试高频问答
Q1:进程上下文包含哪些内容?
硬件上下文(CPU 全部寄存器、PC 指令寄存器) + 软件上下文(PCB 中保存的进程资源信息:虚拟地址空间、文件描述符、信号、优先级、进程组等),硬件上下文是切换核心。
Q2:switch_to 函数主要做了哪些工作?
汇编底层函数,保存当前进程寄存器上下文到 PCB,切换内核栈,修改 CR3 切换进程页表,加载新进程寄存器上下文,完成进程切换。
Q3:进程切换开销为什么大?
除了寄存器读写的直接开销,更换页表会导致 CPU 高速缓存失效,新进程数据需要重新从内存载入缓存,带来很大的间接性能损耗。
Q4:自愿上下文切换和非自愿上下文切换区别?
自愿:进程主动阻塞等待资源,主动放弃 CPU,属于正常 IO 等待; 非自愿:进程时间片耗尽、被高优先级进程抢占,被动被剥夺 CPU。大量非自愿切换代表 CPU 压力高。
Q5:进程切换和线程切换的核心区别?
同一个进程内部多个线程共享虚拟地址空间,线程切换不需要修改 CR3 页表,CPU 缓存大部分数据保留,切换开销远低于进程切换。
Q6:进程切换为什么只能在内核态执行?
用户态权限低,无法直接操作 CR3 页表寄存器、硬件寄存器,只有内核态拥有操作硬件的权限。进程切换一般在中断或者系统调用返回内核态时执行。
九、小结
进程切换本质:保存旧进程硬件现场,加载新进程硬件现场。底层依靠汇编switch_to完成,修改 CR3 寄存器切换页表是进程切换标志性操作。 进程切换存在性能开销,频繁切换会降低系统整体性能。学会区分自愿 / 非自愿上下文切换,是 Linux 服务器性能调优的基础。
本篇结束,下一章:环境变量



