4.1.5 上下文切换与开销分析

上下文切换与开销分析#

上下文切换是操作系统在多个任务间切换执行时保存与恢复运行现场的过程,涉及寄存器、程序计数器、栈、内核数据结构等状态的切换。本节从原理、指标、观测、诊断与优化给出可执行示例。

一、原理草图与类型#

上下文切换路径与触发点示意:

文章图片

类型要点:
- 进程切换:切换地址空间/页表,TLB刷新,开销最大。
- 线程切换:同进程共享地址空间,开销相对小。
- 用户态/内核态切换:系统调用、中断触发,频繁则影响性能。

二、安装与环境准备(工具)#

本节使用常见系统工具与 perf:

# Debian/Ubuntu
sudo apt-get update
sudo apt-get install -y sysstat procps linux-tools-common linux-tools-$(uname -r)

# RHEL/CentOS
sudo yum install -y sysstat procps-ng perf

验证工具:

vmstat -V
pidstat -V
perf --version

三、关键指标与命令示例(含解释)#

1)整体切换频率:vmstat#

vmstat 1 5

输出中的 cs 表示每秒上下文切换次数;持续高于基线且伴随 r(就绪队列)升高,可能存在过度切换。

2)进程/线程切换:pidstat#

# 监控所有进程上下文切换(每秒一次)
pidstat -w 1 5

# 监控指定 PID 的线程切换
pidstat -w -t -p 1234 1 5

字段解释:
- cswch/s:自愿上下文切换(I/O阻塞、sleep)
- nvcswch/s:非自愿上下文切换(被抢占)

3)/proc 查看单进程统计#

cat /proc/1234/status | egrep 'voluntary|nonvoluntary'

用于对比自愿与非自愿切换比例,判断是否因抢占导致。

4)调度延迟与切换路径:perf sched#

# 录制调度事件(运行10秒)
sudo perf sched record -- sleep 10

# 查看调度延迟与切换情况
sudo perf sched latency

四、示例:制造上下文切换并观测#

1)CPU 绑定与多线程自旋示例#

编译一个简单多线程自旋程序,制造切换:

cat > /tmp/spin.c <<'EOF'
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* work(void* arg){ while(1) { } return NULL; }
int main(){
    pthread_t t1,t2,t3,t4;
    pthread_create(&t1,NULL,work,NULL);
    pthread_create(&t2,NULL,work,NULL);
    pthread_create(&t3,NULL,work,NULL);
    pthread_create(&t4,NULL,work,NULL);
    pause();
    return 0;
}
EOF

gcc -O2 -pthread /tmp/spin.c -o /tmp/spin
/tmp/spin &
echo $! > /tmp/spin.pid

观察:

pidstat -w -t -p $(cat /tmp/spin.pid) 1 5
vmstat 1 5

预期:nvcswch/s 上升明显,cs 升高。

2)I/O 阻塞示例(自愿切换)#

# 产生I/O读写阻塞
dd if=/dev/zero of=/tmp/io.test bs=1M count=1024 oflag=direct &
pidstat -w -p $! 1 5

预期:cswch/s(自愿切换)明显升高。

五、排错与诊断清单#

  • 负载高但 CPU 利用率不高
    排查:vmstatcs 是否异常、pidstat -w 找到切换最高进程。
  • 应用响应变慢
    排查:perf sched latency 是否存在调度延迟峰值。
  • 抢占过多
    排查:nvcswch/s 高,考虑线程过多/优先级不合理。

快速定位命令组合:

# 1) 找到切换最高的进程
pidstat -w 1 5 | sort -k6 -nr | head

# 2) 查看进程线程级别的切换
pidstat -w -t -p 1234 1 5

# 3) 对比线程数量
ps -L -p 1234 | wc -l

六、优化与验证#

  • 控制线程数,使用线程池,避免“过度并发”。
  • 合理拆分任务粒度,减少高频同步。
  • 绑定 CPU 亲和性减少跨核迁移:
# 将进程绑定到CPU0
taskset -cp 0 1234

# 启动时绑定
taskset -c 0 /tmp/spin

验证优化效果:
- vmstatcs 是否下降
- pidstat -wnvcswch/s 是否降低

七、练习#

  1. 启动 2、4、8 个线程版本的 spin 程序,记录 csnvcswch/s 的变化趋势。
  2. 使用 taskset 将程序绑定单核与多核对比切换率。
  3. 设计一个“高频系统调用”脚本(如频繁 stat),观察用户/内核切换相关指标变化。