一、CPU利用率和负载率的区别
先来一张 top 命令图。
这里要区别 CPU 负载(Load)和 CPU 利用率,它们是不同的两个概念,但它们的信息可以在同一个 top 命令中进行显示。
什么是负载?
简单来说,负载(Load)指标表示一段时间内,系统有多少个运行的任务,也就是 CPU 使用队列的长度统计信息,这个数字越小越好。任务的统计包含:
- 正在 CPU 上面运行的任务数量
- 等待 CPU 调度运行的任务数量
- 等待 I/O 执行结果的任务数量
根据工程经验,一般来说,当系统的 Load 指标值大于系统中 CPU 数量的 1.5~2 倍时,表明系统负载较重,需要分析原因,采取措施降低 Load 指标值。否则就可能会产生系统卡顿的现象。如果系统的 Load 指标值大于系统中 CPU 数量的 3 倍时,表明系统负载非常严重了。
总结来看负载指标统计主要分为两大部分:CPU 负载、IO 负载
例如,假设有一个进行大规模科学计算的程序,虽然该程序不会频繁地从磁盘输入输出,但是处理完成需要相当长的时间。因为该程序主要被用来做计算、逻辑判断等处理,所以程序的处理速度主要依赖于 CPU 的计算速度。此类 CPU 负载的程序称为“计算密集型程序”。
还有一类程序,主要从磁盘保存的大量数据中搜索找出任意文件。这个搜索程序的处理速度并不依赖于 CPU,而是依赖于磁盘的读取速度,也就是输入输出(input/output,I/O)。磁盘越快,检索花费的时间就越短。此类 I/O 负载的程序,称为“I/O 密集型程序”。
什么是利用率?
CPU 利用率显示的是程序在运行期间实时占用的 CPU 百分比,这是对一个时间段内 CPU 使用状况的统计,通过这个指标可以看出在某一个时间段内 CPU 被占用的情况,如果被占用时间很高,那么就需要考虑 CPU 是否已经处于超负荷运作。
CPU 利用率高并不意味着负载就一定大,可能这个任务是一个 CPU 密集型的。一样 CPU 低利用率的情况下是否会有高负载的情况产生呢?理解了 CPU 占有时间和使用时间就可以知道。
当 CPU 分配时间片给某任务以后,是否使用完全取决于使用者,有的任务是 CPU 密集型,但有的任务是 I/O 密集型。因此完全可能出现低利用率高负载的情况。另外 I/O 设备也可能导致 CPU 负载高,这个跟每个系统的计算负载的方式不同而不同。
由此来看,仅仅从 CPU 的使用率来判断 CPU 是否处于一种超负荷的工作状态还是不够的,必须结合负载来全局的看 CPU 的使用情况。
网上有个例子来说明两者的区别如下:某公用电话亭,有一个人在打电话,四个人在等待,每人限定使用电话一分钟,若有人一分钟之内没有打完电话,只能挂掉电话去排队,等待下一轮。电话在这里就相当于 CPU,而正在或等待打电话的人就相当于任务数。在电话亭使用过程中,肯定会有人打完电话走掉,有人没有打完电话而选择重新排队,更会有新增的人在这儿排队,这个人数的变化就相当于任务数的增减。为了统计平均负载情况,我们5秒钟统计一次人数,并在第 1、5、15 分钟的时候对统计情况取平均值,从而形成第 1、5、15 分钟的平均负载。有的人拿起电话就打,一直打完 1 分钟,而有的人可能前三十秒在找电话号码,或者在犹豫要不要打,后三十秒才真正在打电话。如果把电话看作 CPU,人数看作任务,我们就说前一个人(任务)的 CPU 利用率高,后一个人(任务)的 CPU 利用率低。当然, CPU 并不会在前三十秒工作,后三十秒歇着,CPU 是一直在工作。只是说,有的程序涉及到大量的计算,所以 CPU 利用率就高,而有的程序牵涉到计算的部分很少,CPU 利用率自然就低。但无论 CPU 的利用率是高是低,跟后面有多少任务在排队没有必然关系。
CPU 数量和 CPU 核心数(即内核数)都会影响到 CPU 负载,因为任务最终是要分配到 CPU 核心去处理的。两块 CPU 要比一块 CPU 好,双核要比单核好。因此,我们需要记住,除去 CPU 性能上的差异,CPU 负载是基于内核数来计算的,即“有多少内核,即有多少负载”,如单核最好不要超过 100%,也就是负载为 1.00,如此类推。
Linux 里有一个 /proc 目录,存放的是当前运行系统的虚拟映射,其中有一个文件为 cpuinfo,这个文件里存放着 CPU 的信息。/proc/cpuinfo 文件按逻辑 CPU 而非真实 CPU 分段落显示信息,每个逻辑 CPU 的信息占用一个段落,第一个逻辑 CPU 标识从 0 开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
$ cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 63 model name : Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz stepping : 2 microcode : 0x36 cpu MHz : 2399.998 cache size : 20480 KB physical id : 0 siblings : 2 core id : 0 cpu cores : 2 apicid : 0 initial apicid : 0 fpu : yes fpu_exception : yes cpuid level : 15 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr ...... bogomips : 4799.99 clflush size : 64 cache_alignment : 64 address sizes : 42 bits physical, 48 bits virtual power management: |
要理解该文件中的 CPU 信息,有几个相关的概念要知道,如:processor 表示逻辑 CPU 的标识、model name 表示真实 CPU 的型号信息、physical id 表示真实 CPU 和标识、cpu cores 表示真实 CPU 的内核数等等。
逻辑 CPU 的描述:现在的服务器一般都使用了“超线程”(Hyper-Threading,简称HT)技术来提高 CPU 的性能。超线程技术是在一颗 CPU 同时执行多个程序而共同分享一颗 CPU 内的资源,理论上要像两颗 CPU 一样在同一时间执行两个线程。虽然采用超线程技术能同时执行两个线程,但它并不象两个真正的 CPU 那样,每个 CPU 都具有独立的资源。当两个线程都同时需要某一个资源时,其中一个要暂时停止,并让出资源,直到这些资源闲置后才能继续。因此超线程的性能并不等于两颗 CPU 的性能。具有超线程技术的 CPU 还有一些其它方面的限制。
二、进程调度
进程调度也被一些人称为 CPU 上下文切换意思是:CPU 切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪(或者挂起、中断)状态,另一个被选定的就绪任务成为当前任务。进程调度包括保存当前任务的运行环境,恢复将要运行任务的运行环境。
在 Linux 内核中,每一个进程都存在一个名为“进程描述符”的管理表。该进程描述符会调整为按照优先级降序排序,已按合理的顺序运行进程(任务)。这个调整即为进程调度器的工作。
调度器划分并管理进程的状态,如:
- 等待分配 CPU 资源的状态。
- 等待磁盘输入输出完毕的状态。
下面再说一下进程的状态区别:
状态 | 说明 |
---|---|
运行态(running) | 只要cpu空闲,任何时候都可以运行 |
可中断睡眠(interruptible) | 为恢复时间无法预测的长时间等待状态。如,来自于键盘设备的输入。 |
不可中断睡眠:(uninterruptible) | 主要为短时间时的等待状态。例如磁盘输入输出等待。被IO阻塞的进程 |
就绪态(runnable) | 响应暂停信号而运行的中断状态。 |
僵死态(zombie) | 进程都是由父进程创建,并销毁;在父进程没有销毁其子进程,被销毁的时候,其子进程由于没有父进程被销毁,就会转变为僵死态。 |
下面举例来说明进程状态转变:
这里有三个进程 A、B、C 同时运行。首先,每个进程在生成后都是可运行状态,也就是 running 状态的开始,而不是现在运行状态,由于在 Linux 内核中无法区别正在运行的状态和可运行的等待状态,下面将可运行状态和正在运行状态都称为 running 状态。
- 进程A:running
- 进程B:running
- 进程C:running
running 的三个进程立即成为调度对象。此时,假设调度器给进程A分配了 CPU 的运行权限。
- 进程A:running (正在运行)
- 进程B:running
- 进程C:running
进程A分配了 CPU,所以进程A开始处理。进程B和C则在此等待进程A迁出 CPU。假设进程A进行若干计算之后,需要从磁盘读取数据。那么在A发出读取磁盘数据的请求之后,到请求数据到达之前,将不进行任何工作。此状态称为“因等待 I/O 操作结束而被阻塞”。在 I/O 完成处理前,进程A就一直处于等待中,就会转为不可中断睡眠状态(uninterruptible),并不使用 CPU。于是调度器查看进程B和进程C的优先级计算结果,将 CPU 运行权限交给优先级较高的一方。这里假设进程B的优先级高于进程C。
- 进程A:uninterruptible (等待磁盘输入输出/不可中断状态)
- 进程B:running (正在运行)
- 进程C:running
进程B刚开始运行,就需要等待用户的键盘输入。于是B进入等待用户键盘输入状态,同样被阻塞。结果就变成了进程A和进程B都是等待输出,运行进程C。这时进程A和进程B都是等待状态,但是等待磁盘输入输出和等待键盘输入为不同的状态。等待键盘输入是无限期的事件等待,而读取磁盘则是必须短时间内完成的事件等待,这是两种不同的等待状态。各进程状态如下所示:
- 进程A:uninterruptible (等待磁盘输入输出/不可中断状态)
- 进程B:interruptible (等待键盘输入输出/可中断状态)
- 进程C:running (正在运行)
这次假设进程C在运行的过程中,进程A请求的数据从磁盘到达了缓冲装置。紧接着硬盘对内核发起中断信号,内核知道磁盘读取完成,将进程A恢复为可运行状态。
- 进程A:running (正在运行)
- 进程B:interruptible (等待键盘输入输出/可中断状态)
- 进程C:running (正在运行)
此后进程C也会变为某种等待状态。如CPU的占用时间超出了上限、任务结束、进入I/O等待。一旦满足这些条件,调度器就可以完成从进程C到进程A的进程状态切换。
三、CPU负载率的计算方式
Load average 的概念源自 UNIX 系统,虽然各家的公式不尽相同,但都是用于衡量正在使用 CPU 的进程数量和正在等待 CPU 的进程数量,一句话就是 runable processes 的数量。所以Load average 可以作为CPU瓶颈的参考指标,如果大于 CPU 的数量,说明 CPU 可能不够用了。
但是,在 Linux 上有点差异!
Linux 上的 Load average 除了包括正在使用 CPU 的进程数量和正在等待 CPU 的进程数量之外,还包括 uninterruptible sleep 的进程数量。通常等待 IO 设备、等待网络的时候,进程会处于 uninterruptible sleep 状态。Linux 设计者的逻辑是,uninterruptible sleep 应该都是很短暂的,很快就会恢复运行,所以被等同于 runnable。然而 uninterruptible sleep 即使再短暂也是 sleep,何况现实世界中 uninterruptible sleep 未必很短暂,大量的、或长时间的 uninterruptible sleep 通常意味着 IO 设备遇到了瓶颈。
众所周知,sleep 状态的进程是不需要 CPU 的,即使所有的 CPU 都空闲,正在 sleep 的进程也是运行不了的,所以 sleep 进程的数量绝对不适合用作衡量 CPU 负载的指标,Linux 把 uninterruptible sleep 进程算进 load average 的做法直接颠覆了 load average 的本来意义。所以在 Linux 系统上,load average 这个指标基本失去了作用,因为你不知道它代表什么意思,当看到 load average 很高的时候,你不知道是 runnable 进程太多还是 uninterruptible sleep 进程太多,也就无法判断是 CPU 不够用还是 IO 设备有瓶颈。
从另一个方面来说,也就可以解释为什么磁盘慢时(大量磁盘使用时),CPU 负载会飙高了。基本上我碰到 CPU 负载高的情况就两种情况:CPU 本身处理太多任务,再加上软中断和上下文切换太频繁导致负载高;再就是磁盘太慢导致了不可中断睡眠太多导致 CPU 负载高。
<原理>
https://mp.weixin.qq.com/s/hqSGnAZpaBQrCwdd1P_hWg
https://mp.weixin.qq.com/s/NHB2P4c4bpGMQJQmKDxLSA