Linux内核内存泄漏排查实战从/proc/meminfo到slabinfo的完整诊断流程凌晨三点服务器监控系统突然发出刺耳的警报声。运维工程师小王揉了揉惺忪的睡眼发现线上某台关键服务器的内存使用率正以每小时2%的速度稳步攀升。top和htop显示不出任何异常进程但free -m却清楚地表明可用内存正在被某种神秘力量吞噬。这种场景对于Linux系统管理员来说并不陌生——我们很可能遇到了最棘手的内核态内存泄漏问题。1. 内存泄漏排查的起点理解/proc/meminfo当系统内存出现异常消耗时/proc/meminfo是我们的第一站。这个虚拟文件提供了Linux内存子系统的全景视图远比free命令详细得多。让我们先看一个典型的生产环境输出示例$ cat /proc/meminfo MemTotal: 8010408 kB MemFree: 2345678 kB MemAvailable: 3456789 kB Buffers: 12345 kB Cached: 2345678 kB SwapCached: 0 kB Active: 3456789 kB Inactive: 1234567 kB Active(anon): 2345678 kB Inactive(anon): 123456 kB Active(file): 1111111 kB Inactive(file): 987654 kB ... Slab: 456789 kB SReclaimable: 123456 kB SUnreclaim: 333333 kB在这些指标中Slab字段尤为关键——它表示内核SLAB分配器占用的内存总量。当发现MemFree持续下降而Slab持续上升时基本可以确定遇到了内核态内存泄漏。1.1 关键指标解析SReclaimable可回收的slab内存如dentries和inodes缓存SUnreclaim不可回收的slab内存潜在的内存泄漏源实战技巧建议使用以下命令定时采集内存快照形成时间序列数据# 每5分钟记录一次内存状态 while true; do date /tmp/meminfo.log grep -E MemTotal|MemFree|Slab|SReclaimable|SUnreclaim /proc/meminfo /tmp/meminfo.log sleep 300 done2. 深入SLAB分配器解剖/proc/slabinfo确认了内核态内存泄漏的存在后下一步是定位具体的slab缓存。/proc/slabinfo文件提供了所有活跃slab缓存的详细信息$ cat /proc/slabinfo | head -20 slabinfo - version: 2.1 # name active_objs num_objs objsize objperslab pagesperslab : tunables limit batchcount sharedfactor : slabdata active_slabs num_slabs sharedavail nf_conntrack 102 102 320 51 4 : tunables 0 0 0 : slabdata 2 2 0 kmalloc-8192 36 48 8192 4 8 : tunables 0 0 0 : slabdata 12 12 0 kmalloc-4096 224 240 4096 8 8 : tunables 0 0 0 : slabdata 30 30 0 kmalloc-2048 1014 1104 2048 16 8 : tunables 0 0 0 : slabdata 69 69 02.1 关键字段解读字段名称描述active_objs当前活跃的对象数量最需要关注的指标num_objs包括空闲对象在内的总对象数objsize每个对象的大小字节objperslab每个slab页可以容纳的对象数量pagesperslab每个slab占用的内存页数排查策略定期采集slabinfo快照建议间隔1小时使用awk筛选增长最快的slab缓存awk /^[a-zA-Z]/{print $1,$2} /proc/slabinfo | sort -k2 -n重点关注kmalloc-*系列的通用缓存和特定驱动相关的缓存3. 高级诊断技术slab追踪与调试当确定可疑的slab缓存后比如kmalloc-8192我们需要更深入的诊断手段。这通常需要启用内核调试功能3.1 动态slab追踪对于较新的内核4.x可以直接激活slab追踪而不需要重新编译内核# 启用追踪 echo 1 /sys/kernel/slab/kmalloc-8192/trace # 查看分配调用栈 cat /sys/kernel/slab/kmalloc-8192/alloc_calls # 查看释放调用栈 cat /sys/kernel/slab/kmalloc-8192/free_calls3.2 内核调试符号与SystemTap对于生产环境使用SystemTap进行实时追踪更为安全# 监控kmalloc-8192的分配 stap -e probe kernel.function(kmalloc).return { if ($return ! 0 $size 8192) { print_backtrace(); } }注意事项需要安装内核调试符号包kernel-debuginfo可能对系统性能有轻微影响建议在测试环境先验证脚本4. 实战案例网络驱动内存泄漏分析让我们看一个真实的案例——网络驱动导致的内存泄漏。通过前述方法我们发现kmalloc-8192缓存持续增长且主要分配来自__alloc_skb。4.1 调用栈分析通过slab追踪获得的典型调用栈[ffffffff81345ad4] __alloc_skb0x98/0x238 [ffffffff815d195c] skbmgr_alloc_skb4k0xc8/0x124 [ffffffff816baf60] RTMP_AllocateRxPacketBuffer0x40/0x1b0 [ffffffff815ef068] pci_get_pkt_dynamic_page_ddone0x120/0x3bc [ffffffff815ef3d0] pci_rx_dma_done_handle0xcc/0x1944.2 根本原因定位分析驱动代码后发现// 错误的实现DMA完成后未释放skb static void pci_rx_dma_done_handle(struct device *dev) { struct sk_buff *skb alloc_skb(8192, GFP_KERNEL); // ... DMA操作... // 遗漏了skb的释放 } // 正确的实现应该包含释放 static void pci_rx_dma_done_handle(struct device *dev) { struct sk_buff *skb alloc_skb(8192, GFP_KERNEL); // ... DMA操作... if (dma_success) { kfree_skb(skb); // 必须释放 } }4.3 验证与修复使用kmemleak工具验证内存泄漏echo scan /sys/kernel/debug/kmemleak cat /sys/kernel/debug/kmemleak修复驱动代码后重新编译并加载模块监控/proc/slabinfo确认kmalloc-8192不再增长5. 预防与监控体系建设事后补救不如事前预防。建议建立以下监控体系5.1 Prometheus监控配置# slub监控指标 - name: node_slub rules: - record: slub:leak:potential expr: | increase(node_slab_SUnreclaim[1h]) 102400000 and rate(node_memory_MemFree_bytes[1h]) 05.2 自动化诊断脚本#!/bin/bash # 自动slab泄漏检测脚本 SLAB_LEAK_THRESHOLD100 # MB # 获取不可回收slab内存 unreclaim$(grep SUnreclaim /proc/meminfo | awk {print $2}) unreclaim_mb$((unreclaim / 1024)) if [ $unreclaim_mb -gt $SLAB_LEAK_THRESHOLD ]; then # 找出增长最快的slab echo 检测到潜在slab内存泄漏${unreclaim_mb}MB echo Top 5增长最快的slab缓存 awk /^[a-z]/{print $1,$2} /proc/slabinfo | sort -k2 -rn | head -5 # 自动收集诊断信息 mkdir -p /tmp/slab_debug cp /proc/meminfo /tmp/slab_debug/ cp /proc/slabinfo /tmp/slab_debug/ dmesg /tmp/slab_debug/dmesg.log echo 诊断信息已保存至 /tmp/slab_debug fi5.3 内核参数调优建议对于容易发生内存泄漏的场景可以调整以下参数# 减少slab缓存的最大大小 echo 512000 /proc/sys/vm/slab_max # 更激进的slab回收 echo 100 /proc/sys/vm/vfs_cache_pressure