
Linux PSI 压力停滞信息/proc/pressure 指标、cgroup2 接口与阈值触发监控实战【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本篇技术指南围绕 Linux 内核的 PSIPressure Stall Information压力停滞信息机制展开它量化 CPU、内存、IO 三种资源争用造成的性能损失并支持注册触发器在压力超过阈值时主动唤醒用户空间监控进程。读完本文你将掌握 PSI 的 SOME/FULL 双指标模型与计算公式、/proc/pressure/输出格式的逐字段解读、触发器注册规则窗口范围、非特权限制、EBUSY 语义、可直接运行的 C 语言监控示例以及 cgroup2 场景下的 per-cgroup 压力接口并能对照 kernel/sched/psi.c 理解内核如何以低成本采样实现上述指标。一、PSI 要解决的问题资源争用下的性能损失如何量化当 CPU、内存或 IO 设备发生争用时工作负载会出现延迟尖峰、吞吐下降甚至面临 OOM kill 的风险。正如 Documentation/accounting/psi.rst 开篇所述缺乏准确的争用度量时用户要么保守使用、让硬件资源长期闲置要么冒险过度超卖overcommit而频繁遭受服务中断PSI 识别并量化这类资源紧张造成的中断及其对复杂工作负载乃至整个系统的耗时影响由于 PSI 实时聚合这些信息系统可以动态管理——例如负载卸载load shedding、将任务迁移到其他系统或数据中心、策略性地暂停或杀掉低优先级/可重启的批处理作业从而在不牺牲工作负载健康度、不冒 OOM 风险的前提下最大化硬件利用率。内核侧的实现位于 kernel/sched/psi.c其文件头注释L1–L138给出了完整的模型说明以任务能在 CPU 上执行的时间作为生产力基线压力pressure表达的是由于资源争用而无法实现的这部分时间占比。1.1 SOME 与 FULL 双状态模型PSI 为每个资源定义两种争用状态状态含义源码定义psi.c L38–L39SOME至少一个任务在该资源上被延迟。影响工作负载推进速度但 CPU 可能仍在执行其他任务SOME nr_delayed_tasks ! 0FULL所有非空闲任务同时被该资源延迟没有任何任务能推进CPU 空转浪费FULL nr_delayed_tasks ! 0 nr_productive_tasks 0长时间停留在 FULL 状态意味着系统处于抖动thrashing状态对性能影响严重因此内核将其与SOME 但 CPU 仍在做有效工作的状态分开统计分别导出到some和full两行。不同资源对productive正在产出的定义不同psi.c L41–L44IOproductive 指处于 running 状态的任务内存productive 指 running 且不是回收者reclaimer的任务——正在做回收的 CPU 时间不算产出CPUproductive 指 on-CPU 的任务。一个值得注意的特殊性CPU 资源在系统级别不存在 FULL 状态任务都在等 CPU 本身在逻辑上矛盾但自 5.13 起系统级会报告 CPU full值为 0仅为向后兼容cgroup 级别则存在 CPU FULL含义是该 cgroup 内所有非空闲任务都在等待 CPU——CPU 被 cgroup 外的其他任务占用或被cpu.max配置所限流psi.c L46–L50。1.2 多 CPU 下的压力计算公式任务与 CPU 越多能并发执行的工作越多因此可能损失的执行潜力也随非空闲任务数和 CPU 数共同缩放。文档以两个经典场景说明257 个计算任务竞争 256 个 CPU若简单聚合任务状态会得出 CPU SOME 100%始终有人在等运行队列但实际争用只有 1/256 ≈ 0.4%4 个任务、4 个 CPU其中 1 个任务因内存不足被延迟仅看任务状态会得出内存 FULL 0%总有其他任务在推进但真实损失是 4 个 CPU 中的 1 个即 25%。正确的计算方式psi.c L89–L91threads min(nr_nonidle_tasks, nr_cpus) SOME min(nr_delayed_tasks / threads, 1) FULL (threads - min(nr_productive_tasks, threads)) / threads对 257 任务/256 CPU 场景SOME 1/256 ≈ 0.4%FULL 0%对 4 任务/1 个内存延迟任务场景SOME FULL 25%。单 CPU 时threads 取 1该模型自然退化为单处理器情况。二、压力输出接口/proc/pressure/ 文件格式开启CONFIG_PSI后每种资源的压力信息通过/proc/pressure/下对应文件导出cpu、memory、io。输出格式some avg100.00 avg600.00 avg3000.00 total0 full avg100.00 avg600.00 avg3000.00 total0各字段含义some行至少有一部分任务在该资源上被延迟的时间占比%full行所有非空闲任务同时被该资源延迟的时间占比%avg10/avg60/avg300分别对应 10 秒、60 秒、300 秒窗口的近期趋势兼顾短期事件与中、长期趋势观察total累计绝对停滞时间单位微秒 µs。导出它有两个用途一是发现那些不会显著拉低时间平均值、但确实存在的延迟尖峰二是让用户空间对自定义时间窗做趋势平均。从源码看这三个平均值的衰减常量与负载均值loadavg同源采样周期定义为 2 秒/* Running averages - we need to be higher-res than loadavg */ #define PSI_FREQ (2*HZ1) /* 2 sec intervals */ #define EXP_10s 1677 /* 1/exp(2s/10s) as fixed-point */ #define EXP_60s 1981 /* 1/exp(2s/60s) */ #define EXP_300s 2034 /* 1/exp(2s/300s) */kernel/sched/psi.c平均值更新在 calc_avgs() 中完成采用指数衰减若期间系统空闲错过多个采样周期会以missed_periods参数补零衰减保证趋势连续。三、压力阈值监控注册触发器与 poll() 唤醒除了被动读取平均值用户还可以注册触发器trigger使用poll()在资源压力超过阈值时被唤醒。3.1 触发器语义与写接口格式触发器描述的是特定时间窗内的最大累计停滞时间。例如任意 500ms 窗口内累计停滞 100ms 即产生一次唤醒事件。注册方式打开/proc/pressure/下要监控资源的接口文件向其中写入阈值与时间窗some|full stall amount in us time window in us文档给出的两个示例向/proc/pressure/memory写入some 150000 1000000为部分内存停滞注册 150ms 阈值测量窗口 1 秒向/proc/pressure/io写入full 50000 1000000为 IO 完全停滞注册 50ms 阈值测量窗口 1 秒。3.2 触发器的关键约束逐条对应源码验证每个触发器需要独立 fd同一 psi 指标可以注册多个触发器也可以跨多个指标注册但每个触发器必须单独open()一次才能被独立 poll对已存在触发器的 fd 再写会返回EBUSY见 kernel/sched/psi.c 中psi_trigger_open/文件打开路径对PSI_FILE标志的复用检查。窗口范围 500ms ~ 10s源码中以WINDOW_MAX_US 1000000010s为上限L167下限 500ms 是为了防止过度频繁的轮询上限 10s 则是认为超过该量级后直接使用 psi 平均值更合适。对应地最小监控更新间隔为 50ms、最大 1s——因为 UPDATES_PER_WINDOW 固定为每窗口 10 次更新。非特权用户窗口必须是 2s 的整数倍psi_trigger_create()中直接校验!privileged window_us % 2000000则返回-EINVALkernel/sched/psi.c目的是限制非特权监控的资源消耗——这类触发器走普通的 2 秒周期平均聚合工作PSI_AVGS不需要启动实时RT轮询线程。阈值校验threshold_us 0 || threshold_us window_us返回-EINVAL窗口为 0 或超过 10s 同样-EINVALL1372–L1384。激活/去激活机制监控器仅在系统进入被监控指标对应的停滞状态时激活退出时去激活处于停滞状态期间psi 信号增长以每跟踪窗口 10 次的频率被监视。最小激活持续时间激活后至少保持一个跟踪窗口时长避免系统在停滞态边缘反复抖动导致监控器频繁启停。通知速率限制到用户空间的通知被限制为每跟踪窗口至多一次源码中now t-last_event_time t-win.size的判断update_triggers()。关闭即注销用于定义触发器的 fd 关闭时触发器自动注销psi_trigger_destroy()。从源码结构看特权触发器CAP_SYS_RESOURCE挂在rtpoll_triggers链表并由专门的 RT 轮询工作线程psi_rtpoll_worker()驱动以sched_set_fifo_low优先级运行L738–L754实现 50ms 级更新粒度非特权触发器则挂在avg_triggers链表由 2 秒周期的psi_avgs_work顺带检查L1403–L1424这也解释了为什么非特权窗口被强制为 2s 整数倍。3.3 用户空间监控示例完整可运行以下示例来自官方文档监控内存部分停滞1 秒跟踪窗口、150ms 阈值#include errno.h #include fcntl.h #include stdio.h #include poll.h #include string.h #include unistd.h /* * Monitor memory partial stall with 1s tracking window size * and 150ms threshold. */ int main() { const char trig[] some 150000 1000000; struct pollfd fds; int n; fds.fd open(/proc/pressure/memory, O_RDWR | O_NONBLOCK); if (fds.fd 0) { printf(/proc/pressure/memory open error: %s\n, strerror(errno)); return 1; } fds.events POLLPRI; if (write(fds.fd, trig, strlen(trig) 1) 0) { printf(/proc/pressure/memory write error: %s\n, strerror(errno)); return 1; } printf(waiting for events...\n); while (1) { n poll(fds, 1, -1); if (n 0) { printf(poll error: %s\n, strerror(errno)); return 1; } if (fds.revents POLLERR) { printf(got POLLERR, event source is gone\n); return 0; } if (fds.revents POLLPRI) { printf(event triggered!\n); } else { printf(unknown event received: 0x%x\n, fds.revents); return 1; } } return 0; }使用要点以O_RDWR | O_NONBLOCK打开压力文件fds.events设为POLLPRI写成功后poll()阻塞等待事件触发时revents置位POLLPRIPOLLERR表示事件源消失。由于示例使用了 1s 窗口且要求非特权窗口为 2s 整数倍普通用户直接编译运行该示例会收到EINVAL——需以 root/CAP_SYS_RESOURCE运行或将阈值窗口改为some 300000 2000000。四、Cgroup2 接口按容器/分组查看压力在CONFIG_CGROUPSy且挂载了 cgroup2 文件系统的系统上压力信息同样按 cgroup 分组跟踪cgroupfs 挂载点下的每个子目录都包含cpu.pressure、memory.pressure和io.pressure文件格式与/proc/pressure/完全一致。每个 cgroup 的 psi 监控器触发器的指定与用法和系统级完全相同——打开对应 cgroup 目录下的压力文件、写入阈值窗口、poll 唤醒。结合 1.1 节的说明cgroup 级别的 CPU FULL 具有系统级没有的语义该组所有非空闲任务都在等 CPU被组外任务占用或cpu.max限流这是排查容器 CPU 配额不足的核心依据。五、内核实现要点如何在热路径上低成本采样文档只描述用户可见行为而 kernel/sched/psi.c 文件头注释L108–L137解释了实现为何这样做精确测量每个状态耗时需要在每次任务变更时冻结系统时钟实践上不可扩展。因此内核采用每 CPU 本地记录 低频外推策略任务状态变化打点psi_task_change()L909–L924与psi_task_switch()L926–L1003在调度器的入队/出队/切换路径上更新任务标志TSK_RUNNING、TSK_IOWAIT、TSK_MEMSTALL、TSK_ONCPU等通过psi_group_change()L796–L883更新每个 runqueue 的psi_group_cpu计数NR_IOWAIT、NR_MEMSTALL、NR_RUNNING 等并用test_states()L243–L269推导出该 CPU 当前处于哪些 SOME/FULL 状态。内存停滞的进入/退出由psi_memstall_enter()/psi_memstall_leave()L1056–L1079在内存回收/缺页路径上标记。状态切换时间记账record_times()L764–L791在状态切换时把区间时长累加进该 CPU 的 IO_SOME/IO_FULL/MEM_SOME/MEM_FULL/CPU_SOME/CPU_FULL/NONIDLE 时间桶每次读写用 per-CPU seqcountpsi_write_begin/psi_read_begin保证聚合器读到的快照一致。2 秒周期聚合延迟工作psi_avgs_work()L578–L611周期性调用collect_percpu_times()L362–L415把各 CPU 的时间桶折叠成归一化样本——每个 CPU 按其非空闲时间加权消除负载不均甚至完全空闲 CPU 带来的偏差然后更新total累计值、喂入运行平均值并检查avg_triggers链表上的非特权触发器。若期间无任何任务活动时钟自动停摆恢复后通过missed_periods一次性补算保证不丢样本。特权触发器的高频路径进入被监控状态的停滞时psi_group_change()会调度 RT 轮询工作线程psi_rtpoll_work()L653–L736以最小rtpoll_min_period窗口/10为节拍反复采集window_update()L437–L461用线性插值近似滑动窗口内的信号增长从而在 50ms 粒度上判定阈值突破。六、开启方式与配置项配置选项CONFIG_PSIPressure stall information tracking位于 init/Kconfig选择 Y 后内核创建/proc/pressure/下的cpu、memory、io三个统计文件带 cgroup 支持的内核还会在 cgroup2 中提供cpu.pressure、memory.pressure、io.pressure。CONFIG_PSI_DEFAULT_DISABLEDinit/Kconfig默认关闭 PSI需在启动命令行传入psi1显式开启源码对应psi内核参数解析kernel/sched/psi.c。Kconfig 帮助文本同时说明该特性在任务唤醒/睡眠路径中增加了少量代码实际开销极低对 webserver、memcache 这类调度密集型工作负载无感只在 hackbench 之类的人为调度压力测试中可见若对内核用途不确定可谨慎选择默认关闭。适用前提总结需要CONFIG_PSIy或PSI_DEFAULT_DISABLEDpsi1的较新版本内核cgroup2 压力文件额外要求 cgroup2 文件系统已挂载非特权用户注册触发器时窗口必须取 2s 的整数倍。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考