linux是一个多任务操作系统,它支持远大于cpu核心数的任务同时运行,当然,这些任务实际上并不是真正同时运行,操作系统在很短时间内,将cpu资源轮流分配给这些任务,造成多任务同时运行的错觉。每个任务运行前,cpu需要知道从哪里加载,从哪里开始运行,需要系统事先设置好cpu寄存器和程序计数器,这些都是cpu在运行任务必须依赖的环境,这些也称为cpu上下文。上下文切换,先把前一个任务的cpu上下文保存起来,然后加载新任务的上下文,运行新任务,这称为上下文切换。
cpu上下文
根据任务不同,cpu上下文切换分为以下几个:
进程上下文切换:
- linux按照特权等级,把进程运行空间分为内核空间和用户空间,cpu特权等级分为ring0-ring3四个环,ring0是操心系统内核空间,具有最高权限,可以访问所有资源,ring3是用户空间,只能访问受限资源,不能直接访问内存等硬件,必须通过系统调用陷入内核,由内核访问特权资源
- 进程可以在用户空间运行,称为进程的用户态,也可以在内核空间运行,称为进程内核态,用户态转换到内核态的转变,需要系统调用完成
- 一次系统调用过程,其实发生两次cpu上下文切换
- 进程上下文切换,是指从一个进程切换到另一个进程。系统调用过程中,一直是同一个进程在运行
线程上下文切换:线程是调度的基本单位,进程是资源拥有的基本单位
- 进程只有一个线程时,可认为进程就等于线程
- 当进程拥有多个线程时,这些线程会共享内存和全局变量等资源,这些资源在上下文切换时是不需要修改的
- 线程也有自己的私有数据,上下文切换时,这些资源需要保存的
- 线程上下文切换分两种情况,一是前后两个线程属于不同进程,此时资源不共享,此时与进程上下文切换是一样的,第二种情况是两个线程属于同一个进程,此时有些资源是共享的,只需要切换线程私有数据,如栈和寄存器
中断上下文切换:
- 中断会打断进程的正常调度和执行,中断处理完,会把打断的进程恢复执行
- 中断上下文并不涉及进程的用户态,中断上下文,只包括内核态内中断服务程序
- 对于同一个cpu,中断比进程有更高优先级,故此,中断上下文与进程上下文切换不会同时发生
- 中断上下文切换也需要消耗cpu,切换过多也会降低系统性能,这时候要去排查
上下文切换需要一定时间,这时间很短,但如果上下文切换频繁时,导致cpu将大量时间用在上下文切换上,进而大大缩短真正运行进程时间,这也是导致cpu平均负载升高的一个重要因素
什么时候会发生上下文切换?
- 只有在进程调度的时候,才需要进行上下文切换,linux系统为每个cpu都维护了一个就绪队列,将活跃进程(正在运行和正在等待运行的进程)按优先级和等待cpu的时间排序,按调度算法,选择优先级高的进程来运行
cpu调度
- 为保证进程调度,cpu时间被划分为一段段时间片,时间片分配给进程,当一个进程时间片耗尽了,就会被系统挂起,切换到其他等待的cpu的进程运行
- 进程在系统资源不足时,要等资源满足后才可以运行,此时进程也会挂起,系统调度其他进程运行
- 进程通过sleep函数将自己主动挂起时,自然也会重新调度
- 当有更高优先级的进程需要运行时,当前进程也会被挂起,运行高优先级进程
- 发生硬件中断,进程会被中断挂起,转而执行中断程序
由上面可以看出,,虽然同为上下文切换,同进程内的线程切换,要比多进程的切换消耗跟少的资源,这也正是多线程代替多进程的一个优势
如何查看系统上下文切换情况
vmstat命令是常用的工具,命令结果如下:
# vmstat 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 360340 14456 1251768 0 0 24 35 70 97 6 2 92 0 0
0 0 0 360340 14456 1251804 0 0 0 0 96 165 0 0 100 0 0
0 0 0 360276 14456 1251804 0 0 0 63 94 155 0 0 100 0 0
cs: 每秒上下文切换次数
in:每秒中断次数
r:就绪队列长度,就是正在运行和等待cpu进程数
b:处于不可中断睡眠状态的进程数
vmstat只给出系统总体上下文切换情况,要看每个进程情况,要使用pidstat
# pidstat -w 5
08:30:36 PM UID PID cswch/s nvcswch/s Command
08:30:41 PM 0 8 12.75 0.00 rcu_sched
08:30:41 PM 0 11 0.20 0.00 watchdog/0
08:30:41 PM 0 14 0.20 0.00 watchdog/1
08:30:41 PM 0 321 0.20 0.00 kworker/0:1H
08:30:41 PM 0 459 17.73 0.00 xfsaild/sda4
cswch/s:每秒自愿上下文切换次数
nvcswch/s:每秒非自愿上下文切换次数
自愿上下文切换:进程无法获取所需自愿,导致的上下文切换,如:i/o,内存等系统资源不足,会发生自愿上下文切换
非自愿上下文切换:由于进程时间片已到,被系统强制调度,进而发生上下文切换,如大量进程争抢cpu,就容易发生非自愿上下文切换
案例分析
使用sysbench模拟多线程切换调度情况,使用vmstat和pidstat工具进行分析,开启三个终端
第一个终端上:
# sysbench --threads=10 --max-time=300 threads run
第二个终端上:
# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
8 0 0 350592 14456 1253500 0 0 22 34 72 798 6 2 92 0 0
7 0 0 350592 14456 1253500 0 0 0 0 5701 1538551 11 89 0 0 0
7 0 0 350592 14456 1253500 0 0 0 0 5674 1619777 15 86 0 0 0
7 0 0 350592 14456 1253500 0 0 0 0 5197 1563957 14 86 0 0 0
7 0 0 350592 14456 1253500 0 0 0 0 4767 1600961 14 86 0 0 0
从终端2可以看出,cs(上下文切换)上升到153万,r(就绪队列)达到7,远超cpu个数2,所以会有大量cpu竞争,us和sy加起来达到100%,系统(sy)cpu占用率达到86%,说明cpu主要被系统(内核)占用,中断次数(in)达到5701,综上所述,系统的就绪队列过长,达到7,远大于cpu个数2,这会导致大量上下文切换,大量切换进而导致cpu系统占用过高,但到底是哪个进程导致的,需要进行下一步分析
中断次数达到5700,要查看中断情况,需要查看/proc/interrupts,如下所示,变化最快的是RES,达到230多万,这中断类型是重调度中断,这种类型表示,唤醒空闲状态cpu来调度新任务运行,在多处理器系统中,调度器用来分散任务到不同cpu机制,通常也被称为处理器中断
查看中断情况:
# cat /proc/interrupts
.... CPU0 CPU1
NMI: 0 0 Non-maskable interrupts
LOC: 1490665 1283555 Local timer interrupts
SPU: 0 0 Spurious interrupts
PMI: 0 0 Performance monitoring interrupts
IWI: 0 0 IRQ work interrupts
RTR: 0 0 APIC ICR read retries
RES: 2306244 2366613 Rescheduling interrupts
CAL: 3081 3575 Function call interrupts
TLB: 161 241 TLB shootdowns
TRM: 0 0 Thermal event interrupts
THR: 0 0 Threshold APIC interrupts
DFR: 0 0 Deferred Error APIC interrupts
MCE: 0 0 Machine check exceptions
MCP: 71 72 Machine check polls
HYP: 0 0 Hypervisor callback interrupts
ERR: 0
MIS: 0
PIN: 0 0 Posted-interrupt notification event
NPI: 0 0 Nested posted-interrupt event
PIW: 0 0 Posted-interrupt wakeup event
在第三个终端上,使用pidstat观察
# pidstat -w -u 1
08:45:20 PM UID PID %usr %system %guest %wait %CPU CPU Command
08:45:21 PM 0 27328 0.00 1.00 0.00 0.00 1.00 0 kworker/u256:1
08:45:21 PM 0 27491 0.00 1.00 0.00 0.00 1.00 1 sshd
08:45:21 PM 0 27761 28.00 100.00 0.00 0.00 100.00 1 sysbench
08:45:21 PM 0 27793 0.00 1.00 0.00 3.00 1.00 1 pidstat
08:45:20 PM UID PID cswch/s nvcswch/s Command
08:45:21 PM 0 8 19.00 0.00 rcu_sched
08:45:21 PM 0 16 2.00 0.00 ksoftirqd/1
08:45:21 PM 0 459 18.00 0.00 xfsaild/sda4
08:45:21 PM 0 983 10.00 0.00 vmtoolsd
08:45:21 PM 115 1408 1.00 0.00 snmpd
08:45:21 PM 0 27328 242.00 0.00 kworker/u256:1
08:45:21 PM 0 27400 6.00 0.00 kworker/0:0
08:45:21 PM 0 27491 128.00 1.00 sshd
08:45:21 PM 0 27772 3.00 0.00 kworker/1:0
08:45:21 PM 0 27793 1.00 237.00 pidstat
从pidstat输出可以看出,sysbench的cpu系统占用率(%system)达到100%,上下文切换来自其他进程,非自愿上下文切换(nvcswch)最高的是pidstat,达到237,自愿上下文切换(cswch)最高的是kworker和sshd
pidstat默认显示进程指标数据,若要显示线程数据,要使用-t选项,所以第三个终端命令建议使用:
# pidstat -wt -u 1
上下文切换啥情况才算正常
- 主要取决于系统本身的cpu性能,如果系统上下文切换次数稳定,从几百到一万以内,但如果次数超过一万,或者次数出现数量级增长,就可能出现性能问题
- 需要根据上下文切换类型,做具体分析
- 自愿上下文切换变多,说明进程在等待资源,可能发生io问题
- 非自愿上下文切换变多,说明进程被强制调度,也就是在争cpu,说明cpu成为瓶颈
- 中断次数变多,说明cpu被中断程序占用,还需要查看/proc/interrups文件来分析中断类型