Linux内核分析之内存管理-15

韩乔落

第15章:内存统计与调试

基于 Linux 6.12.38 源码


15.1 内存统计概述

15.1.1 统计接口

Linux 内核提供多种内存统计接口:

接口 用途
/proc/meminfo 系统内存信息
/proc/vmstat 虚拟内存统计
/proc/zoneinfo Zone 详细信息
/proc/pid/maps 进程内存映射
/proc/pid/smaps 进程内存统计
sysfs (cgroup) 内存控制组统计

15.1.2 统计架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────────┐
│ 内存统计架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ Zone 统计 │ │ Node 统计 │ │
│ │ vm_zone_stat │ │ vm_node_stat │ │
│ └────────────────┘ └────────────────┘ │
│ │ │ │
│ └────────────┬───────────┘ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ 全局计数器 │ │
│ │ vm_event_state │ │
│ └──────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ /proc 接口 │ │
│ │ (meminfo, vmstat) │ │
│ └──────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

15.2 vm_stat 结构

15.2.1 统计项定义

位置: include/linux/vmstat.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/* Zone 统计项 */
enum zone_stat_item {
/* LRU 统计 */
NR_ANON_MAPPED, /* 匿名映射页面 */
NR_FILE_MAPPED, /* 文件映射页面 */
NR_FILE_PAGES, /* 文件页面 */
NR_FILE_DIRTY, /* 脏文件页面 */
NR_WRITEBACK, /* 正在回写的页面 */
NR_ANON_THPS, /* 匿名透明巨页 */
NR_FILE_THPS, /* 文件透明巨页 */
NR_SHMEM, /* shmem 页面 */
NR_SHMEM_THPS, /* shmem 巨页 */

/* Slab 统计 */
NR_SLAB_RECLAIMABLE, /* 可回收 slab */
NR_SLAB_UNRECLAIMABLE, /* 不可回收 slab */

/* 页面状态 */
NR_PAGETABLE, /* 页表使用的页面 */
NR_KERNEL_STACK_KB, /* 内核栈使用 */

/* 稳定页面 */
NR_UNSTABLE_NFS, /* 不稳定 NFS 页面 */
NR_VMSCAN_WRITE, /* kswapd 写入的页面 */
NR_VMSCAN_IMMEDIATE, /* 立即回收的页面 */

/* 更多统计项... */
NR_VM_ZONE_STAT_ITEMS,
};

/* Node 统计项 */
enum node_stat_item {
NR_LRU_BASE, /* LRU 基础 */
NR_INACTIVE_ANON, /* 非活跃匿名页 */
NR_ACTIVE_ANON, /* 活跃匿名页 */
NR_INACTIVE_FILE, /* 非活跃文件页 */
NR_ACTIVE_FILE, /* 活跃文件页 */
NR_UNEVICTABLE, /* 不可驱逐页 */

/* 工作集 */
NR_WORKINGSET_NODES, /* 工作集节点 */
NR_WORKINGSET_REFAULT, /* 工作集重故障 */
NR_WORKINGSET_RESTORE, /* 工作集恢复 */

/* 更多统计项... */
NR_VM_NODE_STAT_ITEMS,
};

15.2.2 统计操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* Zone 统计操作 */
static inline void __mod_zone_page_state(struct zone *zone,
enum zone_stat_item item, long delta)
{
atomic_long_add(delta, &zone->vm_stat[item]);
atomic_long_add(delta, &vm_zone_stat[item]);
}

/* Node 统计操作 */
static inline void __mod_node_page_state(struct pglist_data *pgdat,
enum node_stat_item item, long delta)
{
atomic_long_add(delta, &pgdat->vm_stat[item]);
atomic_long_add(delta, &vm_node_stat[item]);
}

/* 全局计数器 */
extern atomic_long_t vm_zone_stat[NR_VM_ZONE_STAT_ITEMS];
extern atomic_long_t vm_node_stat[NR_VM_NODE_STAT_ITEMS];

15.3 vm_event 计数器

15.3.1 事件定义

