Linux load 高的原因
其实 Linux 是不区分线程和进程的,在 Linux 眼里都是进程,Linux 为了完成对线程的 POSIX 标准,引入了 LWP,就是轻量级进程,即我们眼中的线程,线程和进程的创建方法在底层是相同的,都是调用 do_fork() 方法,只是传参不同。就因为这个使得线程和进程的概念混淆了,有人说系统调度单位是进程,又有人说是线程,其实系统调度的单位一直就没有改变,只是后来部分线程和进程的界限模糊了
查看系统所有的 LWP 和普通进程:
$ ps -ALFlf
查看系统中所有的普通进程:
$ ps -ef
下面说的进程都是说普通进程+LWP
简单来说,平均负载是指单位时间内,系统处于正在运行、可运行状态和不可中断状态的平均进程(普通进程+LWP)数,它和 CPU 使用率并没有直接关系。
再简单可以把 load 看成 (正在运行的进程数+不可中断状态的进程数+不可中断状态进程数)
或者把 load 看成 (R 状态的进程+D 状态的进程)
,这只是简单的来说,其实 load 是个拟合值,其中数学算法比较复杂,具体的计算方法在如下代码中。
load 数值的计算代码为: https://github.com/torvalds/linux/blob/master/fs/proc/loadavg.c
内核代码如何统计 load?
内核使用调度器中的 nr_running
和 nr_uninterruptible
来计算运行队列的总任务数:
nr_running
:当前 TASK_RUNNING
的任务数。
nr_uninterruptible
:当前 TASK_UNINTERRUPTIBLE
的任务数。
在 get_avenrun
函数中,负载值是这样统计的:
count = nr_running() + nr_uninterruptible();
get_avenrun
是 Linux 内核中一个核心函数,用于获取系统的负载平均值(Load Average)。它是负载平均值从内核导出到用户空间的关键接口,主要被用于用户态工具(如 uptime
、 top
、 w
等)以显示系统的负载信息。
get_avenrun
的主要用途
1.获取系统负载平均值 :
返回 1 分钟、5 分钟和 15 分钟的负载平均值。
这些值是通过 指数加权移动平均(EWMA)算法 持续更新的,保存在内核中的 avenrun[]
数组。
2.为用户态工具提供接口 :
用户态工具通过系统调用(如 /proc/loadavg
文件)读取这些值,从而显示系统的负载信息。
3.为其他内核组件服务 :
某些内核模块可能需要负载信息来辅助决策。例如:
1.调整 CPU 频率(动态调频)。
2.配置资源分配策略。
get_avenrun
的代码实现
get_avenrun
的具体实现如下(省略一些上下文):
void get_avenrun(unsigned long *loads, unsigned long offset, int shift)
{
int i;
for (i = 0; i < 3; i++)
loads[i] = (avenrun[i] + offset) << shift;
}
代码解读:
1.输入参数 :
loads
:指向用户空间或其他内核模块的数组,用于存储计算结果。
offset
:通常是 FIXED_1/200
,用来调整精度。
shift
:用于调整负载值的位移,通常是 0
或 FSHIFT
。
2.核心逻辑 :
遍历 avenrun[]
,按需求调整值后填充到 loads[]
中。
3.输出结果 :
1 分钟、5 分钟、15 分钟的负载值被写入 loads[]
,通常以 固定点数(fixed-point)表示 。
固定点数的表示
负载值在内核中以**固定点数(fixed-point)**表示,而不是普通浮点数:
avenrun[]
中的值以 FSHIFT
(通常为 11)为小数位偏移。
这样可以避免在内核中使用性能开销更大的浮点数运算。
get_avenrun
的调用场景
1. /proc/loadavg
文件 :
用户通过读取 /proc/loadavg
文件获取负载平均值,系统会调用 get_avenrun
提供数据。
相关代码:
static int loadavg_proc_show(struct seq_file *m, void *v)
{
unsigned long avnrun[3];
get_avenrun(avnrun, FIXED_1/200, 0);
seq_printf(m, "%lu.%02lu %lu.%02lu %lu.%02lu\n",
LOAD_INT(avnrun[0]), LOAD_FRAC(avnrun[0]),
LOAD_INT(avnrun[1]), LOAD_FRAC(avnrun[1]),
LOAD_INT(avnrun[2]), LOAD_FRAC(avnrun[2]));
return 0;
}
2. uptime
、 top
、 w
等工具 :
这些工具间接读取 /proc/loadavg
或调用系统接口,最终依赖 get_avenrun
获取负载值。
3.内核模块 :
动态负载均衡、资源管理、CPU 调频等需要参考系统的历史负载数据。
get_avenrun
的关系图
/proc/loadavg
↓
`loadavg_proc_show`
↓
`get_avenrun`
↓
使用 `avenrun[]` 提供负载值
查看系统的 load 值
$ cat /proc/loadavg
0.06 0.04 0.05 1/170 4087
#前三个值为显示最近 1 分钟,5 分钟,15 分钟的 load 值
#第四个数字为,分子为当前处于 R 状态线程数量,分母为当前系统中有多少个 LWP
#第五个数字为,最新创建出来的 PID 为多少
一般 load 数字在 CPU 的逻辑个数的二倍以下为正常值
- 处于 R 状态(Running 或 Runnable)的进程是指
正在运行
或者可运行并等待 CPU 执行
的进程, ps -ALFlf 命令在第二列S
一列中显示R
状态的进程 - 处于 D 状态(Uninterruptible Sleep,也称为 Disk Sleep)的进程是指
不可中断状态
的进程,这些进程不可打断的。比如最常见的是等待硬件设备的 I/O 响应,或者执行某些关键的系统调用,或者进入锁状态,都是会处于 D 状态
从上述 load 的计算原理可以看出,导致 load 飙高的原因,说简单也简单,无非就是 running+runnable 或者 uninterruptible 的 task 增多了。但是说复杂也复杂,因为导致 task 进入 uninterruptible 状态的路径非常多
R 多导致 load 飙高
通过 vmstat 会发现 r 这一列的数值会飙高,证明大量线程 running 或者 runnable
通过 ps 命令可以查询什么程序有多个 R 和 D,然后通过 perf 调查程序为什么会有那么多 R 的问题。通常是由于业务量的增加导致的,属于正常现象,但也有是业务代码 bug 导致的,比如长循环甚至死循环
$ ps -e -L h o state,pid,ppid,ucmd:30,cmd:100,wchan:30 |awk '{if($1=="R"||$1=="D"){print $0}}' | sort | uniq -c
9 R 5082 2828 sysbench sysbench --threads=20 --max-time=300 threads run -
1 R 5082 2828 sysbench sysbench --threads=20 --max-time=300 threads run futex_wait_queue_me
1 R 5115 1861 ps ps -e -L h o state,pid,ppid,ucmd:30,cmd:100,wchan:30 -
1 R 5116 1861 awk awk {if($1=="R"||$1=="D"){print $0}} -
1 R 9 2 rcu_sched [rcu_sched]
D 多导致 load 飙高
IO 原因
通过 vmstat 会发现 b 这一列的数值会飙高,证明大量线程因为等待 IO 而处于 D 状态。
$ vmstat -wt 1
procs --memory- swap-- --io- -system-- --cpu-- --timestamp--
r b swpd free buff cache si so bi bo in cs us sy id wa st CST
1 28 0 983824 187652 2400824 0 0 0 5 3 1 10 20 0 70 0 2022-05-21 20:10:06
通过 iostat -xt 1 可以查看磁盘的 await 会飙高
$ iostat -xt 1
Linux 3.10.0-1160.45.1.el7.x86_64 (VM-0-20-centos) 05/21/2022 _x86_64_ (2 CPU)
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.62 0.02 1.55 0.65 10.18 13.83 0.00 999.4 0.10 999.3 0.35 0.06
vdb 0.00 0.00 0.00 0.00 0.00 0.00 48.89 0.00 0.45 0.45 0.00 0.33 0.00
内存原因
- task 在申请内存的时候,可能会触发内存回收,如果触发的是直接内存回收,那对性能的伤害很大。当前 task 会被阻塞直到内存回收完成。一般这种只是暂时
- 因为内存的分配是以页进行分配,有的时候,有的程序需要某个大小的页的内存或者连续的内存空间没有,导致分配内存的系统调用处于 D 状态,后续的内存申请都不能完成,导致大量线程处于 D 状态等待,从而 load 高
网络原因
如果网卡的大量的丢包或者重传,就会导致程序处于 D 状态,如果 D 状态进程太多,就会导致 load 高
锁
一般这种情况,通过 vmstat 查看系统可以 r 和 b 都非常少,但是通过 ps 命令查询 D 状态的进程非常多
$ vmstat -wt 1
procs --memory- swap-- --io- -system-- --cpu-- --timestamp--
r b swpd free buff cache si so bi bo in cs us sy id wa st CST
1 0 0 983824 187652 2400824 0 0 0 5 3 1 0 0 100 0 0 2022-05-21 20:10:06
$ ps -e -L h o state,pid,ppid,ucmd:30,cmd:100,wchan:30 |awk '{if($1=="R"||$1=="D"){print $0}}' | sort | uniq -c
99 D 5082 2828 sysbench sysbench --threads=20 --max-time=300 threads run futex_wait_queue_me
当某些程序执行系统调用时。该系统调用处于死锁状态,后续的程序只要运行该系统调用时,就会让程序处于 D 状态,造成大量进程处于 D 状态,从而 LOAD 高
这种就比较棘手,通过上面的 PS 命令查询处于 D 状态的进程执行的 wchan,可以看到程序执行哪些系统调用进入了死锁状态,也可以查看/proc/pid/stat 以及/proc/pid/stat 以及/proc/{pid}/task/${pid}/stat 文件 查看进程在哪些系统调用进入了死锁状态。即使知道哪个系统调用进入死锁,但是也不知道哪个程序导致该系统调用第一个进入死锁状态的,所以需要开发专用的追踪内核程序来分析
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: Linux core_dump
下一篇: Linux LWP
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论