Linux服务器内存被‘吃’光了?别慌,手把手教你用/proc/slabinfo定位内核SLAB泄露元凶
Linux服务器内存被‘吃’光了手把手教你用/proc/slabinfo定位内核SLAB泄露凌晨三点服务器告警铃声刺破了运维室的宁静。王工揉了揉酸胀的眼睛盯着监控大屏上那根几乎触顶的内存使用曲线——96%的占用率但top和free却显示不出明显的凶手。这种无形的内存吞噬往往比用户态应用的内存泄露更令人头疼。今天我们就来揭开这个运维噩梦的真面目内核SLAB泄露。1. 初诊区分用户态与内核态内存泄露当服务器内存异常增长时首先要确定问题发生在用户空间还是内核空间。这就像医生问诊时需要先确定是内科还是外科问题一样关键。典型症状对比特征用户态泄露内核态SLAB泄露内存增长趋势相对缓慢可能快速飙升可观测性通过top/ps容易发现常规命令难以定位影响范围通常限于单个进程影响整个系统常见原因应用未释放malloc的内存内核驱动或模块资源泄漏使用/proc/meminfo进行初步诊断cat /proc/meminfo | grep -E MemTotal|MemFree|Slab重点关注Slab字段的值特别是SUnreclaim不可回收的SLAB内存。如果这个值随时间持续增长基本可以确定是内核态的内存泄露。实战案例某次线上事故中/proc/meminfo显示如下演变# 启动后1小时 Slab: 42604 kB SReclaimable: 18096 kB SUnreclaim: 24508 kB # 运行24小时后 Slab: 168304 kB (295%) SReclaimable: 20128 kB (11%) SUnreclaim: 148176 kB (504%)这个数据清晰地展示了不可回收SLAB内存的异常增长。2. 深度排查解剖/proc/slabinfo确认是SLAB泄露后/proc/slabinfo就是我们的手术刀。这个文件记录了内核SLAB分配器的详细状态包含每个缓存的对象数量、大小等信息。2.1 解读slabinfo数据结构先看一个真实的slabinfo片段# name active_objs num_objs objsize objperslab pagesperslab kmalloc-8192 36 48 8192 4 8 kmalloc-4096 224 240 4096 8 8 kmalloc-2048 1014 1104 2048 16 8各列含义active_objs当前活跃对象数真正被使用的num_objs总对象数包括空闲的objsize每个对象的大小字节objperslab每个SLAB能存放的对象数pagesperslab每个SLAB占用的内存页数2.2 定位异常增长的SLAB缓存通过定期采集和对比数据找出增长异常的SLAB缓存# 采集当前slab状态 cat /proc/slabinfo slabinfo-$(date %s).log # 对比两个时间点的差异 diff -u slabinfo-1620000000.log slabinfo-1620086400.log关键技巧重点关注kmalloc-*这类通用缓存注意active_objs和num_objs的比值如果两者都在增长且比值稳定很可能是泄露大对象缓存如kmalloc-8192的泄露影响更严重3. 高级追踪启用内核调试功能当确定可疑的SLAB缓存后比如kmalloc-8192需要更深入的追踪分配源头。这需要启用内核的调试功能。3.1 配置内核SLAB追踪如果可能重新编译内核启用以下选项CONFIG_SLUB_DEBUGy CONFIG_SLUB_DEBUG_ONy或者动态启用特定缓存的追踪echo 1 /sys/kernel/slab/kmalloc-8192/trace3.2 分析分配调用栈查看特定SLAB缓存的分配记录cat /sys/kernel/slab/kmalloc-8192/alloc_calls典型输出示例315 __alloc_skb0x98/0x238 age430/366961/410069 pid0-1467 cpus1,3 29 pskb_expand_head0xa0/0x2b0 age22161/203241/396156 pid0-1467 cpus13.3 获取完整调用栈通过dump_stack()或SLUB调试功能获取调用栈Call Trace: [8079f940] __alloc_skb0x1e8/0x238 [807d195c] skbmgr_alloc_skb4k0xc8/0x124 [806baf60] RTMP_AllocateRxPacketBuffer0x40/0x1b0 [805ef068] pci_get_pkt_dynamic_page_ddone0x120/0x3bc分析技巧从下往上阅读调用栈找到最可能的应用层入口关注重复出现的函数调用模式注意没有对应释放操作的分配路径4. 根治方案修复与预防找到泄露点后真正的挑战才开始。内核内存泄露的修复需要格外谨慎。4.1 常见修复模式配对释放缺失// 错误示例 void handle_packet(struct sk_buff *skb) { struct metadata *meta kmalloc(sizeof(*meta), GFP_KERNEL); process_packet(meta); // 忘记 kfree(meta); } // 正确写法 void handle_packet(struct sk_buff *skb) { struct metadata *meta kmalloc(sizeof(*meta), GFP_KERNEL); if (!meta) return; process_packet(meta); kfree(meta); }错误的内存管理上下文// 在中断上下文中使用了可能睡眠的GFP_KERNEL irq_handler() { buf kmalloc(size, GFP_KERNEL); // 错误 } // 应使用GFP_ATOMIC irq_handler() { buf kmalloc(size, GFP_ATOMIC); }4.2 监控与预防体系建立长效监控机制定期检查脚本示例#!/bin/bash # 监控SLAB增长趋势 while true; do date /var/log/slab_monitor.log cat /proc/meminfo | grep Slab /var/log/slab_monitor.log awk /kmalloc/ {print $1,$2,$3} /proc/slabinfo /var/log/slab_monitor.log sleep 300 done关键预防措施对新内核模块进行严格的内存泄露测试在CI流程中加入SLAB泄露检查对生产环境建立基线监控设置合理的告警阈值5. 疑难案例解析最后分享一个真实案例的排查过程展示如何将上述技术综合运用。问题现象每24小时内存使用增长约5%slabtop显示dentry缓存异常增长但常规的drop_caches无法释放排查过程通过/proc/slabinfo确认dentry活跃对象持续增长使用ftrace追踪dentry分配路径echo 1 /sys/kernel/debug/tracing/events/kmem/kmalloc/enable cat /sys/kernel/debug/tracing/trace_pipe发现某个文件系统驱动的d_alloc调用异常频繁检查该驱动代码发现路径查找操作未正确释放dentry根本原因文件系统驱动在处理符号链接时没有正确释放中间生成的dentry结构体导致每次路径查找都泄露少量内存。随着系统运行时间增长这些微小泄露累积成GB级的内存占用。