位置: include/linux/vm_event_item.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
enum vm_event_item {
/* 分配/释放 */
PGPGIN, /* 页面换入 */
PGPGOUT, /* 页面换出 */
PSWPIN, /* Swap 换入 */
PSWPOUT, /* Swap 换出 */
PGFREE, /* 页面释放 */

/* 页面激活 */
PGACTIVATE, /* 页面激活 */
PGDEACTIVATE, /* 页面停用 */
PGLAZYFREE, /* 懒惰释放 */

/* 缺页异常 */
PGFAULT, /* 缺页异常 */
PGMAJFAULT, /* 主缺页异常 (需要 I/O) */

/* 页面回收 */
PGSTEAL_KSWAPD, /* kswapd 回收 */
PGSTEAL_DIRECT, /* 直接回收 */
PGSCAN_KSWAPD, /* kswapd 扫描 */
PGSCAN_DIRECT, /* 直接扫描 */

/* 压缩 */
COMPACTSTALL, /* 压缩延迟 */
COMPACTFAIL, /* 压缩失败 */
COMPACTSUCCESS, /* 压缩成功 */
KCOMPACTD_WAKE, /* kcompactd 唤醒 */

/* 巨页 */
THP_FAULT_ALLOC, /* 巨页分配 */
THP_FAULT_FALLBACK, /* 巨页回退 */
THP_COLLAPSE_ALLOC, /* 巨页折叠 */
THP_SPLIT_PAGE, /* 巨页分裂 */

/* 交换 */
SWAP_RA, /* Swap 预读 */
SWAP_RA_HIT, /* Swap 预读命中 */

/* 更多事件... */
NR_VM_EVENT_ITEMS
};

15.3.2 事件计数

位置: include/linux/vmstat.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* 每核事件状态 */
struct vm_event_state {
unsigned long event[NR_VM_EVENT_ITEMS];
};

DECLARE_PER_CPU(struct vm_event_state, vm_event_states);

/* 计数事件 */
static inline void count_vm_event(enum vm_event_item item)
{
this_cpu_inc(vm_event_states.event[item]);
}

static inline void count_vm_events(enum vm_event_item item, long delta)
{
this_cpu_add(vm_event_states.event[item], delta);
}

/* 汇总所有 CPU 的事件 */
void all_vm_events(unsigned long *ret)
{
int cpu;
int i;

memset(ret, 0, NR_VM_EVENT_ITEMS * sizeof(unsigned long));

for_each_online_cpu(cpu) {
struct vm_event_state *this = &per_cpu(vm_event_states, cpu);
for (i = 0; i < NR_VM_EVENT_ITEMS; i++)
ret[i] += this->event[i];
}
}

15.4 /proc/meminfo

15.4.1 meminfo 实现

位置: mm/page_alloc.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
static int meminfo_proc_show(struct seq_file *m, void *v)
{
struct sysinfo i;
unsigned long committed;
unsigned long allowed;
unsigned long cached;
unsigned long pages[NR_LRU_LISTS];

/* 获取系统信息 */
si_meminfo(&i);
si_swapinfo(&i);

/* 获取 LRU 页面 */
for (lru = LRU_INACTIVE_ANON; lru <= LRU_ACTIVE_FILE; lru++)
pages[lru] = global_node_page_state(NR_LRU_BASE + lru);

/* 输出信息 */
seq_printf(m,
"MemTotal: %8lu kB\n"
"MemFree: %8lu kB\n"
"MemAvailable: %8lu kB\n"
"Buffers: %8lu kB\n"
"Cached: %8lu kB\n"
"SwapCached: %8lu kB\n"
"Active: %8lu kB\n"
"Inactive: %8lu kB\n"
"Active(anon): %8lu kB\n"
"Inactive(anon): %8lu kB\n"
"Active(file): %8lu kB\n"
"Inactive(file): %8lu kB\n"
"Unevictable: %8lu kB\n"
"Mlocked: %8lu kB\n"
/* 更多信息... */
, K(i.totalram)
, K(i.freeram)
, K(available)
, K(i.bufferram)
, K(cached)
/* 更多字段... */
);

return 0;
}

15.4.2 meminfo 字段说明

1
2
3
4
5
6
7
8
9
10
11
# 主要字段
MemTotal: 16384000 kB # 总内存
MemFree: 512000 kB # 空闲内存
MemAvailable: 8192000 kB # 可用内存 (包含可回收)
Buffers: 256000 kB # 缓冲区缓存
Cached: 5120000 kB # 页面缓存
SwapCached: 128000 kB # Swap 缓存
Active: 6144000 kB # 活跃页面
Inactive: 4096000 kB # 非活跃页面
Unevictable: 25600 kB # 不可驱逐页面
Mlocked: 25600 kB # 锁定内存

