Linux 内核内存占用分析实战:3个关键文件与22M内存的精确计算

在Linux系统运维和性能调优的日常工作中,内核内存占用分析往往是最令人头疼的环节之一。当系统出现内存不足告警时,我们常常需要快速定位究竟是用户进程还是内核本身消耗了过多内存。本文将带你深入 /proc/meminfo dmesg /proc/vmallocinfo 这三个关键文件,通过实战案例演示如何精确计算内核各部分内存占用。

1. 内核内存管理的核心机制

Linux内核内存管理是一个复杂的多层次系统,理解其基本架构是进行精确分析的前提。现代Linux内核将内存划分为几个关键区域:

  • 代码段(text) :存放内核可执行代码
  • 数据段(data) :包含初始化数据
  • BSS段 :存放未初始化静态变量
  • Slab分配器 :管理内核对象缓存
  • vmalloc区域 :非连续内存映射区
  • 页表 :维护虚拟到物理地址的映射

这些区域的内存消耗会反映在不同的系统文件中。在开始分析前,我们需要先了解几个关键概念:

# 查看系统总内存和内核版本
$ free -h
$ uname -r

提示:不同内核版本的内存管理实现可能有细微差异,建议在分析时确认内核版本号。

2. 通过dmesg获取内核启动内存信息

系统启动时,内核会输出内存布局的详细信息到内核环缓冲区。这些信息可以通过 dmesg 命令获取:

$ dmesg | grep Memory
Memory: 180784K/524288K available (6912K kernel code, 327K rwdata, 2380K rodata, 400K init, 424K bss, 15824K reserved, 327680K cma-reserved, 0K highmem)

这段输出包含了丰富的信息:

字段 描述 大小
kernel code 内核代码段 6912K
rwdata 可读写数据段 327K
rodata 只读数据段 2380K
init 初始化段(后续释放) 400K
bss 未初始化数据段 424K
reserved 内核保留内存 15824K

这些静态区域的内存占用相对固定,不会随着系统运行而大幅变化。其中 reserved 部分特别值得关注,因为它包含了内核为关键操作预留的内存。

3. 分析Slab分配器内存占用

Slab分配器是内核用于管理小块内存的核心机制,它通过缓存常用对象类型(如inode、dentry等)来提高性能。我们可以通过 /proc/meminfo 获取Slab使用情况:

$ cat /proc/meminfo | grep -E 'Slab|SReclaimable|SUnreclaim'
Slab:              6060 kB
SReclaimable:      1848 kB
SUnreclaim:        4212 kB

这里有几个关键指标:

  • Slab :Slab分配器管理的总内存
  • SReclaimable :可回收的Slab内存
  • SUnreclaim :不可回收的Slab内存

Slab内存主要分为两类:

  1. kmalloc分配的内存 :通过 kmalloc() 函数分配,物理连续
  2. 专用缓存 :如inode_cache、dentry等对象缓存

我们可以进一步查看详细缓存信息:

$ cat /proc/slabinfo

4. 解析vmalloc内存区域

vmalloc区域用于分配大块虚拟连续但物理不连续的内存,常用于内核模块、设备驱动等场景。分析vmalloc内存需要查看 /proc/vmallocinfo

$ cat /proc/vmallocinfo | head -5
0x8c000000-0xa0000000 335544320 iotable_init+0x0/0xb4 phys=8c000000 ioremap 
0xa0800000-0xa0802000 8192 of_iomap+0x2c/0x34 phys=a01000 ioremap
0xa0806000-0xa0808000 8192 bpf_prog_alloc+0x34/0xa4 pages=1 vmalloc
0xa0808000-0xa080d000 20480 of_iomap+0x2c/0x34 phys=2098000 ioremap
0xa080e000-0xa0810000 8192 devm_ioremap_nocache+0x38/0x74 phys=20c9000 ioremap

要计算纯vmalloc分配的内存(不包括ioremap等),可以使用:

$ grep vmalloc /proc/vmallocinfo | awk '{total+=$2};END {print total/1024" KB"}'
572 KB

5. 页表内存占用分析

每个进程都有自己的页表,用于将虚拟地址映射到物理地址。内核自身也有页表结构,这部分内存占用可以通过 /proc/meminfo 查看:

$ cat /proc/meminfo | grep PageTables
PageTables:        104 kB

在大型系统中,特别是运行大量进程或使用大内存应用时,页表内存可能变得相当可观。

6. 完整的内核内存计算脚本

将上述各部分整合,我们可以创建一个自动化计算脚本:

#!/bin/bash

