1. 为什么内存分析绕不开 vmstat
做嵌入式开发的人,尤其是碰过 QNX 的,大概都有过这种经历:系统跑着跑着突然变慢,某个进程响应时间从几毫秒飙到几百毫秒,日志里什么异常都没有,CPU 占用看起来也不高。这时候你打开 top 或者 hogs 看了一眼,发现 CPU 确实不忙,但系统就是不流畅。问题大概率出在内存上。
QNX 作为实时操作系统,内存管理机制和 Linux 有本质区别。Linux 有虚拟内存交换分区,物理内存不够了可以把不活跃的页换到磁盘上,虽然慢但至少不会立刻崩。QNX 不一样,它是一个微内核架构的实时系统,绝大多数嵌入式场景下根本不配交换分区,物理内存就是硬上限。一旦内存分配出现问题,轻则进程被内核杀掉,重则整个系统进入不可预测的状态。
vmstat 这个工具在 QNX 里就是用来观察内存和系统整体运行状态的。它不像 Linux 的 vmstat 那样输出一大堆字段,QNX 的 vmstat 输出更精简,但每一个字段背后都对应着内核里实实在在的计数器。很多人第一次用 vmstat 的时候会被那一列列数字搞懵,不知道哪个该关注、哪个可以忽略、数值到多少算异常。这篇文章就是把我自己在实际项目里反复用 vmstat 排查内存问题的经验整理出来,从字段含义到实操判断,再到常见坑,尽量讲透。
不管你是刚接触 QNX 的新手,还是已经用过 vmstat 但总觉得没抓到重点的老手,下面这些内容应该都能帮你少走一些弯路。我会先讲清楚 vmstat 每个字段到底在说什么,然后讲怎么用它定位问题,最后分享几个我踩过的坑和排查技巧。
2. vmstat 输出字段逐个拆解
2.1 先搞清楚 vmstat 的基本用法
QNX 下的 vmstat 用法很直接,基本格式是vmstat [间隔秒数] [次数]。比如vmstat 1 10就是每秒采样一次,一共采十次。如果不带参数直接运行,它只输出一行当前值就退出了。实际排查问题的时候,我一般用vmstat 2让它一直跑,每两秒刷新一行,这样既能观察到趋势,又不会因为刷新太快导致输出刷屏看不清。
有一点需要注意,QNX 的 vmstat 在不同版本里输出格式略有差异。QNX 6.5、QNX 7.0、QNX 7.1 的字段名和列顺序可能有细微变化。我下面讲的是以 QNX 7.x 为主,如果你用的是更早的版本,建议先跑一下use vmstat看看帮助信息,确认字段名对得上。
vmstat 的输出通常长这样:
cpu memory page system idle free cache fault active pgin pgout intr csw 85% 120M 45M 1203 12 0 0 450 320不同版本的列可能多一些或少一些,但核心字段就这几个。下面我逐个说。
2.2 cpu idle:别被它骗了
cpu idle表示 CPU 空闲百分比。这个值高不代表系统就没问题。我遇到过好几次,idle 一直在 80% 以上,但系统响应就是慢。原因可能是某个高优先级线程在等内存分配,虽然它没占 CPU,但整个系统的实时性已经被破坏了。
还有一种情况是 idle 看起来正常,但intr和csw异常高。这说明 CPU 大量时间花在中断处理和上下文切换上,真正干活的时间被碎片化了。这种时候光看 idle 是看不出问题的,得结合后面的字段一起判断。
我的经验是,idle 低于 20% 要警惕 CPU 瓶颈,但 idle 高于 80% 同时系统响应慢,那问题大概率不在 CPU 上,得往内存和中断方向查。
2.3 memory free 和 cache:此 cache 非彼 cache
memory free是当前完全空闲的物理内存量。memory cache是内核用于缓存的内存,这部分内存可以被回收再利用。很多人看到 free 很低就紧张,其实不一定。如果 cache 很大,说明内存被有效利用了,系统需要的时候可以快速回收。
但 QNX 的 cache 和 Linux 的 page cache 不是一回事。QNX 的 cache 更多是指内核对象缓存、文件系统缓存这些。在嵌入式场景下,cache 通常不会特别大,因为很多 QNX 系统跑在无文件系统的环境里,或者文件系统访问很少。
判断内存是否紧张,我一般看 free 的绝对值。如果 free 低于总内存的 10%,而且 cache 也不大,那就要小心了。比如一个 512M 内存的系统,free 长期低于 50M,这时候任何一次稍大的内存分配都可能失败。
还有一个细节:QNX 的内存分配是按页来的,页大小通常是 4KB。所以 free 的值一定是 4KB 的整数倍。如果你看到 free 不是 4KB 对齐的,那可能是显示单位的问题,比如显示的是 KB 但实际值经过了取整。
2.4 page fault 和 active:内存压力的核心指标
page fault是页错误次数。在 QNX 里,页错误主要发生在两种情况下:一是进程访问了尚未映射的虚拟地址,内核需要分配物理页并建立映射;二是访问了被换出或尚未从文件系统加载的页。由于 QNX 通常没有交换分区,第二种情况相对少见,主要是第一种。
active表示当前活跃的页数,也就是最近被访问过的页。这个值高说明内存访问局部性好,低说明内存访问分散。但 QNX 的 active 统计方式和 Linux 不同,它更多是反映内核认为“最近用过”的页,不一定和实际硬件访问完全对应。
我一般把 page fault 和 active 结合起来看。如果 page fault 持续很高,同时 active 也在增长,说明进程在不断申请新内存,可能是内存泄漏。如果 page fault 高但 active 稳定,可能是进程在频繁映射和解除映射,比如反复 mmap/munmap,这种操作本身开销就大,会拖慢系统。
2.5 pgin 和 pgout:页面换入换出
pgin和pgout是页面换入和换出的次数。在标准 QNX 系统里,如果没有配置交换分区,这两个值应该一直是 0。如果你看到非零值,说明系统配置了某种形式的内存回收机制,或者有文件系统页的换入换出。
我见过一个案例,pgin 和 pgout 持续有值,但系统并没有配交换分区。后来查出来是某个进程在反复读取一个大文件,文件系统的页缓存不断换入换出。虽然这不是传统意义上的内存交换,但同样会消耗 I/O 带宽和 CPU 时间,影响实时性。
所以看到 pgin/pgout 非零,第一件事是确认系统有没有配交换,第二件事是查有没有频繁的文件 I/O。
2.6 intr 和 csw:系统开销的晴雨表
intr是中断次数,csw是上下文切换次数。这两个值本身不直接反映内存问题,但它们能告诉你系统整体有多“忙”。如果 intr 和 csw 都很高,说明系统在频繁处理中断和切换任务,这时候即使内存充足,系统响应也会变慢。
我遇到过一次内存问题导致的 csw 飙升。某个进程因为内存分配失败,不断重试,每次重试都触发一次上下文切换。结果 csw 从正常的几百涨到几千,CPU 大量时间花在切换上,真正干活的线程反而得不到调度。
所以看 vmstat 的时候,intr 和 csw 要作为背景参考。如果它们异常高,先解决中断和调度问题,再回头看内存。
3. 用 vmstat 定位内存问题的实操方法
3.1 建立基线:先知道正常是什么样
排查问题的第一步永远是建立基线。系统刚启动、负载正常的时候,跑一次vmstat 5 12,记录一分钟的数据。重点记下 free、cache、page fault、intr、csw 这几个值的范围。
比如一个典型的 QNX 工控板,512M 内存,启动后 free 大概在 300M 左右,cache 在 50M 左右,page fault 每秒几十次,intr 几百,csw 几百。这就是基线。后面如果 free 掉到 100M 以下,或者 page fault 涨到几千,那就是异常。
基线不是跑一次就完事。不同负载场景下基线不同。空载、半载、满载各跑一次,心里才有数。我一般会把这些基线值记在项目笔记里,下次再遇到类似硬件配置,直接拿来参考。
3.2 趋势比绝对值更重要
单看一行 vmstat 输出,很难判断问题。关键是看趋势。比如 free 从 300M 慢慢降到 100M,花了几个小时,那可能是正常的内存增长。但如果 free 在几分钟内从 300M 掉到 50M,那肯定有问题。
我习惯用vmstat 2 > /tmp/vmstat.log把输出重定向到文件,跑一段时间后拿回来分析。QNX 上可以用awk或者写个简单脚本提取关键列,画成趋势图。没有图形环境的话,直接看数字变化也能看出端倪。
有一次我排查一个偶发的系统卡顿,vmstat 跑了整整一天。最后发现每次卡顿前,page fault 都会有一个尖峰,然后 free 骤降。顺着这个线索查下去,发现是某个定时任务在特定条件下会申请一大块内存,申请失败后不断重试,导致系统短暂卡死。
3.3 结合其他工具交叉验证
vmstat 给的是系统级视图,要定位到具体进程,还得配合其他工具。QNX 下常用的有pidin、hogs、showmem等。
pidin mem可以看每个进程的内存使用情况,包括代码段、数据段、堆栈等。如果 vmstat 显示 free 在降,用pidin mem找到哪个进程的内存在涨,基本就能锁定目标。
showmem可以看更详细的内存分配信息,包括内核内存池的使用情况。有时候进程内存看起来正常,但内核内存池快满了,这时候 vmstat 的 free 可能还没明显变化,但系统已经开始不稳定了。
我一般的排查顺序是:vmstat 发现异常 -> pidin mem 定位进程 -> showmem 确认内核内存状态 -> 结合代码分析原因。
3.4 一个真实案例的完整排查过程
之前有个项目,设备运行几天后就会自动重启。日志里没有任何错误信息,看门狗直接触发。用 vmstat 观察,发现重启前 free 内存会从 200M 快速降到 20M 左右,然后系统就挂了。
第一步,用vmstat 1盯着看,确认 free 下降的速度和规律。发现大概每 10 分钟掉 10M 左右,很稳定,说明是持续泄漏,不是突发性的。
第二步,用pidin mem每隔几分钟抓一次所有进程的内存数据。对比几次数据后,发现某个服务进程的堆内存一直在涨,其他进程都稳定。
第三步,查这个服务进程的代码。发现它有个定时任务,每次执行都会申请一块内存,但异常分支里忘了释放。正常情况下异常分支不会走到,但某个特定条件下会触发,于是内存就一点点漏掉了。
第四步,修复后重新跑 vmstat,free 稳定在 180M 左右不再下降,问题解决。
这个案例里,vmstat 的作用是发现问题和确认修复效果,真正的定位靠的是 pidin mem 和代码审查。但如果没有 vmstat 第一步的发现,后面根本无从下手。
4. 常见问题与排查技巧实录
4.1 vmstat 输出里哪些值可以忽略
不是所有字段都需要关注。比如pgin和pgout,在无交换分区的系统里长期为 0,你就不用管它。active这个值在 QNX 里参考意义有限,因为它的统计口径和实际内存压力关联度不高,我一般只看趋势,不看绝对值。
真正需要重点盯的是:free、page fault、intr、csw。这四个值基本能覆盖大部分内存和系统负载问题。
4.2 free 内存突然掉很多但找不到进程
这种情况通常是内核内存泄漏,或者某个驱动在疯狂申请内存。pidin mem看的是进程内存,内核内存不在里面。这时候要用showmem看内核内存池。
我遇到过一次网卡驱动的问题,驱动在处理某种异常包的时候会泄漏内核缓冲区。进程内存完全正常,但 free 一直在掉。用 showmem 发现内核内存池的使用量在涨,最后定位到驱动。
4.3 page fault 高但系统看起来正常
page fault 高不一定有问题。如果系统在频繁加载和卸载模块,或者进程在反复 mmap/munmap,page fault 自然会高。关键看系统响应是否受影响。
如果 page fault 高但 idle 也高,csw 正常,那可能只是某个进程在做正常的内存操作。如果 page fault 高同时 idle 低、csw 高,那就是内存操作拖累了系统,需要优化。
4.4 vmstat 采样间隔怎么选
采样间隔太短,输出刷屏,而且单次采样的瞬时波动可能误导判断。采样间隔太长,可能错过短时间的异常。
我的经验是:初步观察用 5 秒间隔,跑几分钟看趋势。发现异常后,缩短到 1 秒,抓细节。如果要长时间监控,用 10 秒或 30 秒间隔,减少对系统的影响。
注意:vmstat 本身也会消耗少量 CPU 和内存,在资源极度紧张的系统中,高频采样可能加重问题。排查问题时可以适当采样,但不要长期开着高频 vmstat。
4.5 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| free 持续下降 | 内存泄漏 | pidin mem 查进程,showmem 查内核 |
| page fault 尖峰后系统卡顿 | 大块内存申请失败重试 | 查定时任务和异常分支 |
| csw 异常高 | 调度频繁,可能内存分配失败导致重试 | 结合 intr 和进程状态分析 |
| pgin/pgout 非零 | 文件系统页换入换出 | 查文件 I/O 频率 |
| free 低但 cache 高 | 正常缓存行为 | 观察 cache 能否回收 |
| 系统重启前 free 骤降 | 内存耗尽触发看门狗 | 抓重启前的 vmstat 日志 |
4.6 几个我踩过的坑
第一个坑:只看 free 不看 cache。有次 free 很低,我以为是泄漏,查了半天没找到。后来发现 cache 很大,手动触发回收后 free 就回来了。QNX 的 cache 回收机制和 Linux 不同,不能想当然。
第二个坑:忽略 intr 和 csw。有次内存问题其实不严重,但 csw 高得离谱,系统响应很差。我先去查内存,浪费了很多时间。后来发现是某个中断处理程序写得不好,导致大量上下文切换。先解决中断问题,内存问题反而变得可接受了。
第三个坑:在错误的时间点采样。有次系统卡顿是偶发的,我跑 vmstat 的时候正好不卡,什么异常都没看到。后来改成长时间后台记录,才抓到卡顿瞬间的数据。
第四个坑:忘了看内核内存。进程内存正常不代表系统内存正常。内核内存泄漏更隐蔽,危害也更大。
5. 把 vmstat 用成条件反射
vmstat 这个工具,说简单也简单,就几个字段。但要用好,得把它变成条件反射。系统一有异常,第一反应就是跑 vmstat 看看。看得多了,自然就能从数字的细微变化里读出问题。
我现在的习惯是,每个新项目上线前,先跑一遍基线,记下来。运行过程中,定期抽查 vmstat,和基线对比。一旦发现偏离,立刻深入排查。这样大部分内存问题都能在造成严重后果之前被发现。
还有一点,vmstat 的输出要结合代码和系统配置一起看。同样的数字,在不同系统上含义可能不同。比如一个内存充裕的系统,free 低一点没关系;但一个内存紧张的系统,free 低一点就可能致命。脱离上下文看 vmstat,很容易误判。
最后分享一个小技巧:如果系统支持,可以把 vmstat 的输出和进程内存数据一起记录到日志里,出问题的时候直接翻日志,比事后复现要高效得多。我在几个项目里都这么干,省了很多排查时间。