uptime

$ uptime -pup 1 week, 1 day, 21 hours, 27 minutes
$ uptime 12:04:11 up 8 days, 21:27,1 user,load average: 0.54, 0.32, 0.23
  • “12:04:11” 表示当前时间
  • “up 8 days, 21:27,” 表示运行了多长时间
  • “load average: 0.54, 0.32, 0.23”
  • “1 user” 表示 正在登录的用户数
  • “load average: 0.54, 0.32, 0.23”,是 过去 1 分钟、5 分钟、15 分钟的平均负载(Load Average)。
Load Average

平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,它和 CPU 使用率并没有直接关系.

  • 可运行状态的进程,是指正在使用 CPU 或者正在等待 CPU 的进程,也就是我们常用 ps 命令看到的,处于 R 状态(Running 或 Runnable)的进程。
  • 不可中断状态的进程则是正处于内核态关键流程中的进程,并且这些流程是不可打断的,比如最常见的是等待硬件设备的 I/O 响应,也就是我们在ps 命令中看到的 D 状态(Uninterruptible Sleep,也称为 Disk Sleep)的进程。

当一个进程向磁盘读写数据时,为了保证数据的一致性,在得到磁盘回复前,它是不能被其他进程或者中断打断的,这个时候的进程就处于不可中断状态。如果此时的进程被打断了,就容易出现磁盘数据与进程数据不一致的问题。

所以,不可中断状态实际上是系统对进程和硬件设备的一种保护机制。

平均负载其实就是平均活跃进程数。平均活跃进程数,直观上的理解就是单位时间内的活跃进程数,但它实际上是活跃进程数的指数衰减平均值。这个“指数衰减平均”的详细含义先不用计较,这只是系统的一种更快速的计算方式,把它直接当成活跃进程数的平均值也没问题。

$ ps aux | moreUSERPID %CPU %MEMVSZ RSS TTYSTAT START TIME COMMANDroot10.00.0550404452 ?Ss Dec05 4:01 /usr/lib/systemd/systemd --switched-root --system --deserialize 22root20.00.00 0 ?SDec05 0:00 [kthreadd]root30.00.00 0 ?I< Dec05 0:00 [rcu_gp]root40.00.00 0 ?I< Dec05 0:00 [rcu_par_gp]root60.00.00 0 ?I< Dec05 0:00 [kworker/0:0H-kb]root80.00.00 0 ?I< Dec05 0:00 [mm_percpu_wq]root90.00.00 0 ?SDec05 0:19 [ksoftirqd/0]root 100.00.00 0 ?IDec05 4:50 [rcu_sched]
STAT进程状态

R:runing,表示当前正在运行的进程
S:sleep,当前正在睡眠的进程
T:stopped,当前停止运行的进程
D:当前不可中断的进程
Z:zombie,僵尸进程,即进程已终止,但却无法被移除至内存外

STAT状态后的内容含义

< 表示进程运行在高优先级上
N 表示进程运行在低优先级上
L 表示进程有页面锁定在内存中
s 表示进程是控制进程
l 表示进程是多进程
+表示当前进程运行在前台

那么最理想的,就是每个 CPU 上都刚好运行着一个进程,这样每个 CPU 都得到了充分利用。比如当平均负载为 2 时。

  • 在只有 2 个 CPU 的系统上,意味着所有的 CPU 都刚好被完全占用。
  • 在 4 个 CPU 的系统上,意味着 CPU 有 50% 的空闲。
  • 而在只有 1 个 CPU 的系统中,则意味着有一半的进程竞争不到 CPU。

查看CPU 的个数

grep 'model name' /proc/cpuinfo | wc -l

当平均负载比 CPU 个数还大的时候,系统已经出现了过载。

三个不同时间间隔的平均值,其实给我们提供了,分析系统负载趋势的数据来源,让我们能更全面、更立体地理解目前的负载状况。

  • 如果 1 分钟、5 分钟、15 分钟的三个值基本相同,或者相差不大,那就说明系统负载很平稳。
  • 但如果 1 分钟的值远小于 15 分钟的值,就说明系统最近 1 分钟的负载在减少,而过去 15 分钟内却有很大的负载。
  • 反过来,如果 1 分钟的值远大于 15 分钟的值,就说明最近 1 分钟的负载在增加,这种增加有可能只是临时性的,也有可能还会持续增加下去,所以就需要持续观察。一旦 1 分钟的平均负载接近或超过了 CPU 的个数,就意味着系统正在发生过载的问题,这时就得分析调查是哪里导致的问题,并要想办法优化了

当平均负载高于 CPU 数量 70% 的时候,你就应该分析排查负载高的问题了。一旦负载过高,就可能导致进程响应变慢,进而影响服务的正常功能。

平均负载与 CPU 使用率

平均负载是指单位时间内,处于可运行状态和不可中断状态的进程数。所以,它不仅包括了正在使用 CPU 的进程,还包括等待 CPU 和等待 I/O 的进程。

而 CPU 使用率,是单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。比如:

  • CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;
  • I/O 密集型进程,等待 I/O 也会导致平均负载升高,但CPU 使用率不一定很高;
  • 大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高。

性能分析

sysstat 包含了常用的 Linux 性能工具,用来监控和分析系统的性能
这个包有两个常用命令 mpstat 和 pidstat。

  • mpstat 是一个常用的多核 CPU 性能分析工具,用来实时查看每个 CPU 的性能指标,以及所有 CPU 的平均指标。
  • pidstat 是一个常用的进程性能分析工具,用来实时查看进程的 CPU、内存、I/O 以及上下文切换等性能指标。
stress 实验
模拟一个 CPU 使用率 100% 的场景
yuminstall -y stress$ stress --cpu 1 --timeout 600stress: info: [316930] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

终端运行 stress 命令,模拟一个 CPU 使用率 100% 的场景

$ watch -d uptimeEvery 2.0s: uptime Thu Dec 14 14:19:17 2023 14:19:17 up 8 days, 23:42,2 users,load average: 0.66, 0.23, 0.16

这边可以 1 分钟的平均负载会慢慢增加到 1.00

$ watch -d uptimeEvery 2.0s: uptime Thu Dec 14 14:24:53 2023 14:24:53 up 8 days, 23:48,3 users,load average: 1.73, 1.14, 0.59
# -P ALL 表示监控所有CPU,后面数字5表示间隔5秒后输出一组数据$ mpstat -P ALL 5Linux 5.4.xxx 2023年12月14日 _x86_64_(4 CPU)14时20分34秒CPU%usr %nice%sys %iowait%irq %soft%steal%guest%gnice %idle14时20分39秒all 25.780.000.400.100.000.050.000.000.00 73.6714时20分39秒01.210.000.600.000.000.000.000.000.00 98.1914时20分39秒10.800.000.600.000.000.000.000.000.00 98.5914时20分39秒20.800.000.400.000.000.000.000.000.00 98.7914时20分39秒3100.000.000.000.000.000.000.000.000.000.00
# 间隔5秒后输出一组数据$ pidstat -u 5 114时26分45秒 UID PID%usr %system%guest%CPU CPUCommand14时26分50秒 0316931 99.800.000.00 99.80 3stress
I/O 密集型进程 模拟
$ stress -i 1 --timeout 600
大量进程的场景
$ stress -c 8 --timeout 600

CPU

CPU 的上下文切换就可以分为几个不同的场景,也就是

  • 进程上下文切换、
  • 线程上下文切换
  • 以及中断上下文切换。

根据 Tsuna 的测试报告,每次上下文切换都需要几十纳秒到数微秒的 CPU 时间。

系统调用

进程既可以在用户空间运行,又可以在内核空间中运行。进程在用户空间运行时,被称为进程的用户态,而陷入内核空间的时候,被称为进程的内核态。

系统调用过程通常称为特权模式切换,而不是上下文切换。

进程和线程

线程是调度的基本单位,而进程则是资源拥有的基本单位。

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

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

跟进程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。

对同一个 CPU 来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。同样道理,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便尽可能快的执行结束。

另外,跟进程上下文切换一样,中断上下文切换也需要消耗 CPU,切换次数过多也会耗费大量的 CPU,甚至严重降低系统的整体性能。所以,当你发现中断次数过多时,就需要注意去排查它是否会给你的系统带来严重的性能问题。

profiling

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

# 每隔5秒输出1组数据$ vmstat 5procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- rb swpd free buffcache si sobibo in cs us sy id wa st 000 700536091564 81890000 0 0 25 3300 10000
  • cs(context switch)是每秒上下文切换的次数。
  • in(interrupt)则是每秒中断的次数。
  • r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
  • b(Blocked)则是处于不可中断睡眠状态的进程数。
# 每隔5秒输出1组数据$ pidstat -w 5Linux 4.15.0 (ubuntu)09/23/18_x86_64_(2 CPU)08:18:26UID PID cswch/s nvcswch/sCommand08:18:310 10.200.00systemd08:18:310 85.400.00rcu_sched...

这个结果中有两列内容是我们的重点关注对象。一个是 cswch ,表示每秒自愿上下文切换(voluntary context switches)的次数,另一个则是 nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数。

  • 所谓自愿上下文切换,是指进程无法获取所需资源,导致的上下文切换。比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换。
  • 而非自愿上下文切换,则是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换。
sysbench

sysbench 是一个多线程的基准测试工具,一般用来评估不同系统参数下的数据库负载情况。

yum install -y sysbench
# 以10个线程运行5分钟的基准测试,模拟多线程切换的问题$ sysbench --threads=10 --max-time=300 threads run
# 每隔1秒输出1组数据(需要Ctrl+C才结束)$ vmstat 1procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- rb swpd free buffcache si sobibo in cs us sy id wa st 600 6487428 118240 129277200 0 0 9019 1398830 16 84000 800 6487428 118240 129277200 0 0 10191 1392312 16 84000

cs 列的上下文切换次数从之前的 35 骤然上升到了 139 万。同时,注意观察其他几个指标:

  • r 列:就绪队列的长度已经到了 8,远远超过了系统 CPU 的个数 2,所以肯定会有大量的 CPU 竞争。
  • us(user)和 sy(system)列:这两列的 CPU 使用率加起来上升到了 100%,其中系统 CPU 使用率,也就是 sy 列高达 84%,说明 CPU 主要是被内核占用了。
  • in 列:中断次数也上升到了 1 万左右,说明中断处理也是个潜在的问题。
# 每隔1秒输出1组数据(需要 Ctrl+C 才结束)# -w参数表示输出进程切换指标,而-u参数则表示输出CPU使用指标$ pidstat -w -u 108:06:33UID PID%usr %system%guest %wait%CPU CPUCommand08:06:340 10488 30.00100.000.000.00100.00 0sysbench08:06:340 263260.001.000.000.001.00 0kworker/u4:208:06:33UID PID cswch/s nvcswch/sCommand08:06:340 8 11.000.00rcu_sched08:06:340161.000.00ksoftirqd/108:06:340 4711.000.00hv_balloon08:06:34012301.000.00iscsid08:06:34040891.000.00kworker/1:508:06:34043331.000.00kworker/0:308:06:340 104991.00224.00pidstat08:06:340 26326236.000.00kworker/u4:208:06:34 1000 26784223.000.00sshd

查看线程上下文切换

# 每隔1秒输出一组数据(需要 Ctrl+C 才结束)# -wt 参数表示输出线程的上下文切换指标$ pidstat -wt 108:14:05UIDTGID TID cswch/s nvcswch/sCommand...08:14:050 10551 -6.000.00sysbench08:14:050 - 105516.000.00|__sysbench08:14:050 - 1055218911.00 103740.00|__sysbench08:14:050 - 1055318915.00 100955.00|__sysbench08:14:050 - 1055418827.00 103954.00|__sysbench...

pidstat 只是一个进程的性能分析工具,并不提供任何关于中断的详细信息,怎样才能知道中断发生的类型呢?

从 /proc/interrupts 这个只读文件中读取。/proc 实际上是 Linux 的一个虚拟文件系统,用于内核空间与用户空间之间的通信。/proc/interrupts 就是这种通信机制的一部分,提供了一个只读的中断使用情况。

运行下面的命令,观察中断的变化情况:

# -d 参数表示高亮显示变化的区域$ watch -d cat /proc/interrupts CPU0 CPU1...RES:24504315279697 Rescheduling interrupts...

变化速度最快的是重调度中断(RES),这个中断类型表示,唤醒空闲状态的 CPU 来调度新的任务运行。这是多处理器系统(SMP)中,调度器用来分散任务到不同 CPU 的机制,通常也被称为处理器间中断(Inter-Processor Interrupts,IPI)。

小结
  • 自愿上下文切换变多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题;
  • 非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈;
  • 中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。

####. CPU 使用率

$ grep 'CONFIG_HZ=' /boot/config-$(uname -r)CONFIG_HZ=1000

即每秒钟触发 1000 次时间中断。

节拍率 HZ 是内核选项,所以用户空间程序并不能直接访问。为了方便用户空间程序,内核还提供了一个用户空间节拍率 USER_HZ,它总是固定为 100,也就是 1/100 秒。这样,用户空间程序并不需要关心内核中 HZ 被设置成了多少,因为它看到的总是固定值 USER_HZ。

# 只保留各个CPU的数据$ cat /proc/stat | grep ^cpucpu280580 7407 286084 172900810 83602 0 583 0 0 0cpu0 144745 4181 176701 86423902 52076 0 301 0 0 0cpu1 135834 3226 109383 86476907 31525 0 282 0 0 0

第一列表示的是 CPU 编号,如 cpu0、cpu1 ,而第一行没有编号的 cpu ,表示的是所有 CPU 的累加。其他列则表示不同场景下 CPU 的累加节拍数,它的单位是 USER_HZ,也就是 10 ms(1/100 秒),所以这其实就是不同场景下的 CPU 时间。