# 从dmesg获取静态内存信息
kernel_code=$(dmesg | grep Memory | awk -F'kernel code, ' '{print $2}' | awk '{print $1}')
rwdata=$(dmesg | grep Memory | awk -F'rwdata, ' '{print $2}' | awk '{print $1}')
rodata=$(dmesg | grep Memory | awk -F'rodata, ' '{print $2}' | awk '{print $1}')
init=$(dmesg | grep Memory | awk -F'init, ' '{print $2}' | awk '{print $1}')
bss=$(dmesg | grep Memory | awk -F'bss, ' '{print $2}' | awk '{print $1}')
reserved=$(dmesg | grep Memory | awk -F'reserved, ' '{print $2}' | awk '{print $1}')

# 从/proc/meminfo获取动态内存信息
slab=$(grep Slab /proc/meminfo | awk '{print $2}')
page_tables=$(grep PageTables /proc/meminfo | awk '{print $2}')
vmalloc=$(grep vmalloc /proc/vmallocinfo | awk '{total+=$2};END {print total/1024}')

# 计算总和(转换为KB)
static_mem=$((kernel_code + rwdata + rodata + init + bss + reserved))
dynamic_mem=$(echo "$slab + $page_tables + $vmalloc" | bc)
total_mem=$(echo "$static_mem + $dynamic_mem" | bc)

# 输出结果
echo "内核内存占用分析报告"
echo "====================="
echo "1. 静态内存区域:"
echo "   - 代码段: ${kernel_code}KB"
echo "   - 数据段: $((rwdata + rodata))KB (rwdata: ${rwdata}KB, rodata: ${rodata}KB)"
echo "   - BSS段: ${bss}KB"
echo "   - 初始化段: ${init}KB"
echo "   - 保留内存: ${reserved}KB"
echo "   -> 总计: ${static_mem}KB"
echo ""
echo "2. 动态内存区域:"
echo "   - Slab分配器: ${slab}KB"
echo "   - 页表: ${page_tables}KB"
echo "   - vmalloc: ${vmalloc}KB"
echo "   -> 总计: ${dynamic_mem}KB"
echo ""
echo "3. 内核总内存占用: ${total_mem}KB"

运行此脚本将输出类似以下结果:

内核内存占用分析报告
=====================
1. 静态内存区域:
   - 代码段: 6912KB
   - 数据段: 2707KB (rwdata: 327KB, rodata: 2380KB)
   - BSS段: 424KB
   - 初始化段: 400KB
   - 保留内存: 15824KB
   -> 总计: 26267KB

2. 动态内存区域:
   - Slab分配器: 6060KB
   - 页表: 104KB
   - vmalloc: 572KB
   -> 总计: 6736KB

3. 内核总内存占用: 33003KB

7. 高级分析与优化建议

掌握了基本的内存计算后,我们可以进一步分析内存使用模式:

Slab内存优化

  • 查看具体缓存使用: sudo slabtop
  • 清理可回收缓存: echo 2 > /proc/sys/vm/drop_caches

vmalloc内存分析

  • 按调用者统计: cat /proc/vmallocinfo | awk '{print $3}' | sort | uniq -c | sort -nr
  • 检查是否有异常的驱动或模块占用过多内存

页表优化

  • 考虑使用大页(HugePages)减少页表开销
  • 监控页表增长趋势: watch -n 1 'grep PageTables /proc/meminfo'

在实际生产环境中,我曾遇到一个案例:某台服务器频繁出现内存不足告警,但用户进程内存总和并不高。通过上述方法分析发现,是某个内核模块通过vmalloc异常申请了大量内存。最终通过更新驱动版本解决了问题。

8. 可视化分析工具

虽然命令行工具足够强大,但可视化工具能提供更直观的分析:

  1. 使用gnuplot绘制内存趋势图
# 记录一段时间的内存数据
while true; do
    date +"%T" >> mem.log
    grep -E 'Slab|PageTables|Vmalloc' /proc/meminfo >> mem.log
    sleep 60
done

# 使用gnuplot生成图表
gnuplot << EOF
set terminal png
set output "memory_usage.png"
set xdata time
set timefmt "%H:%M:%S"
set xlabel "Time"
set ylabel "Memory (KB)"
plot "mem.log" using 1:4 title "Slab" with lines, \
     "" using 1:7 title "PageTables" with lines, \
     "" using 1:10 title "Vmalloc" with lines
EOF
  1. 使用Python生成饼图
import matplotlib.pyplot as plt

labels = ['Code', 'Data', 'Slab', 'vmalloc', 'PageTables']
sizes = [6912, 2707, 6060, 572, 104]
colors = ['gold', 'yellowgreen', 'lightcoral', 'lightskyblue', 'pink']

plt.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=140)
plt.axis('equal')
plt.title('Kernel Memory Usage Breakdown')
plt.savefig('kernel_mem_pie.png')

这些可视化工具可以帮助我们快速发现内存使用的异常模式。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