温馨提示×

Linux 进程如何切换

小樊
47
2026-08-31 10:06:46
栏目: 智能运维

在 Linux 中,**进程切换(Process Switch)**本质上是由内核调度器(scheduler)完成的,它涉及 进程被抢占、上下文保存与恢复、以及新进程的运行。下面按“整体流程 → 关键机制 → 代码层面 → 常见误区”来解释。


一、进程切换的本质

进程切换 = 从一个进程的执行上下文切换到另一个进程的执行上下文。

切换时至少包括:

  • CPU 寄存器(PC、SP、通用寄存器等)
  • 堆栈(内核栈 / 用户栈)
  • 内存映射(页表)
  • 调度相关信息(优先级、调度类等)

注意:进程切换 ≠ 线程切换

  • 进程切换:地址空间(页表)通常也要切换
  • 线程切换:同一进程内,地址空间不变

二、进程切换的触发时机

进程切换只在 内核态 发生,常见触发点有:

1️⃣ 时间片耗尽(时钟中断)

  • 时钟中断(timer interrupt
  • 调度器发现当前进程时间片用完
  • 调用 schedule()

2️⃣ 进程主动让出 CPU

  • sleep()
  • wait()
  • mutex / sem 阻塞
  • schedule() 被显式调用

3️⃣ 进程被抢占

  • 高优先级进程就绪
  • 中断处理结束返回内核态时检查 need_resched

4️⃣ 进程退出

  • exit() → 切换到其他进程

三、进程切换的整体流程(宏观)

用户态执行
   ↓
中断 / 系统调用进入内核态
   ↓
调度器选择下一个进程
   ↓
执行上下文切换(context switch)
   ↓
恢复新进程的执行上下文
   ↓
返回用户态继续执行

四、调度器如何工作(核心)

Linux 使用 完全公平调度器(CFS,Completely Fair Scheduler)(默认)。

调度核心函数

schedule()

主要做三件事:

  1. 选择下一个 task_struct
  2. 切换地址空间(页表)
  3. 切换 CPU 寄存器和栈

五、上下文切换的具体过程(重点)

1️⃣ 保存当前进程上下文

保存的内容包括:

  • 通用寄存器
  • 程序计数器(PC)
  • 栈指针(SP)
  • CPU 状态

这些信息保存在:

struct thread_struct thread;

位于 task_struct 中。


2️⃣ 切换地址空间(进程切换关键)

switch_mm()

主要工作:

  • 切换页表(CR3 寄存器)
  • TLB 刷新(必要时)
  • 切换内存映射

✅ 这是 进程切换与线程切换最重要的区别


3️⃣ 切换内核栈和执行流

switch_to(prev, next, last)

这是 架构相关代码(如 x86):

  • 保存旧进程寄存器
  • 恢复新进程寄存器
  • 跳转到新进程的返回地址

在 x86 上本质类似:

push regs
mov esp, next->thread.esp
pop regs
ret

切换完成后:

✅ CPU 看起来就像“从未离开过新进程”


六、进程切换中用户态 / 内核态的变化

阶段 状态
切换前 当前进程内核态
上下文切换 内核态
切换后 新进程内核态
返回 用户态(或继续内核态)

进程切换 不会直接发生在用户态


七、进程切换是否保存用户态寄存器?

不会显式保存,但会“间接保存”

原因:

  • 用户态 → 内核态:寄存器已保存在内核栈
  • 上下文切换时:保存的是内核态上下文
  • 返回用户态时:通过 iret 恢复用户态现场

八、进程切换性能开销

开销主要来自:

  1. 保存 / 恢复寄存器
  2. 切换页表(TLB flush)
  3. Cache / 分支预测失效

因此:

  • 进程切换 > 线程切换
  • 频繁切换会影响性能

九、常见误区

❌ 进程切换 = 直接切换 PC
✅ 实际是完整上下文切换

❌ 切换发生在用户态
✅ 只发生在内核态

❌ 调度器每次都切换进程
✅ 可优化为“不切换”


十、总结一句话

Linux 进程切换是调度器通过 schedule(),在内核态保存当前进程上下文、切换页表和寄存器,恢复新进程上下文完成的执行流切换。


如果你愿意,我也可以:

  • 画一张 进程切换时序图
  • 结合 x86 / ARMswitch_to
  • 对比 进程 / 线程 / 协程切换
  • 源码角度schedule()context_switch()

你更想深入哪一部分?

0