目录
一、CPU上下文概念和种类
Linux是一个多任务操作系统,可以支持远大于CPU数量的任务,但是任务并非真正的在同时运行,而是CPU快速切换到不同的任务进行执行,造成一种多任务同时执行的错觉。而在CPU切换到其他任务执行之前,为了确保在切换任务之后还能够继续切换回原来的任务继续执行,并且看起来是一种连续的状态,就必须将任务的状态保持起来,以便恢复原始任务时能够继续之前的状态执行。
那么状态保存的位置位于CPU寄存器和程序计数器(PC寄存器)中。CPU寄存器内存小,但是速度极快,保存程序的堆栈(数据段信息),程序计数器保存程序的下一条指令的位置(代码段信息)。
因此,CPU上下文就是指CPU寄存器和程序计数器中保存的任务状态信息;CPU上下文切换就是把前一个任务的CPU上下文保存起来,然后加载下一个任务的上下文到这些寄存器和程序计数器,再跳转到程序计数器所指示的位置运行程序。CPU上下文种类切换包括:
- 进程上下文切换
- 线程上下文切换
- 中断上下文切换
二、进程上下文切换
1、进程上下文切换:
过程如下:
进程可运行于用户态和内核态,因此进程上下文保存的信息主要包括:虚拟内存、堆栈、全局变量等用户空间资源,内核堆栈、寄存器等内核空间资源。
2、这里需要相关的介绍一下系统调用以及跟进程上下文切换的区别。
前面说到进程可在用户态和内核态运行,用户态和内核态是属于CPU不同的特权等级,内核态拥有最高的特权等级,可以访问任何资源,用户态只能访问有限的资源,那么进程从用户态陷入内核态就是系统调用,比如查看文件的过程:先open打开文件,然后read读取内容,再wirte到终端,最后close关闭文件,这些接口都是属于系统调用。
系统调用之前,用户态的指令位置需要先在CPU寄存器中保存起来,然后更新为内核态的指令位置,最后才是跳转到内核态运行。系统调用之后,CPU寄存器需要恢复之前保存的用户态,然后再切换到用户控件,因此,系统调用涉及到两次CPU上下文切换。那么系统调用和进程切换的区别是:
- 进程切换是在不同的进程间切换,而系统调用是在同一进程内切换,只是在不同的CPU特权等级之间转换。
- 进程切换比系统调用多了一步,在保存当前进程的内核状态和CPU寄存器之前,进程切换还需要保存虚拟内存和堆栈。
3、进程切换对系统性能的影响
每一次进程切换都需要几十纳秒到数微妙的时间,这个时间是比较可观的,如果大量的进程进程切换,回导致大量CPU消耗在寄存器、内核堆栈、虚拟内存的保存和恢复上,进而缩短了进程运行的时间,导致系统负载升高。
4、进程何时被调度
- 进程被分配了一定的时间片,当时间片耗尽,CPU切换到其他的进程运行。
- 进程资源不足,比如内存不足,网络请求结果返回等待、文件读写等待等情况,要等到资源满足时才能继续运行,这时进程会被挂起,CPU切换到其他进程。
- 进程通过sleep这种方式主动挂起,让CPU重新调度。
- 优先级更高的进程需要运行,当前进程被挂起。
- 发生硬件中断时,CPU上的进程也会被挂起,转而执行中断服务程序。
三、线程上下文切换
对于线程上下文切换的理解主要以下几点:
- 进程是系统资源分配的基本单位,线程是系统任务调度的基本单位,因此,所谓的内核任务调度,实际就是对线程的调度。
- 当进程只有一个线程时,可以认为线程等于进程。
- 当进程有多个线程时,这些线程会共享虚拟内存和全局变量等资源,这些资源在上下文切换时不需要修改。
线程上下文切换的情况:
- 前后两个线程属于不同的进程,这时线程上下文切换等同于进程上下文切换。
- 前后两个线程属于同一个进程,这时线程上下文切换不需要改变虚拟内存,只切换线程的私有数据(局部变量)、寄存器等不共享的数据。
同一进程内的线程切换,比多进程之间的线程切换消耗更多的资源(需要改变虚拟内存、全局变量),因此这也是用多线程替代多进程的原因。
四、中断上下文切换
- 中断上下文切换是为了快速响应硬件事件,通过中断处理打断进程的正常调度并执行中断服务程序。
- 中断上下文切换可以打断用户态的进程,但是不需要保存用户态资源(虚拟内存、全局变量),只需要保存内核态资源(CPU寄存器、内核堆栈、硬件中断参数)。
- 中断上下切换比进程上下文切换具有更高的优先级。由于中断处理会打断正常的进程调度,因此中断服务处理程序都应该短小精悍。
五、系统中断上下文切换分析工具
1、vmstat是一个系统性能分析工具,常用来分析CPU上下文切换次数和中断的次数。
常用用法:vmstat 2 #每隔2秒打印一次系统性能中指标。
root@ubuntu:~# vmstat 2
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 10692 627092 306340 1478872 0 0 0 1 1 3 3 2 94 0 0
主要关注指标:
- cs(context switch):每秒的上下文切换次数
- in(interrupt):每秒中断次数
- r(running or runnable):就绪队列长度,包括正在运行CPU和等待CPU运行的进程数。
- b(Blocked):不可中断睡眠状态的进程数。
2、pidstat是一个查看进程性能的分析工具。
常用用法:pidstat -wt -u 1 #w输出进程切换指标,t输出线程切换指标,u输出CPU使用指标。
root@ubuntu:~# pidstat -wt -u 1
Linux 4.4.0-31-generic (ubuntu) 01/06/2019 _x86_64_ (4 CPU)
11:10:01 PM UID TGID TID %usr %system %guest %CPU CPU Command
11:10:02 PM 0 7 - 0.00 1.52 0.00 1.52 0 rcu_sched
11:10:02 PM 0 - 7 0.00 1.52 0.00 1.52 0 |__rcu_sched
11:10:01 PM UID TGID TID cswch/s nvcswch/s Command
11:10:02 PM 0 7 - 50.76 0.00 rcu_sched
11:10:02 PM 0 - 7 50.76 0.00 |__rcu_sched
其中有两列需要重点关注:
- cswch(voluntary context switches):自愿上下文切换,是指主动式的导致切换,比如IO操作引起的资源等待。
- nswch(none voluntary context switches):非自愿上下文切换,是指被动式的导致切换,比如时间片到,被系统强制调度,一般大量进程竞争CPU时会导致nswch。
六、案例分析
1、第一个终端通过sysbench工具模拟多线程切换。
第一个终端输入:sysbench --num-threads=10 --max-time=3000 --test=threads run
参数说明:
--num-threads:启动线程数
--max-time:运行时间(秒)
--test:测试集,选择threads,测试线程
2、第二个终端通过vmstat查看系统性能
lsm@ubuntu:~$ 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
0 0 10692 608844 306480 1483768 0 0 0 0 200 279 1 1 98 0 0
7 0 10692 608624 306480 1483768 0 0 0 0 1895 288375 5 35 60 0 0
10 0 10692 608268 306480 1483768 0 0 0 0 7428 1638533 9 76 14 0 0
观察发现:
- cs从279飙升到1638533。
- r队列:升到10,远超过CPU个数4(grep 'model name' /proc/cpuinfo | wc -l查看),已经开始出现CPU竞争。
- us和sy:升到%85,其中内核CPU高达%76,说明CPU主要被内核占用。
- in上升到7428,说明中断处理也是问题。
可见,正是r队列过长,导致大量CPU竞争,引起大量线程上下文切换,而线程上下文切换主要消耗内核态资源,因此导致致内核CPU使用率过高。
3、第三个终端通过pidstat查看具体进程
lsm@ubuntu:~$ pidstat -wtu 1
Linux 4.4.0-31-generic (ubuntu) 01/07/2019 _x86_64_ (4 CPU)
12:40:05 AM UID TGID TID %usr %system %guest %CPU CPU Command
12:40:06 AM 0 53793 - 29.00 303.00 0.00 332.00 2 sysbench
12:40:06 AM 0 - 53794 3.00 30.00 0.00 33.00 0 |__sysbench
12:40:06 AM 0 - 53795 5.00 29.00 0.00 34.00 0 |__sysbench
12:40:06 AM 0 - 53796 3.00 31.00 0.00 34.00 3 |__sysbench
12:40:06 AM 0 - 53797 2.00 30.00 0.00 32.00 3 |__sysbench
12:40:06 AM 0 - 53798 1.00 33.00 0.00 34.00 0 |__sysbench
12:40:06 AM 0 - 53799 2.00 28.00 0.00 30.00 2 |__sysbench
12:40:06 AM 0 - 53800 3.00 30.00 0.00 33.00 3 |__sysbench
12:40:05 AM UID TGID TID cswch/s nvcswch/s Command
12:40:06 AM 1000 - 53790 1.00 6.00 |__pidstat
12:40:06 AM 0 - 53794 33029.00 119302.00 |__sysbench
12:40:06 AM 0 - 53795 29024.00 144719.00 |__sysbench
12:40:06 AM 0 - 53796 22800.00 109972.00 |__sysbench
12:40:06 AM 0 - 53797 25421.00 123484.00 |__sysbench
12:40:06 AM 0 - 53798 34135.00 139033.00 |__sysbench
12:40:06 AM 0 - 53799 31236.00 125567.00 |__sysbench
12:40:06 AM 0 - 53800 36826.00 115408.00 |__sysbench
可见,CPU主要是被sysbench占用,各个子线程加起来达到%332,而nvcswch远大于cswch,是因为所创建的大量线程主要是CPU密集型任务,基于时间片原理而产生调度,属于主动切换,并非IO问题的被动切换。
4、查看中断发生类型
通过查看/proc/interrupts这个文件内容,proc是虚拟文件系统,保存了很多内核信息,其中interrupts提供了中断使用情况。
root@ubuntu:~# watch -d cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
...
RES: 108297505 105334518 107312897 104255732 Rescheduling interrupts
...
观察一段时间,发现变化最快的是重调度中断(RES),这个中断表示,唤醒空闲的CPU来调度新的任务执行。所以这种中断数量升高也是因为任务过多。
七、切换分析思路总结:
1、发现是否出现过多切换问题:如果切换次数比较稳定,那么在数百到一万以内一般都是正常;如果上下文切换超过了一万,或者呈现爆发式增长,就意味着可能出现性能问题。
2、存在切换过多问题时具体分析属于哪一种情况:
- cswch变多,说明任务都在等待资源,一般是IO问题。
- ncswch变多,说明任务由于时间片到了而在被强制调度,一方面说明任务过多,也说明CPU性能有瓶颈。
- 中断次数变多,说明系统被中断处理程序占用,需要查看/proc/interrupts文件中的变化来具体分析是哪一种中断增多。