Linux的性能

Linux的性能

一、什么是平均负载

当我们使用top或者uptime命令时,会观察到这样的值
top命令查看平均负载
uptime命令查看平均负载

load average:就是指单位时间内,系统中处于可运行状态和不可中端状态的平均进场数
那什么是可运行状态和不可中端状态呢?
可运行状态:是正在使用CPU或者正在等待CPU的进程,即使用top命令和ps aux命令下 S(STAT)处于R状态的进程
不可中端状态的进程:处于内核态关键流程中的进程,且不可被打断,比如等待硬件设备IO响应。即使用top命令和ps aux命令下 S(STAT)处于D状态的进程
进程状态STAT

load average(平均负载) 后面三个值就是表示最近1分钟、5分钟、15分钟的平均负载。

那么这个平均负载多大最好呢?
每个CPU上都有一个活跃进程,平均负载数等于CPU核心数
什么时候是过载状态,需要告警?
当平均负载高于CPU核心数70%的时候。

如何查看CPU核心数:lscpu命令下的CPU(s),或者使用 grep 'model name' /proc/cpuinfo | wc -l命令。

二、什么是上下文切换

当多个进程争夺CPU的时候,运行的也还是只有一个CPU,其他进程并没有真正的运行,为什么还是会导致负载上升呢。 这就要说到上下文切换了。

我们都知道Linux是一个多任务操作系统,它支持远大于CPU数量的任务同时进行。 当然,这不是真正的同时运行, 而是在很短的时间内,将CPU轮流分配,形成宏观的同时运行。

在每个任务运行前,CPU需要知道任务要从哪里加载,从哪里开始运行,也就是说,系统在CPU执行任务之前,需要帮它设置好CPU寄存器和程序计数器

CPU寄存器:是CPU内置的容量很小的,但是速度极快的内存。
程序计数器:则是用来存储CPU正在执行的指令位置。
它们都是CPU在运行任何任务之前,必须依赖的东西,所以也被叫做CPU上下文

那么CPU上下文切换也就是把上一个任务的上下文保存起来,然后加载下一个任务的上下文,最后跳到将要执行的位置,运行新任务。

这些保存起来的上下文,会被存储在操作系统内核中,并在任务重新调度执行时再次被加载。

根据任务的不同,CPU的切换会有几个不同的场景: 进程上下文切换、线程上下文切换、中断上下文切换(即中断处理程序的调用)

进程上下文切换

进程在内核中运行时,可以访问所有资源,而在用户空间运行时称为用户态,无法访问全部的资源,当进程陷入内核空间时,被称为内核态。
从用户态到内核态,需要系统调用完成。 比如我们查看文件内容, 打开文件会调用open(),然后调用read()读取文件,再调用write()将内容写到标准输出,最后调用close()关闭文件

那么系统调用过程中有没有发生上下文切换?
CPU寄存器中原来用户态的指令位置,需要保存起来,然后为了执行内核代码,CPU寄存器需要更新为内核态指令的新位置,最后跳转到内核态运行内核任务。
而调用结束后,CPU寄存器需要恢复到原来保存的用户态,然后切换到用户空间,继续运行。
这样一次系统调用就会造成两次CPU上下文切换。

系统调用并不会切换进程,所以通常被称为特权模式切换,而不是上下文切换。但实际上,系统调用中的CPU上下文切换是不能避免的。

那么进程上下文切换和系统调用有什么区别呢。

进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以进程的上下文切换就会比系统调用时多了一步,在保存当前进程的内核状态和CPU寄存器之前,需要先把该进程的虚拟内存、栈保存起来,然后加载下一个进程的内核态后,还需要刷新进程的虚拟内存和栈等资源

保存上下文和恢复上下文的过程并不是免费的,它需要内核在CPU上运行才可以。
每次上下文切换都需要几十纳秒~数微秒的CPU时间,这个时间如果在上下文切换次数较多的情况下,很容易导致CPU将大量时间耗费在寄存器、内核栈以及虚拟内存等资源的保存和恢复上,进而大大缩短了进程的真正运行时间。

什么时候会进行进程上下文切换

究竟什么时候,才会切换进程上下文呢?

只有进程切换时才会切换进程上下文。Linux会为CPU维护一个就绪队列,将正在运行和正在等待CPU的进程按照优先级和等待CPU的时间排序,然后选择最需要CPU的进程,也就是优先级最高和等待CPU时间最长的进程来运行。

什么时候会触发进程调度?

  1. 上一个进程的CPU时间片耗尽,就会被系统挂起,切换到其他的正在等待CPU的进程运行
  2. 进程所需的系统资源不足时,进程也会被挂起,并由系统调度其他进程来运行
  3. 当进程通过函数主动挂起时,自然也要重新调度
  4. 有优先级更高的进程运行时,当前进程也会被挂起
  5. 发生硬件中断时,CPU上的进程会被中断挂起,转而执行内核中的任务

线程上下文切换

线程和进程最大的区别在于线程是调度的基本单位,而进程是资源拥有的基本单位。
内核中的任务调度,说白了调度对象是线程。

对于线程和进程

  • 当进程只有一个线程时,可以认为进程就是线程。
  • 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量,这些公共资源在线程的上下文切换中时不需要修改的。
  • 线程也有自己的私有数据,这些在线程上下文切换中是需要保存的。

所以当线程切换在两个进程之间时, 切换过程就跟进程上下文切换一样。
如果两个线程在一个进程下,那么线程切换只需要切换线程的私有资源。

因此同进程内的线程上下文切换要比多进程之间切换消耗更少的资源,而这也是多线程代替多进程的一个优势。

中断上下文切换

为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。而在打断其他进程时,就需要将进程当前的状态保存起来,这样在中断结束以后才可以从原来的状态恢复运行。

跟进程上下文切换不同的是,中断上下文并不涉及进程的用户态,所以中断过程打断了一个正处在用户态的进程时,也不需要保存和恢复这个进程的用户态资源。
中断上下文,只包含内核态中断服务程序执行所必须的状态,包括CPU寄存器,内核堆栈,硬件中断等。

对同一个CPU,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会于进程上下文切换同时发生。

三、怎么查看上下文的切换情况

既然上下文切换对于系统性能影响很大,那么怎么查看上下文呢?

使用vmstat命令,这是一个常用的系统性能分析工具,主要是原来分析系统的内存使用情况,也能分析CPU上下文切换和中断的次数。

vmstat命令

注意这几个值:

  • cs(context switch): 每秒上下文切换的次数
  • in(interrupt): 每秒中断的次数
  • r(running ro runnable): 就绪队列的长度,也就是正在运行和等待CPU的进程数
  • b(Blocked): 处于不可中断睡眠状态的进程数

vmstat只给出了系统总体的上下文切换情况,想要查看某个进程的详细情况,就需要使用pidstat -w

pidstat查看上下文切换

  • cswch: 每秒自愿上下文切换,所谓自愿上下文切换,就是指进程无法获取所需自愿导致的上下文切换,是进程自主发起的
  • nvcswch: 每秒非自愿上下文切换,非自愿上下文切换,就是指进程由于时间片耗尽或者其他原因,被系统强制调度,进而发生的上下文切换,比如当大量进程争夺CPU资源时,就容易发生非自愿上下文切换