前言:Linux是一个多任务的操作系统,可以支持远大于CPU数量的任务同时运行,但是我们都知道这其实是一个错觉,真正是系统在很短的时间内将CPU轮流分配给各个进程,给用户造成多任务同时运行的错觉。所以这就是有一个问题,在每次运行进程之前CPU都需要知道进程从哪里加载、从哪里运行,也就是说需要系统提前帮它设置好CPU寄存器和程序计数器。
CPU上下文其实是一些环境正是有这些环境的支撑,任务得以运行,而这些环境的硬件条件便是CPU寄存器和程序计数器。CPU寄存器是CPU内置的容量非常小但是速度极快的存储设备,程序计数器则是CPU在运行任何任务时必要的,里面记录了当前运行任务的行数等信息,这就是CPU上下文。
img
根据任务的不同,CPU的上下文切换就可以分为进程上下文切换、线程上下文切换、中断上下文切换 ,进程上下文切换。
在Linux中,Linux按照特权等级,将进程的运行空间分为内核空间和用户空间:
对于一个进程来说,一般是运行在用户态的,但是当需要访问内存、磁盘等硬件设备的时候需要陷入到内核态中,也就是要从用户态到内核态的转变,而这种转变需要通过系统调用来实现,例如一个打开文件的操作,需要调用open()打开文件,read()读取文件内容,write()将文件内容输出到控制台,最后close()关闭文件,这就是系统调用
在系统调用的过程中同样发发生了CPU上下文切换:
系统调用结束后,CPU寄存器需要恢复原来保存的用户态,然后切换为用户空间,所以一次系统调用的过程,会发生两次的CPU上下文切换但是我们一般说系统调用是特权模式切换而不是上下文切换,因为这里没有涉及到虚拟内存等这些进程用户态的资源,也不会切换进程是属于进程之内的上下文切换,进程是由内核来管理和调度的,进程的切换只能发生在内核态,所以进程的上下文包含了虚拟内存、栈、全局变量等用户空间的资源,还包含了内核堆栈、寄存器等内核空间的状态,所以进程的上下文切换要比系统调用更多一步,保存该进程的虚拟内存、栈等用户空间的资源,进程上下文切换一般需要几十纳秒到数微秒的CPU时间,当进程上下文切换次数比较多的情况下爱,将导致CPU将大量的时间耗费在寄存器、内核栈即虚拟内存等资源的保存和恢复上,另外,Linux通过TLB快表来管理虚拟内存到物理内存的映射关系,当虚拟内存更新之后,需要刷新缓存,在这多处理系统上是很复杂的,因为多个处理器共享一个缓存。
下面再来说说什么时候会进行进程的上下文切换,其实就是进程在被调度的时候需要切换上下文,可能是主动地,也有可能是被动的
线程是调度的基本单位,而进程则是资源拥有的基本单位,也就是说对于内核中的任务调度是以线程为单位,但是进程只是给线程提供了虚拟内存、全局变量等资源,进程与线程之间的区别这里不再介绍 那么线程上下文的切换,其实分为两种情况:
所以同进程内的线程切换要比多进程内的线程切换消耗更少的资源
中断是为了快速响应硬件的事件,简单来shu就是计算机停下当前的事情,去处理其他的事情,然后在回来继续执行之前的任务,例如我们在调用print函数的时候,其实汇编的底层会帮我们调用一条 int 0x80的指令,便是调用0x80号中断 当然,中断要先将当前进程的状态保存下来,这样中断结束后进程仍然可以从原来的状态恢复运行,中断上下文的切换并不涉及进程的用户态,所以当中断程序打断了正在处于用户态的进程,不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源,只需要保存和恢复这个进程的内核态中的资源包括CPU寄存器、内核堆栈等 对于同一个CPU来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生,一般来说中断程序都执行比较快短小精悍,以便快速结束执行之前的任务。当中断上下文切换次数比较多的时候,会耗费大量的CPU 怎么查看系统上下文 上面已经介绍到CPU上下文切换分为进程上下文切换、线程上下文切换、中断上下文切换,那么过多的上下文切换会把CPU的时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,缩短进程真正运行的时间,成为系统性能大幅下降的一个因素 所以我们可以使用vmstat这个工具来查询系统的上下文切换情况,vmstat是一个常用的系统性能分析工具,可以用来分析CPU上下文切换和中断的次数
需要特别关注的是:
vmstat是给出整个系统总体的上下文切换情况,要想查看每个进程的详细情况就需要使用pidstat,加上-w选项就可以查看进程上下文切换的情况
img
需要特别关注的是:
这两个概念的分别含义:
实战分析 通过上面的工具已经可以初步查看到系统上下文切换的次数,但是当系统上下文切换的次数为多少时是不正常的呢? 案例使用sysbench工具来模拟多线程调度切换的情况,sysbench是一个多线程的基准测试工具,可以模拟上下文切换过多的问题 首先在第一个终端运行stsbench,模拟多线程切换问题 # 以 10 个线程运行 5 分钟的基准测试,模拟多线程切换的问题 sysbench --threads=10 --max-time=300 threads run 然后在第二个终端运行vmstat,每1秒查看上下文切换的情况
img
可以观察到如下指标:
那我们接着使用pidstat来查看是那一个进程出现了问题,由于pidstat默认是显示进程的指标数据,但是我们使用sysbench模拟的线程的数据,所以需要加上-t选项 gpw@gopuwe:~$ pidstat -wt
img
所以到这里可以分析出是sysbench的子线程的上下文切换次数有很多 还有一个问题,在使用vmstat的时候,发现in指标的数据也比较多,那么我们需要找出是什么类型的中断导致了中断上升,中断肯定是发生在内核态,但是pidstat只是一个进程的性能分析工具,并不提供任何关于中断的详细信息 我们可以从/proc/interrupts这个只读文件中读取,/proc是一个虚拟文件系统,用于内核空间和用户空间之间的通信,/proc/interrupts则提供了一个只读的中断使用情况,可以使用cat命令查看/proc/interrupts可以发现变化速度最快的是重调度中断RES,这个中断类型表示唤醒空闲状态的CPU来调度新的任务运行,也被成为处理器中断 那么到底上下文切换的次数为多少合适呢? 这个数值其实取决于系统本身的 CPU 性能,在我看来,如果系统的上下文切换次数比较稳 定,那么从数百到一万以内,都应该算是正常的。但当上下文切换次数超过一万次,或者切 换次数出现数量级的增长时,就很可能已经出现了性能问题,这个时候还要根据上下文切换的类型,做具体的分析,例如: