uptime
$ uptime -p
up 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 | more
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 55040 4452 ? Ss Dec05 4:01 /usr/lib/systemd/systemd --switched-root --syst
em --deserialize 22
root 2 0.0 0.0 0 0 ? S Dec05 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< Dec05 0:00 [rcu_gp]
root 4 0.0 0.0 0 0 ? I< Dec05 0:00 [rcu_par_gp]
root 6 0.0 0.0 0 0 ? I< Dec05 0:00 [kworker/0:0H-kb]
root 8 0.0 0.0 0 0 ? I< Dec05 0:00 [mm_percpu_wq]
root 9 0.0 0.0 0 0 ? S Dec05 0:19 [ksoftirqd/0]
root 10 0.0 0.0 0 0 ? I Dec05 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% 的场景
yum install -y stress
$ stress --cpu 1 --timeout 600
stress: info: [316930] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
终端运行 stress 命令,模拟一个 CPU 使用率 100% 的场景
$ watch -d uptime
Every 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 uptime
Every 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 5
Linux 5.4.xxx 2023年12月14日 _x86_64_ (4 CPU)
14时20分34秒 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
14时20分39秒 all 25.78 0.00 0.40 0.10 0.00 0.05 0.00 0.00 0.00 73.67
14时20分39秒 0 1.21 0.00 0.60 0.00 0.00 0.00 0.00 0.00 0.00 98.19
14时20分39秒 1 0.80 0.00 0.60 0.00 0.00 0.00 0.00 0.00 0.00 98.59
14时20分39秒 2 0.80 0.00 0.40 0.00 0.00 0.00 0.00 0.00 0.00 98.79
14时20分39秒 3 100.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
# 间隔5秒后输出一组数据
$ pidstat -u 5 1
14时26分45秒 UID PID %usr %system %guest %CPU CPU Command
14时26分50秒 0 316931 99.80 0.00 0.00 99.80 3 stress
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 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 7005360 91564 818900 0 0 0 0 25 33 0 0 100 0 0
-
cs(context switch)是每秒上下文切换的次数。
-
in(interrupt)则是每秒中断的次数。
-
r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
-
b(Blocked)则是处于不可中断睡眠状态的进程数。
每隔5秒输出1组数据
$ pidstat -w 5
Linux 4.15.0 (ubuntu) 09/23/18 x86_64 (2 CPU)08:18:26 UID PID cswch/s nvcswch/s Command
08:18:31 0 1 0.20 0.00 systemd
08:18:31 0 8 5.40 0.00 rcu_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 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
6 0 0 6487428 118240 1292772 0 0 0 0 9019 1398830 16 84 0 0 0
8 0 0 6487428 118240 1292772 0 0 0 0 10191 1392312 16 84 0 0 0
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 1
08:06:33 UID PID %usr %system %guest %wait %CPU CPU Command
08:06:34 0 10488 30.00 100.00 0.00 0.00 100.00 0 sysbench
08:06:34 0 26326 0.00 1.00 0.00 0.00 1.00 0 kworker/u4:208:06:33 UID PID cswch/s nvcswch/s Command
08:06:34 0 8 11.00 0.00 rcu_sched
08:06:34 0 16 1.00 0.00 ksoftirqd/1
08:06:34 0 471 1.00 0.00 hv_balloon
08:06:34 0 1230 1.00 0.00 iscsid
08:06:34 0 4089 1.00 0.00 kworker/1:5
08:06:34 0 4333 1.00 0.00 kworker/0:3
08:06:34 0 10499 1.00 224.00 pidstat
08:06:34 0 26326 236.00 0.00 kworker/u4:2
08:06:34 1000 26784 223.00 0.00 sshd
查看线程上下文切换
# 每隔1秒输出一组数据(需要 Ctrl+C 才结束)
# -wt 参数表示输出线程的上下文切换指标
$ pidstat -wt 1
08:14:05 UID TGID TID cswch/s nvcswch/s Command
...
08:14:05 0 10551 - 6.00 0.00 sysbench
08:14:05 0 - 10551 6.00 0.00 |__sysbench
08:14:05 0 - 10552 18911.00 103740.00 |__sysbench
08:14:05 0 - 10553 18915.00 100955.00 |__sysbench
08:14:05 0 - 10554 18827.00 103954.00 |__sysbench
...
pidstat 只是一个进程的性能分析工具,并不提供任何关于中断的详细信息,怎样才能知道中断发生的类型呢?
从 /proc/interrupts 这个只读文件中读取。/proc 实际上是 Linux 的一个虚拟文件系统,用于内核空间与用户空间之间的通信。/proc/interrupts 就是这种通信机制的一部分,提供了一个只读的中断使用情况。
运行下面的命令,观察中断的变化情况:
# -d 参数表示高亮显示变化的区域
$ watch -d cat /proc/interrupts
CPU0 CPU1
...
RES: 2450431 5279697 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 ^cpu
cpu 280580 7407 286084 172900810 83602 0 583 0 0 0
cpu0 144745 4181 176701 86423902 52076 0 301 0 0 0
cpu1 135834 3226 109383 86476907 31525 0 282 0 0 0
第一列表示的是 CPU 编号,如 cpu0、cpu1 ,而第一行没有编号的 cpu ,表示的是所有 CPU 的累加。其他列则表示不同场景下 CPU 的累加节拍数,它的单位是 USER_HZ,也就是 10 ms(1/100 秒),所以这其实就是不同场景下的 CPU 时间。