2. PSI内核原理:Linux内核如何采集PSI数据、PSI的采样周期与半衰期、/proc/pressure/文件系统详解
在深入使用PSI指标进行异常检测之前,必须理解Linux内核是如何在底层采集、计算和暴露这些数据的。本章将拆解PSI的内核实现机制,包括数据采集路径、核心的采样周期与半衰期算法,以及用户态最常接触的/proc/pressure/文件系统接口。
2.1 内核数据采集路径
PSI(Pressure Stall Information)在内核中是一个轻量级的统计子系统,它不依赖额外的硬件或守护进程,而是直接挂载在调度器和内存管理的关键路径上。
核心采集点:PSI的计数器在以下三种资源等待事件发生时递增:
- CPU:任务处于可运行状态(TASK_RUNNING)但未能获得CPU时间片,即被调度器标记为“排队等待”。
- IO:任务因等待块设备I/O(如磁盘读写)而处于不可中断睡眠状态(D状态)。
- MEM:任务因等待回收物理内存页(如缺页异常、直接内存回收)而处于不可中断睡眠状态。
内核维护一个全局的psi_group结构体,每个CPU核心都有独立的per-CPU计数器,用于记录上述三种资源的等待时间。采集流程如下:
- 事件触发:调度器在
try_to_wake_up()、dequeue_task()等函数中,检测到任务状态变化(如从运行变为睡眠),调用psi_task_change()。 - 时间戳记录:内核记录任务进入等待状态的起始时间戳(使用高精度时钟
sched_clock())。 - 累积计算:当任务退出等待状态时,计算等待时长,并累加到对应CPU的per-CPU计数器中。
- 聚合更新:内核周期性地(由采样周期驱动)将per-CPU计数器聚合到全局
psi_group中,并运行半衰期算法计算滑动平均。
2.2 采样周期与半衰期
PSI指标并非实时绝对值,而是通过指数加权移动平均(EWMA)计算的压力百分比。这个算法由两个关键参数控制:采样周期和半衰期。
| 参数 | 内核默认值 | 含义 | 对指标的影响 |
|---|---|---|---|
| 采样周期 | 2秒 | 内核将per-CPU计数器聚合到全局状态的时间间隔 | 决定了指标更新的粒度;周期越短,对突发压力的响应越快 |
| 半衰期 | 10秒(some/avg10) 60秒(some/avg60) 300秒(some/avg300) |
EWMA中历史权重衰减一半所需的时间 | 决定了指标对历史压力的“记忆”长度;半衰期越长,曲线越平滑 |
理解半衰期:假设当前压力为100%,半衰期为10秒。10秒后,指标值会衰减到50%(如果新压力为0);20秒后衰减到25%。这意味着
avg10主要反映最近10秒的压力状况,而avg300反映最近5分钟的趋势。
EWMA的递推公式如下(内核源码kernel/sched/psi.c中的实现):
/* 伪代码:PSI的EWMA更新逻辑 */
total_ticks = now - last_update;
decay = exp(total_ticks * ln(0.5) / halflife);
/* 新平均值 = 旧平均值 × 衰减因子 + 新样本 × (1 - 衰减因子) */
avg_new = avg_old * decay + sample * (1 - decay);
内核使用固定周期采样而非事件驱动更新。每2秒(由PSI_FREQ宏定义),内核唤醒一个工作队列,执行以下操作:
- 读取当前所有per-CPU计数器的快照。
- 计算自上次采样以来的新增等待时间。
- 将新增时间除以采样周期(2秒),得到该周期内的平均压力百分比(
sample)。 - 将
sample代入EWMA公式,更新avg10、avg60、avg300三个滑动平均值。
2.3 /proc/pressure/ 文件系统详解
用户态通过/proc/pressure/目录下的三个伪文件读取PSI指标。这些文件由内核的psi_proc_init()函数注册,每次读取时动态计算并格式化输出。
| 文件 | 资源类型 | 输出格式示例 | 关键字段 |
|---|---|---|---|
/proc/pressure/cpu |
CPU | some avg10=2.34 avg60=1.56 avg300=0.89 total=12345678 |
some(部分任务等待CPU) |
/proc/pressure/io |
I/O | some avg10=5.67 avg60=3.21 avg300=1.45 total=9876543 |
some(部分任务等待I/O) full(所有非空闲任务等待I/O) |
/proc/pressure/memory |
内存 | some avg10=0.00 avg60=0.00 avg300=0.00 total=0 |
some(部分任务等待内存) full(所有非空闲任务等待内存) |
注意:
some与full的区别:
- some:系统中至少有一个任务正在等待该资源,但并非所有任务都被阻塞。
- full:系统中所有可运行的任务(非空闲)都在等待该资源,意味着CPU完全被I/O或内存瓶颈拖死。
CPU资源只有some指标,因为CPU等待不会导致所有任务同时阻塞(总有任务在运行或空闲)。
字段详解:
- avg10 / avg60 / avg300:过去10秒、60秒、300秒内的平均压力百分比,取值范围0.00~100.00。这是异常检测最常用的指标。
- total:自系统启动以来,所有任务等待该资源的总微秒数(单调递增)。可用于计算长时间窗口内的平均压力,或检测压力累积趋势。
2.4 内核配置与可观测性
PSI功能在Linux内核中默认启用(从4.20版本开始),但可以通过内核编译选项控制:
# 内核编译选项
CONFIG_PSI=y # 启用PSI支持
CONFIG_PSI_DEFAULT_DISABLED=n # 默认启用(若为y,需内核启动参数psi=1)
运行时可以通过sysfs查看PSI是否启用:
# 检查PSI是否启用
cat /proc/pressure/cpu
# 如果返回"psi_disabled",说明内核未启用PSI
# 查看内核启动参数(如果CONFIG_PSI_DEFAULT_DISABLED=y)
cat /proc/cmdline | grep psi
# 应包含 "psi=1"
性能影响:PSI的采集路径在调度器关键路径上,但设计为O(1)复杂度,仅增加少量原子操作和条件判断。根据内核社区测试,启用PSI对整体吞吐量的影响通常小于1%。在压力测试场景中,这个开销完全可以接受。
2.5 本章小结
- PSI数据由内核在调度器路径上同步采集,通过per-CPU计数器避免锁竞争。
- 采样周期固定为2秒,驱动EWMA滑动平均计算。
- 三个半衰期(10s/60s/300s)分别对应
avg10、avg60、avg300,用于不同时间尺度的压力感知。 /proc/pressure/提供cpu、io、memory三个文件,输出some/full维度的平均压力百分比和累计等待时间。- 理解这些内核原理,是后续章节中正确设置异常检测阈值、解读压力曲线的基础。