15.5 /proc/vmstat

15.5.1 vmstat 实现

位置: mm/vmstat.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static int vmstat_show(struct seq_file *m, void *v)
{
unsigned long *e = v;
unsigned long nr = e - (unsigned long *)v;

if (nr < NR_VM_ZONE_STAT_ITEMS)
seq_printf(m, "%s %lu\n",
zone_stat_name(nr),
global_zone_page_state(nr));
else if (nr < NR_VM_ZONE_STAT_ITEMS + NR_VM_NUMA_EVENT_ITEMS)
seq_printf(m, "%s %lu\n",
numa_stat_name(nr - NR_VM_ZONE_STAT_ITEMS),
global_numa_event_state(nr - NR_VM_ZONE_STAT_ITEMS));
else if (nr < NR_VM_ZONE_STAT_ITEMS + NR_VM_NUMA_EVENT_ITEMS +
NR_VM_NODE_STAT_ITEMS)
seq_printf(m, "%s %lu\n",
node_stat_name(nr - NR_VM_ZONE_STAT_ITEMS -
NR_VM_NUMA_EVENT_ITEMS),
global_node_page_state(nr - NR_VM_ZONE_STAT_ITEMS -
NR_VM_NUMA_EVENT_ITEMS));
else
seq_printf(m, "%s %lu\n",
vm_event_name(nr - NR_VM_ZONE_STAT_ITEMS -
NR_VM_NUMA_EVENT_ITEMS - NR_VM_NODE_STAT_ITEMS),
vm_events[nr - NR_VM_ZONE_STAT_ITEMS -
NR_VM_NUMA_EVENT_ITEMS - NR_VM_NODE_STAT_ITEMS]);

return 0;
}

15.5.2 vmstat 字段说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 主要字段
nr_free_pages 12345678 # 空闲页面
nr_inactive_anon 1234567 # 非活跃匿名页
nr_active_anon 1234567 # 活跃匿名页
nr_inactive_file 1234567 # 非活跃文件页
nr_active_file 1234567 # 活跃文件页
nr_unevictable 12345 # 不可驱逐页

pgpgin 12345678 # 页面换入
pgpgout 12345678 # 页面换出
pswpin 123456 # Swap 换入
pswpout 123456 # Swap 换出

pgfault 123456789 # 缺页异常
pgmajfault 12345 # 主缺页异常

pgalloc_dma 123 # DMA 分配
pgalloc_normal 12345678 # Normal 分配

compact_stall 1234 # 压缩延迟
compact_success 12345 # 压缩成功
compact_fail 123 # 压缩失败

thp_fault_alloc 12345 # 巨页分配
thp_collapse_alloc 1234 # 巨页折叠
thp_split_page 123 # 巨页分裂

15.6 /proc/zoneinfo

15.6.1 zoneinfo 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static int zoneinfo_show(struct seq_file *m, void *v)
{
struct zone *zone = v;

/* Zone 基本信息 */
seq_printf(m, "Node %d, zone %s", zone_to_nid(zone), zone->name);

/* 水位信息 */
seq_printf(m,
" pages free %lu\n"
" min %lu\n"
" low %lu\n"
" high %lu\n"
" scanned %lu\n"
" spanned %lu\n"
" present %lu\n"
" managed %lu\n",
zone_page_state(zone, NR_FREE_PAGES),
min_wmark_pages(zone),
low_wmark_pages(zone),
high_wmark_pages(zone),
zone_page_state(zone, NR_PAGES_SCANNED),
zone->spanned_pages,
zone->present_pages,
zone->managed_pages);

/* 保护信息 */
seq_printf(m,
" protection: (%ld",
zone->lowmem_reserve[0]);
for (i = 1; i < ARRAY_SIZE(zone->lowmem_reserve); i++)
seq_printf(m, ", %ld", zone->lowmem_reserve[i]);
seq_printf(m, ")\n");

/* 统计信息 */
for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++)
seq_printf(m, " %-12s %lu\n",
zone_stat_name(i),
zone_page_state(zone, i));

