3. 内存泄漏定义
内核内存泄漏(Kernel Memory Leak)是指内核在运行过程中,动态分配的内存(通过 kmalloc、vmalloc、alloc_pages 等接口)在不再使用时,没有被正确释放,导致这部分内存永久丢失,无法被系统重新利用。与用户空间内存泄漏不同,内核内存泄漏往往更隐蔽、危害更大,因为内核管理着整个系统的物理内存资源。
核心定义: 内核内存泄漏 = 动态分配的内存对象丢失了所有引用指针,且未调用对应的释放函数(如
kfree、vfree、free_pages),导致该内存块永久脱离内核内存管理器的追踪。
3.1 泄漏的典型症状
内核内存泄漏不会像用户态程序那样立即崩溃,而是通过一系列系统级症状逐步显现。下表总结了最常见的泄漏症状及其排查线索:
| 症状类别 | 具体表现 | 排查线索 |
|---|---|---|
| 系统可用内存持续减少 | /proc/meminfo 中 MemFree 和 MemAvailable 随时间线性下降,即使没有新进程启动 |
使用 free -h 或 cat /proc/meminfo 定期采样 |
| Slab 缓存异常增长 | /proc/slabinfo 中特定 slab 缓存(如 kmalloc-*、dentry)持续增长,不回落 |
slabtop -s c 按缓存大小排序观察 |
| OOM(Out-Of-Memory)触发 | 系统日志(dmesg)出现 Out of memory: Killed process 信息,OOM Killer 被迫终止进程 |
dmesg | grep -i "oom" 查看被杀进程 |
| 内核线程/进程 RSS 异常 | /proc/<pid>/status 中 VmRSS 持续增长,尤其是内核工作线程 |
ps aux --sort=-%mem 或 top -p <pid> |
| 系统响应变慢、卡顿 | 内存压力导致频繁回收(reclaim)、swap 换入换出,CPU iowait 升高 | vmstat 1 观察 si/so 和 wa 列 |
经验法则: 如果系统在无业务负载变化的情况下,
MemFree 每小时下降超过 5%,且无法通过 drop_caches 回收,基本可以判定存在内核内存泄漏。
3.2 泄漏对系统稳定性的影响
内核内存泄漏对系统稳定性的破坏是渐进式且多层次的。下面通过一张流程图展示泄漏从发生到系统崩溃的完整演化路径:
3.3 泄漏对系统各组件的具体影响
内核内存泄漏不仅影响内存子系统,还会波及整个系统的运行稳定性。下表从不同维度分析了泄漏带来的连锁反应:
| 影响维度 | 具体表现 | 严重程度 |
|---|---|---|
| 内存子系统 | Buddy 系统可用页面减少,NR_FREE_PAGES 趋近于 0;Slab 缓存膨胀,active_anon 异常升高 |
★★★★★ |
| 进程调度 | 内存压力导致 direct reclaim 路径被频繁触发,进程在分配内存时阻塞,调度延迟增加 |
★★★★☆ |
| I/O 子系统 | kswapd 大量换出匿名页,导致磁盘 I/O 压力骤增,iowait 飙升,影响存储性能 |
★★★★☆ |
| 网络子系统 | sk_buff 泄漏导致网络吞吐量下降,连接超时,甚至网卡 OOM | ★★★☆☆ |
| 文件系统 | dentry/inode 缓存泄漏导致目录遍历变慢,文件打开失败,No space left on device 误报 |
★★★★☆ |
| 设备驱动 | DMA 缓冲区泄漏导致设备无法提交新 I/O 请求,硬件超时,驱动报错 | ★★★★★ |
3.4 泄漏的隐蔽性与危害
内核内存泄漏之所以是驱动开发中的“头号杀手”,在于其三个核心特性:
- 累积性: 单次泄漏量可能很小(几十字节),但经过数小时甚至数天的累积,会耗尽整个系统的内存。
- 不可回收性: 与用户态不同,内核泄漏的内存无法通过
kill进程来回收,只能重启系统。 - 连带破坏性: OOM Killer 在杀死进程时,可能误杀关键系统服务(如
systemd、sshd),导致系统完全不可用。
实战警示: 某生产环境中的 NVMe 驱动因
bio 结构体泄漏,每处理 100 万次 I/O 泄漏 4KB。系统运行 72 小时后,MemFree 从 32GB 降至 128MB,最终 OOM Killer 杀死了 MySQL 进程,导致 30 分钟业务中断。事后排查发现,驱动在 I/O 完成回调中遗漏了 bio_put() 调用。
3.5 泄漏的常见根因分类
根据内核驱动开发中的实际案例,内存泄漏的根因可以归纳为以下几类:
| 根因类型 | 典型场景 | 示例代码(错误写法) |
|---|---|---|
| 遗漏释放 | 在错误路径或提前返回时忘记调用 kfree |
|
| 引用丢失 | 将指针覆盖或赋值为 NULL,原内存无法访问 | |
| 循环累积 | 在循环或定时器中不断分配但不释放 | |
| 引用计数失衡 | kref、module_put 等引用计数只增不减 | |
| 数据结构未清理 | 链表、哈希表中的节点在移除时未释放内存 | |
排查起点: 当怀疑驱动存在内存泄漏时,首先检查
/proc/slabinfo 中驱动使用的 slab 缓存是否持续增长。如果 kmalloc-128 或自定义 slab 的 active_objs 随时间线性上升,基本可以锁定泄漏源。