一、平均负载
1.1、什么是平均负载
17:33:53 当前时间
up 57 min,系统运行时间
3 users 正在登录的用户
后面三个分别代表:最近1分钟、5分钟、15分钟平均负载
平均负载:单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数。
可运行状态:正在使用CPU(Running)或正在等待CPU(Runnable)的进程。
不可中断状态:正处于内核态关键流程中的进程,并且这些流程是不可打断的。
平均活跃进程数:单位时间内活跃的进程数,是活跃进程数的指数衰减值。
1.2、平均负载的合理值
查看CPU个数
grep 'model name' /proc/cpuinfo |wc -l
当平均负载大于CPU个数当时候就出现了过载。(平均负载不要高于CPU数量70%)
1.3、平均负载与CPU使用率
平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数。包括正在使用CPU的进程,等待CPU等待I/O的进程。
CPU使用率是单位时间内CPU繁忙情况的统计 。跟平均负载不一定相对应。比如:
- CPU密集型进程,使用大量CPU会导致平均负载升高,此时两者一致。
- I/O密集型进程,等待I/O也会导致平均负载升高,但是CPU 使用率不一定高。
- 大量等待CPU的进程调度也会导致平均负载升高,此时CPU使用率也会比较高。
二、CPU上下文切换
2.1、上下文切换
CPU上下文切换就是先把前一个任务的CPU上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文到寄存器和程序计数器,最后再跳转到程序计数器所指到新位置,运行新任务。
2.2、上下文切换分类
1、进程上下文切换
Linux按照特权等级,把进程的运行空间分为内核空间和用户空间。
- 内核空间:具有最高权限,可以直接访问各种资源;
- 用户空间:只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。
从用户态切换到内核态是通过系统调用来玩的。比如,当我们查看文件的过程:
1)调用open()打开文件;
2)调用read()读取文件内容;
3)调用write()将文件写到标准输出;
4)调用close()关闭文件;
CPU寄存器里原来用户态的指令位置,需要先保存起来。然后CPU寄存器更新为内核态指令的新位置,最后跳转到内核态运行内核任务。系统调用结束后,CPU寄存器恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,发生了两次CPU上下文切换。
系统调用的过程不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。
- 进程上下文切换是指从一个进程切换到另一个进程;
- 系统调用过程一直在同一个进程中进行;
系统调用过程称为特权切换,而不是上下文切换。
进程上下文切换跟系统调用的区别?
首先,进程由内核管理和调度的,进程的切换只发生在内核态。所以,进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间资源,还包含内核堆栈、寄存器等内核空间资源。
因此,进程上下文切换比系统调用多了一步,在保存当前进程的内核状态和CPU寄存器之前,需要先把该进程的虚拟内存、栈等先保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。
如下图所示:保存上下文和恢复上下文并不是免费的,需要内核在CPU上运行才能完成。
什么时候会发生上下文切换?
进程切换的时候才会发生上下文切换。Linux为每个CPU维护了一个就绪队列,将活跃进程(即正在运行或正在等待CPU的进程)按照优先级和等待CPU的时间排序,然后选择最需要CPU的进程,也就是优先级最高和等待时间最长的进程来运行。
进程什么时候会调度到CPU上运行?
- 为了保证所有CPU都可以公平调度,CPU时间被划分为一段段的时间片,这些时间片在轮流分配给各个进程。这样,当某个进程当时间片耗尽了,就会被系统挂起,切换到其他正在等待CPU的进程运行;
- 进程在系统资源不足(如:内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行;
- 当进程通过睡眠函数sleep()这样的方法将自己挂起时,也会被重新调度;
- 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程运行;
- 发生硬件中断时,CPU上的进程会被中断挂起,转而执行内核中的中断服务程序;
这几个场景是导致上下文切换性能问题的根本原因。
2、线程上下文切换
线程与进程最大的区别在于,线程是调度的基本单位,而进程则是资源拥有的基本单位。内核中的任务调度,调度的对象是线程;进程给线程提供了虚拟内存、全局变量等资源。所以,对于线程和进程可以理解为:
- 当前只有一个线程时,可以认为线程等于进程;
- 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量资源。这些资源在上下文切换时是不需要更改的;
- 线程也有自己的私有数据,比如:栈、寄存器等,这些在上下文切换时也是需要保存的。
所以线程上下文切换可以分为以下两种情况:
第一种,前后两个线程属于不同的进程;此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样。
第二种,前后两个线程属于同一个进程;此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不懂,只需要切换线程的私有数据、寄存器等不共享的数据。
虽然同为上下文切换,同进程的线程切换要比多进程间的线程切换消耗更少的资源,而这,也正是多线程代替多进程的一个优势。
3、中断上下文切换
为了快速相应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,相应设备事件。在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的 状态恢复运行。
跟进程上下文切换不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处于用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文切换只包括内核态中断服务程序执行所需的状态,包括CPU寄存器、内核堆栈、硬件中断参数等。
对同一个CPU来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。
跟进程上下文切换一样,中断上下文切换也需要消耗CPU,切换次数过多也会消耗大量对CPU,甚至严重降低系统的整体性能。
2.3、查看系统的上下文切换情况
查看上下文切换可以使用vmstat工具,它是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析CPU上下文切换和中断的次数。
示例:5表示每隔5秒输出1组数据
各指标含义:
procs
r:等待访问处理器的进程总数。展示CPU运行饱和度,不包含IO
b:sleep 状态的进程数
memory:
swpd:交换区大小,大于0说明内存不足
free:未分配的内存
buff:缓冲区大小,一般只有几十M
cache:缓存大小,通常几个G
swap:
si(swap-in):每秒从交换区写入内存的大小(单位:kb/s)
so(swap-out):每秒从内存写到交换区的大小
io:
bi(blocks-in):每秒读取的块数(读磁盘)
bo(blocks-out):每秒写入的块数(写磁盘)
system:
in(system interrupts):每秒中断数,包括时钟中断
cs(context switches):每秒上下文切换数
cpu(us+sy+id+wa+st = 100):
us(user time):用户进程执行消耗cpu时间
sy(system time):系统进程消耗cpu时间
Id(idle):空闲时间(包括IO等待时间)
wa(wait I/O):等待IO时间
st(steal time):代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU时间
vmstat只给出了系统总体上下文切换情况,想要看每个进程的详细情况,需要使用pidstat.
命令:pidstat -w 5 (每隔5秒输出一组数据)
重点关注两列:
- cswch(voluntary context switches):表示每秒自愿上下文切换的次数,指进程无法获取所需资源,导致的上下文切换;比如:I/O、内存等系统资源不足时
- nvcswch(non voluntary context switches):表示每秒非自愿上下文切换的次数,指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换;比如:大量进程都在竞争CPU时
2.4、案例分析
1、准备环境
系统:Ubuntu 18.04.2
机器配置:2CPU 2G内存
工具:sysbench sysstat
2、执行步骤
1)观察空闲系统的上下文切换次数
- 命令:vmstat 1 1
- 描述:间隔1秒后输出1组数据
cs(上下文切换):883次 in(中断):221次 r:1个 b:0
2)在第一个终端运行sysbench,模拟系统多线程调用瓶颈
- 命令:sysbench --threads=10 --max-time=300 threads run
- 描述:10个线程运行 5分钟基准测试
3)在第二个终端运行vmstat,观察上下文切换情况
- 命令:vmstat 1
- 描述:每隔1描述输出1组数据
需要关注几列数据的变化:
cs(上下文切换):从802升到200万+,存在大量的上下文切换;
r列:就绪队列的长度升到列9左右,超过了CPU(2个)个数,所以存在CPU竞争问题;
us列和sy列:这两列的CPU使用率加起来达到列100%,其中系统CPU使用率达到列83%,说明CPU主要被内核使用;
in列:终端 次数上升到1.8万+,说明中断处理也存在一定的问题;
从这些指标的变化,我们可以知道系统的就绪队列过长,导致了大量的上下文切换,而上下文切换导致了系统CPU的占用率升高。
但是还不知道引起这些指标升高的进程。我们可以借助pidstat看一下,执行下面命令。
命令:pidstat -w -u 1
描述:每隔一秒输出1组数据,-w 表示输出进程切换指标,-u表示输出cpu使用指标。
从pidstat输出我们可以看到,CPU使用率升高正是sysbench导致的,达到了100%,但是上下文切换来自其他进程,但是总的加起来上下文切换次数也比200万少很多。可以想一下这是为什么?
Linux调度的基本单位是线程,我们使用sysbench模拟的也是线程的调度,pidstat默认使用的是进程的指标数据,可以加上-t输出线程指标。使用下面命令:
命令:pidstat -wt 1
描述:每隔1秒输出1组数据,-wt 表示输出线程的上下文切换指标
从中我们可以看到 sysbench的子线程的上下文切换次数很多,这样我们就找到了引起上下文切换过多的原因。
但是我们在前面查找的过程中还发现一个问题,除了CPU使用率升高,上下文切换次数增多,中断次数也发生了很大的变化。但是还不清楚中断的源头!
中断只发生在内核,所以我们可以从/proc/interrupts这个文件读取中断次数。/proc是Linux的一个虚拟文件系统,用于内核空间与用户空间之间的通信。
命令:watch -d cat /proc/interrupts
描述:-d 表示高亮显示变化的区域
通过观察可以发现变化最快的是重调度中断(RES),这个中断表示:唤醒空闲状态的CPU来调度新的任务运行。这是多处理器系统(SMP)中,调度器用来分散任务到不同CPU的机制,通常也被称为处理器间中断。
所以前面我们看到的中断升高还是因为多任务的调度问题,跟前面上下文切换次数的分析结果一致。
回到最初的问题,每秒上下文切换多少才算是正常?
上下文切换次数取决于系统本身的CPU性能。如果系统的CPU上下文切换次数比较稳定,那么从数百到一万以内,都算正常。如果超过一万,或者上下文切换次数出现数量级增长,就有可能出现了性能问题。根据不同上下文切换的类型,可能有以下几个方面:
- 自愿上下文切换变多,说明进程都在等待资源,可能发生了I/O等其他问题;
- 非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在抢CPU资源,说明CPU成了瓶颈;
- 中断次数多了,说明CPU被中断处理程序占用,可以通过查看/proc/interrupts文件具体分析。
三、CPU使用率
3.1、CPU使用率介绍
Linux是一个多任务操作系统,将每个CPU的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成了多任务同时运行的错觉。
为了维护CPU时间,Linux通过预先定义的节拍率(内核中表示为HZ),触发时间中断,并使用全局变量Jiffies记录了开机以来的节拍数。没发生一次中断,Jiffies的值就加1.
节拍率是内核的可配置选项,不同的系统可能不同。可以通过查询/boot/config内核选项来查看他的配置。我安装的系统中设置成了250,也就是每秒触发250次时间中断。
同时也正是因为节拍率HZ是内核选项,所以用户空间程序不能直接访问。为了方便用户空间程序,内核提供了一个用户空间节拍率USER_HZ,它固定为100,也就是1/100 秒。
Linux 通过 /proc 虚拟文件系统,向用户空间提供了系统内部状态的信息,而 /proc/stat 提供的就是系统的 CPU 和任务统计信息。
命令:cat /proc/stat |grep ^cpu
描述:查看CPU信息
第一例显示的是CPU编号,第一行没有编号的表示所有CPU的累加。其他列则表示不同场景下 CPU 的累加节拍数,它的单位是 USER_HZ,也就是 10 ms(1/100 秒),所以这其实就是不同场景下的 CPU 时间。具体各列如下:
- user(缩写us):代表用户态 CPU 时间。不包括下面的 nice 时间,但是包括列guest时间。
- nice(缩写ni):代表低优先级用户态CPU时间,也就是进程的nice值被调整为1~19之间时的CPU时间。nice值的取值范围是-20~19,数值越大,优先级越低。
- system(缩写sys):代表内核态的CPU时间。
- idle(缩写id):代表空闲时间,不包括等待I/O的时间(iowait)。
- irq(缩写hi):代表处理硬中断的CPU时间。
- softirq(缩写si):代表处理软中断的CPU时间。
- steal(缩写st):代表当前系统运行在虚拟机中的时候,被其他虚拟机占用的CPU的时间。
- guest(缩写guest):代表通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的CPU时间。
- guest_nice(缩写gnice):代表以低优先级运行虚拟机的时间。
我们通常所说的CPU使用率,就是除了空闲时间外的其他时间占总CPU时间的百分比。用公式表示如下:
根据这个公式我们可以直接算出CPU的使用率,但是根据/proc/stat 中的数据计算出的是开机以来的平均CPU使用率,没有太大参考价值。
通常为了计算 CPU 使用率,性能工具一般都会取间隔一段时间(比如 3 秒)的两次值,作差后,再计算出这段时间内的平均 CPU使用率,即:
这个公式,就是我们用各种性能工具所看到的 CPU 使用率的实际计算方法。
了解了CPU使用率计算公式,是不是每次都需要我们去根据公式计算CPU使用率呢?
没有必要,各种各样的性能分析工具已经帮我们计算好了。不过要注意的是,性能分析工具给出的都是间隔一段时间的平均 CPU 使用率,所以要注意间隔时间的设置,特别是用多个工具对比分析时,你一定要保证它们用的是相同的间隔时间。
比如,对比一下 top 和 ps 这两个工具报告的 CPU 使用率,默认的结果很可能不一样,因为 top 默认使用 3 秒时间间隔,而 ps 使用的却是进程的整个生命周期。
3.2、怎么查看CPU使用率
查看CPU使用率常用的工具有top,这是最常用的性能分析工具。
- top显示了系统总体的CPU 和内存使用情况,以及各个进程资源的使用情况。
- ps则显示了每个进程资源的使用情况。
比如top命令输出如下:
第三行就是cpu使用率情况。top 默认显示的是所有 CPU 的平均值,这个时候你只需要按下数字 1 ,就可以切换到每个 CPU 的使用率了。
空白行之后是进程的实时信息,每个进程都有一个 %CPU 列,表示进程的 CPU 使用率。它是用户态和内核态 CPU 使用率的总和,包括进程用户空间使用的 CPU、通过系统调用执行的内核空间 CPU 、以及在就绪队列等待运行的 CPU。在虚拟化环境中,它还包括了运行虚拟机占用的 CPU。
top 并没有细分进程的用户态 CPU 和内核态 CPU。但是我们可以借助pidstat命令,它是专门分析每个进程 CPU 使用情况的工具。
比如,下面的 pidstat 命令,就间隔 1 秒展示了进程的 3 组 CPU 使用率,包括:
- %user:用户态 CPU 使用率;
- %system:内核态 CPU 使用率;
- %guest:运行虚拟机 CPU 使用率;
- %wait:等待 CPU 使用率;
- %CPU:总的 CPU 使用率(%CPU)。
最后的 Average 部分,还计算了 5 组数据的平均值。
3.3、CPU使用率过高怎么办
了解了CPU相关各个指标的含义,知道了如何查看CPU使用率,那么当我们发现CPU使用过高怎么办?通过top我们能够找到使用率高的进程,可能我们又想查看引起CPU使用率升高的是代码中哪个具体的函数?这时,我们又该怎么做呢?
这里我们可以借助perf工具,perf 是 Linux 2.6.31 以后内置的性能分析工具。它以性能事件采样为基础,不仅可以分析系统的各种事件和内核性能,还可以用来分析指定应用程序的性能问题。
使用perf分析CPU性能,可以采用以下两种方式:
第一种常见用法是 perf top,类似于 top,它能够实时显示占用 CPU 时钟最多的函数或者指令,因此可以用来查找热点函数,使用界面如下所示:
输出结果中,第一行包含三个数据,分别是采样数(Samples)、事件类型(event)和事件总数量(Event count)。
另外,采样数需要我们特别注意。如果采样数过少(比如只有十几个)。那么下面的排序还有百分比就没有了什么太大的参考价值。
我们看一下输出列表中各列数据的含义。
- 第一列Overhead,是该符号的性能事件在所有采样中的比例,用百分比表示;
- 第二个列Shared,是该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等。
- 第三列Object,是动态共享对象的类型。比如[.]表示用户空间的可执行程序、或者动态链接库,而[k]则表示内核空间。
- 最后一列Symbol是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示。
第二种用法,perf record和perf report。perf top虽然实时展示了系统的性能信息,但它的缺点是并不保存数据,也就无法用于离线或后续的分析。而perf record则提供了保存数据的功能,保存后的数据,需要你用perf report解析展示。
四、软中断
4.1、怎么理解软中断
中断是系统用来相应硬件设备请求的一种机制,它会打断进程的正常调度和执行,然后调用内核中的中断处理程序来响应设备的请求。
中断其实是一种异步的处理机制,可以提高系统的并发处理能力。
由于中断会打断其他程序的运行,所以为了减少对正常进程运行调度的影响,中断处理程序就需要尽可能快地运行。如果中断本身要做的事情不多,那么处理起来也不会有太大问题;但如果中断要处理的事情很多,中断服务程序就有可能要运行很长时间。
特别是,中断处理程序在响应中断时,还会临时关闭中断。这就会导导致上一次中断处理完成之前,其他中断都不能响应,也就是说中断有可能会丢失。
为了解决软中断处理程序执行过长和中断丢失的问题,Linux将中断处理过程分成了两个阶段,上半部和下半部:
- 上半部用来快速处理中断,它在中断禁止模式下运行,主要处理硬件紧密相关的或时间敏感的工作;
- 下半部用来延迟处理上半部未完成的工作,通常以内核线程的方式运行。
可以举个例子,比如网卡接收数据包。网卡接收到数据包后,会通过硬件中断的方式,通知内核有新的数据到了。这时,内核就应该调用中断处理程序来响应它。
这时对于上半部来说,其实就是要把网卡的数据读到内存中,然后更新一下硬件寄存器的状态(表示数据已经准备好了),最后再发送一个软中断信号,通知下半部做进一步的处理。
而下半部被软中断唤醒后,需要从内存中找到网络数据,再按照网络协议栈,对数据进行逐层解析和处理,直到把它送给应用程序。
所以,这两个阶段可以这样理解:
- 上半部直接处理硬件请求,也就是我们常说对硬中断,特点是快速执行;
- 而下半部则是由内核触发,也就是我们常说的软中断,特点是延迟执行。
实际上,上半部会打断 CPU 正在执行的任务,然后立即执行中断处理程序。而下半部以内核线程的方式执行,并且每个 CPU都对应一个软中断内核线程,名字为 “ksoftirqd/CPU编号”,比如说, 0 号 CPU 对应的软中断内核线程的名ksoftirqd/0。
4.2、查看软中断和内核线程
proc文件系统,是一种内核空间和用户空间互相通信的机制,可以用来查看内核的数据结构,或者用来动态修改内核的配置。其中:
- /proc/softirqs提供来软中断的运行情况;
- /proc/interrupts提供来硬中断的运行情况;
运行下面的命令可以查看文件/proc/softirqs文件的内容。就可以看到各种类型软中断在不同 CPU 上的累积运行次数:
cat /proc/softirqs
CPU0 CPU1
HI: 0 0
TIMER: 811613 1972736
NET_TX: 49 7
NET_RX: 1136736 1506885
BLOCK: 0 0
IRQ_POLL: 0 0
TASKLET: 304787 3691
SCHED: 689718 1897539
HRTIMER: 0 0
RCU: 1330771 1354737
查看/proc/softirqs文件内容时,需要特别注意两点:
第一、要注意软中断的类型,也就是第一列的内容。如NET_RX 表示网络接收中断,而 NET_TX 表示网络发送中断。
第二、要注意同一种软中断在不同 CPU 上的分布情况,也就是同一行的内容。正常情况下,同一种中断在不同 CPU 上的累积次数应该差不多。
从查看的信息中看到TASKLET在不同 CPU 上的分布并不均匀。TASKLET 是最常用的软中断实现机制,每个 TASKLET 只运行一次就会结束 ,并且只在调用它的函数所在的 CPU 上运行。
因此,使用 TASKLET 特别简便,当然也会存在一些问题,比如说由于只在一个 CPU 上运行导致的调度不均衡,再比如因为不能在多个 CPU 上并行运行带来了性能限制。
另外,刚刚提到过,软中断实际上是以内核线程的方式运行的,每个CPU 都对应一个软中断内核线程,这个软中断内核线程就叫做 ksoftirqd/CPU 编号。
root@ubuntu:~# ps aux|grep softirq
root 9 0.0 0.0 0 0 ? S 10:59 0:00 [ksoftirqd/0]
root 18 0.0 0.0 0 0 ? S 10:59 0:00 [ksoftirqd/1]
root 6654 0.0 0.0 21536 1060 pts/1 S+ 11:21 0:00 grep --color=auto softirq
注意,这些线程的名字外面都有中括号,这说明 ps 无法获取它们的命令行参数(cmline)。一般来说,ps 的输出中,名字括在中括号里的,一般都是内核线程。