return 0;
}

15.7 进程内存统计

15.7.1 /proc/pid/smaps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 查看进程内存映射统计
cat /proc/1234/smaps

# 输出示例
# 7f8a0000000-7f8a0002000 r-xp 00000000 08:01 12345 /lib/libc.so.6
# Size: 8 kB
# KernelPageSize: 4 kB
# MMUPageSize: 4 kB
# Rss: 4 kB
# Pss: 2 kB
# Shared_Clean: 0 kB
# Shared_Dirty: 0 kB
# Private_Clean: 4 kB
# Private_Dirty: 0 kB
# Referenced: 4 kB
# Anonymous: 0 kB
# LazyFree: 0 kB
# AnonHugePages: 0 kB
# ShmemPmdMapped: 0 kB
# Shared_Hugetlb: 0 kB
# Private_Hugetlb: 0 kB
# Swap: 0 kB
# SwapPss: 0 kB
# Locked: 0 kB
# THPeligible: 0
# VmFlags: rd ex mr mw me dw

15.7.2 字段说明

字段 说明
Size 映射大小
Rss 驻留集大小 (实际物理内存)
Pss 按比例共享内存
Shared_Clean 共享干净页面
Shared_Dirty 共享脏页面
Private_Clean 私有干净页面
Private_Dirty 私有脏页面
Swap Swap 使用
Locked 锁定内存

15.8 调试工具

15.8.1 用户空间工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# vmstat - 虚拟内存统计
vmstat 1 5 # 每秒刷新,共 5 次

# 输出
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 1 0 0 123456 12345 123456 0 0 123 45 1234 1234 10 5 85 0 0

# slabtop - Slab 缓存统计
slabtop

# 输出
# Active / Total Objects (% used) : 123456 / 234567 (52.6%)
# Active / Total Slabs (% used) : 1234 / 2345 (52.6%)
# Active / Total Caches (% used) : 123 / 234 (52.6%)
# Active / Total Size (% used) : 12345 / 23456 (52.6%)
# Minimum / Average / Maximum Object : 0.01 / 0.10 / 1.00

# pmap - 内存映射
pmap 1234 # 显示进程 1234 的内存映射
pmap -x 1234 # 详细格式

# smem - 内存使用统计
smem -m # 按进程显示
smem -u # 按用户显示

15.8.2 内核调试选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 内存调试选项
CONFIG_DEBUG_KERNEL # 启用内核调试
CONFIG_DEBUG_INFO # 调试信息
CONFIG_DEBUG_FS # debugfs

# 内存泄漏检测
CONFIG_DEBUG_KMEMLEAK # kmemleak
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=400

# 页面调试
CONFIG_DEBUG_PAGEALLOC # 页面分配调试
CONFIG_PAGE_OWNER # 页面所有者跟踪
CONFIG_PAGE_POISONING # 页面投毒

# Slab 调试
CONFIG_SLUB_DEBUG # SLUB 调试
CONFIG_SLUB_DEBUG_ON # 默认启用 SLUB 调试

# 内存统计
CONFIG_VM_EVENT_COUNTERS # VM 事件计数器
CONFIG_DEBUG_VM # VM 调试
CONFIG_DEBUG_VM_PGFLAGS # 页面标志调试

15.9 kmemleak

15.9.1 kmemleak 概述

kmemleak 是内核内存泄漏检测工具,自动检测未释放的内存。

15.9.2 使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 启用 kmemleak
# 1. 内核配置
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=400

# 2. 启动参数
kmemleak=on

# 3. 挂载 debugfs
mount -t debugfs none /sys/kernel/debug

# 4. 扫描内存泄漏
echo scan > /sys/kernel/debug/kmemleak

# 5. 查看结果
cat /sys/kernel/debug/kmemleak

# 6. 清除标记
echo clear > /sys/kernel/debug/kmemleak

15.9.3 kmemleak 输出

1
2
3
4
5
6
7
8
unreferenced object 0xffff888123456789 (size 1024):
comm "systemd", pid 1, jiffies 4294967295
hex dump (first 32 bytes):
01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 ................
backtrace:
[<ffffffff00000000>] kmalloc+0x123/0x456
[<ffffffff00000000>] some_function+0x789/0xabc
[<ffffffff00000000>] another_function+0xdef/0x012

15.10 page_owner

15.10.1 page_owner 概述

page_owner 跟踪每个页面的分配者,帮助调试页面分配问题。

15.10.2 使用方法

1
2
3
4
5
6
7
8
9
10
11
12
# 启用 page_owner
# 1. 内核配置
CONFIG_PAGE_OWNER=y

# 2. 启动参数
page_owner=on

# 3. 使用
cat /sys/kernel/debug/page_owner

# 过滤特定函数
cat /sys/kernel/debug/page_owner | grep "alloc_pages"

15.10.3 page_owner 输出

1
2
3
4
5
6
7
8
9
10
Page allocated via order 0, mask 0x42c0ca(GFP_KERNEL|__GFP_NOWARN|__GFP_COMP|__GFP_RECLAIMABLE),
PFN 0x12345
alloc_pages+0x123/0x456
some_function+0x789/0xabc
another_function+0xdef/0x012

Page allocated via order 9, mask 0x42c0ca(GFP_KERNEL|__GFP_NOWARN|__GFP_COMP|__GFP_RECLAIMABLE),
PFN 0x67890
alloc_pages+0x123/0x456
huge_page_alloc+0x789/0xabc

15.11 页面投毒

15.11.1 page_poisoning 概述

页面投毒用特定模式填充释放的页面,检测 use-after-free 错误。

15.11.2 配置和使用

1
2
3
CONFIG_PAGE_POISONING=y
CONFIG_PAGE_POISONING_NO_SANITY=n
CONFIG_PAGE_POISONING_ZERO=n
1
2
3
4
5
6
# 启动参数
page_poison=on

# 检查投毒
echo 1 > /sys/kernel/debug/page_poison/poison
echo 1 > /sys/kernel/debug/page_poison/poison_enabled

15.12 内存热点分析

15.12.1 perf 使用

1
2
3
4
5
6
7
8
9
10
11
# 内存分配分析
perf record -e kmem:kmalloc -a sleep 10
perf report

# 缺页异常分析
perf record -e page-faults -a sleep 10
perf report

# 内存访问分析
perf mem record -a sleep 10
perf mem report

15.12.2 eBPF 工具

1
2
3
4
5
6
7
8
# memleak - BCC 内存泄漏检测
memleak -p $(pidof myapp)

# bashreadahead - 预读分析
bashreadahead

# slabratetop - Slab 分配率
slabratetop

15.13 本章小结

本章介绍了 Linux 6.12 的内存统计与调试:

  1. 统计接口: /proc/meminfo、/proc/vmstat、/proc/zoneinfo
  2. vm_stat: Zone/Node 统计项和操作
  3. vm_event: 事件计数器,跟踪内存操作
  4. 进程统计: /proc/pid/smaps,进程内存映射
  5. 调试工具: vmstat、slabtop、pmap、smem
  6. 内核调试: kmemleak、page_owner、page_poisoning
  7. 性能分析: perf、eBPF 工具

内存统计和调试工具对于理解和优化内存使用至关重要。


附录:内存管理完整系列

本系列 15 章涵盖了 Linux 6.12 内存管理的全部内容:

  1. 概述 - 内存管理架构和设计
  2. 物理内存模型 - Node、Zone、Page、Watermark
  3. 物理内存分配器 - Buddy System
  4. 虚拟地址空间 - 用户/内核空间布局
  5. 页表管理 - 多级页表、页表操作
  6. 虚拟内存区域 (VMA) - VMA 管理
  7. 页面分配 - alloc_pages、kmalloc
  8. Slab 分配器 - SLUB 实现
  9. 缺页异常处理 - 页面错误处理
  10. 页面回收 - LRU、kswapd、直接回收
  11. 内存映射 - mmap、mprotect、munmap
  12. 匿名内存与页缓存 - 堆、页缓存、回写
  13. 内存压缩与迁移 - Compaction、Migration
  14. 内存控制组 - memcg 限制和统计
  15. 内存统计与调试 - 统计接口和调试工具
  • Title: Linux内核分析之内存管理-15
  • Author: 韩乔落
  • Created at : 2026-01-20 21:39:55
  • Updated at : 2026-02-24 14:04:51
  • Link: https://jelasin.github.io/2026/01/20/Linux内核分析之内存管理-15/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments