问题说明
如果你某天发现通过“Free”查看内存几乎耗尽,但通过top/ps命令却看不出来用户态应用程序占用太多的内存空间,那么内核模块可能发生了内存泄露。
问题确认
- 统计所有应用程序占用的内存 U (单位是K):
$ grep Pss /proc/[1-9]*/smaps | awk '{total+=$2}; END {print total}'
1721106
- 查看系统内存总数 T 、空闲内存 F , 共享内存S ,缓存 C
$ free -h
total used free shared buff/cache available
Mem: 125G 95G 4.2G 4.0G 26G 25G
Swap: 9.4G 444M 8.9G
$ cat /proc/meminfo
MemTotal: 131748024 kB
MemFree: 4229544 kB
MemAvailable: 26634796 kB
Buffers: 141416 kB
Cached: 24657800 kB
SwapCached: 198316 kB
Active: 7972388 kB
Inactive: 19558436 kB
Active(anon): 4249920 kB
Inactive(anon): 2666784 kB
Active(file): 3722468 kB
Inactive(file): 16891652 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 9830396 kB
SwapFree: 9375476 kB
Dirty: 80 kB
Writeback: 0 kB
AnonPages: 2601440 kB
Mapped: 71828 kB
Shmem: 4185096 kB
Slab: 2607824 kB
SReclaimable: 2129004 kB
SUnreclaim: 478820 kB
KernelStack: 29616 kB
PageTables: 45636 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 75704408 kB
Committed_AS: 14023220 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 529824 kB
VmallocChunk: 34292084736 kB
HardwareCorrupted: 0 kB
AnonHugePages: 260096 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 125688 kB
DirectMap2M: 3973120 kB
DirectMap1G: 132120576 kB
- 正常情况下,应该有如下公式(K 为内核占用内存,这里忽略掉Swap内存):
T = U + K + S + C + F
由此可以计算出内核使用的内存 K。
原理分析
根据经验,一般内存泄露并耗尽内存的代码,一定是频繁申请释放内存的部分。
内核中可能会出现频繁申请释放的内存可能有:
- 内核管理数据结构,如task_struct,inode等,而这些代码一般都经过大量测试,出现问题的可能性不大。
- 内核IO子系统或者驱动,比如块设备的BIO,网络协议栈的SKB,存储网络设备驱动。
这里最可能出现问题的地方便是存储或者网络设备的驱动。
现场分析
Linux内核使用层次化内存管理的方法,每一层解决不同的问题,从下至上的关键部分如下:
- 物理内存管理,主要用于描述内存的布局和属性,主要有Node、Zone和Page三个结构,使内存按照Page为单位来进行管理;
- Buddy内存管理,主要解决外部碎片问题,使用get_free_pages等函数以Page的N次方为单位进行申请释放;
- Slab内存管理,主要解决内部碎片问题,可以按照使用者指定的大小批量申请内存(需要先创建对象缓存池);
- 内核缓存对象,使用Slab预先分配一些固定大小的缓存,使用kmalloc、vmalloc等函数以字节为单位进行内存申请释放。
接下来,我们首先要看内存是从哪个层次上泄露的(额外说明:还有很多诸如如大页内存,页缓存,块缓存等相关内存管理技术,他们都是从这几个层次里面申请内存,不是关键,这里全部忽略掉。)。
- 查看Buddy内存使用情况:
$ cat /proc/buddyinfo
Node 0, zone DMA 0 1 1 0 2 1 1 0 0 1 3
Node 0, zone DMA32 3222 6030 3094 3627 379 0 0 0 0 0 0
Node 0, zone Normal 13628 0 0 0 0 0 0 0 0 0 0
Node 1, zone Normal 73167 165265 104556 17921 2120 144 1 0 0 0 0
$ cat /proc/buddyinfo | awk '{sum=0;for(i=5;i<=NF;i++) sum+=$i*(2^(i-5))};{total+=sum/256};{print $1 " " $2 " " $3 " " $4 "\t : " sum/256 "M"} END {print "total\t\t\t : " total "M"}'
Node 0, zone DMA : 14.5234M
Node 0, zone DMA32 : 245.07M
Node 0, zone Normal : 53.2344M
Node 1, zone Normal : 3921.41M
total : 4234.24M
从中我们可以看出Buddy一共分配出去多少内存。
- 查看Slab内存使用情况:
$ slabtop -o
Active / Total Objects (% used) : 3522231 / 6345435 (55.5%)
Active / Total Slabs (% used) : 148128 / 148128 (100.0%)
Active / Total Caches (% used) : 74 / 107 (69.2%)
Active / Total Size (% used) : 1297934.98K / 2593929.78K (50.0%)
Minimum / Average / Maximum Object : 0.01K / 0.41K / 15.88K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
1449510 666502 45% 1.06K 48317 30 1546144K xfs_inode
1229592 967866 78% 0.10K 31528 39 126112K buffer_head
1018560 375285 36% 0.06K 15915 64 63660K kmalloc-64
643216 322167 50% 0.57K 11486 56 367552K radix_tree_node
350826 147688 42% 0.38K 8353 42 133648K blkdev_requests
310421 131953 42% 0.15K 5857 53 46856K xfs_ili
273420 95765 35% 0.19K 6510 42 52080K dentry
174592 36069 20% 0.25K 2728 64 43648K kmalloc-256
155680 155680 100% 0.07K 2780 56 11120K Acpi-ParseExt
88704 34318 38% 0.50K 1386 64 44352K kmalloc-512
85176 52022 61% 0.19K 2028 42 16224K kmalloc-192
59580 59580 100% 0.11K 1655 36 6620K sysfs_dir_cache
43031 42594 98% 0.21K 1163 37 9304K vm_area_struct
35392 30850 87% 0.12K 553 64 4424K kmalloc-128
35070 20418 58% 0.09K 835 42 3340K kmalloc-96
34304 34304 100% 0.03K 268 128 1072K kmalloc-32
$ cat /proc/slabinfo
slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
kvm_async_pf 0 0 136 60 2 : tunables 0 0 0 : slabdata 0 0 0
kvm_vcpu 0 0 16256 2 8 : tunables 0 0 0 : slabdata 0 0 0
kvm_mmu_page_header 0 0 168 48 2 : tunables 0 0 0 : slabdata 0 0 0
xfs_dqtrx 0 0 528 62 8 : tunables 0 0 0 : slabdata 0 0 0
xfs_dquot 0 0 472 69 8 : tunables 0 0 0 : slabdata 0 0 0
xfs_icr 0 0 144 56 2 : tunables 0 0 0 : slabdata 0 0 0
xfs_ili 131960 310421 152 53 2 : tunables 0 0 0 : slabdata 5857 5857 0
xfs_inode 666461 1449510 1088 30 8 : tunables 0 0 0 : slabdata 48317 48317 0
xfs_efd_item 8120 8280 400 40 4 : tunables 0 0 0 : slabdata 207 207 0
xfs_da_state 2176 2176 480 68 8 : tunables 0 0 0 : slabdata 32 32 0
xfs_btree_cur 1248 1248 208 39 2 : tunables 0 0 0 : slabdata 32 32 0
xfs_log_ticket 12981 13200 184 44 2 : tunables 0 0 0 : slabdata 300 300 0
nfsd4_openowners 0 0 440 37 4 : tunables 0 0 0 : slabdata 0 0 0
rpc_inode_cache 51 51 640 51 8 : tunables 0 0 0 : slabdata 1 1 0
ext4_groupinfo_4k 4440 4440 136 60 2 : tunables 0 0 0 : slabdata 74 74 0
ext4_inode_cache 4074 5921 1048 31 8 : tunables 0 0 0 : slabdata 191 191 0
ext4_xattr 276 276 88 46 1 : tunables 0 0 0 : slabdata 6 6 0
ext4_free_data 3264 3264 64 64 1 : tunables 0 0 0 : slabdata 51 51 0
ext4_allocation_context 2048 2048 128 64 2 : tunables 0 0 0 : slabdata 32 32 0
ext4_io_end 1785 1785 80 51 1 : tunables 0 0 0 : slabdata 35 35 0
ext4_extent_status 20706 20706 40 102 1 : tunables 0 0 0 : slabdata 203 203 0
jbd2_journal_handle 2720 2720 48 85 1 : tunables 0 0 0 : slabdata 32 32 0
jbd2_journal_head 4680 4680 112 36 1 : tunables 0 0 0 : slabdata 130 130 0
jbd2_revoke_table_s 256 256 16 256 1 : tunables 0 0 0 : slabdata 1 1 0
jbd2_revoke_record_s 4096 4096 32 128 1 : tunables 0 0 0 : slabdata 32 32 0
scsi_cmd_cache 7056 7272 448 36 4 : tunables 0 0 0 : slabdata 202 202 0
UDPLITEv6 0 0 1152 28 8 : tunables 0 0 0 : slabdata 0 0 0
UDPv6 728 728 1152 28 8 : tunables 0 0 0 : slabdata 26 26 0
tw_sock_TCPv6 0 0 256 64 4 : tunables 0 0 0 : slabdata 0 0 0
TCPv6 405 405 2112 15 8 : tunables 0 0 0 : slabdata 27 27 0
uhci_urb_priv 0 0 56 73 1 : tunables 0 0 0 : slabdata 0 0 0
cfq_queue 27790 27930 232 70 4 : tunables 0 0 0 : slabdata 399 399 0
bsg_cmd 0 0 312 52 4 : tunables 0 0 0 : slabdata 0 0 0
mqueue_inode_cache 36 36 896 36 8 : tunables 0 0 0 : slabdata 1 1 0
hugetlbfs_inode_cache 106 106 608 53 8 : tunables 0 0 0 : slabdata 2 2 0
configfs_dir_cache 0 0 88 46 1 : tunables 0 0 0 : slabdata 0 0 0
dquot 2048 2048 256 64 4 : tunables 0 0 0 : slabdata 32 32 0
kioctx 0 0 576 56 8 : tunables 0 0 0 : slabdata 0 0 0
userfaultfd_ctx_cache 0 0 128 64 2 : tunables 0 0 0 : slabdata 0 0 0
pid_namespace 0 0 2176 15 8 : tunables 0 0 0 : slabdata 0 0 0
user_namespace 0 0 280 58 4 : tunables 0 0 0 : slabdata 0 0 0
posix_timers_cache 0 0 248 66 4 : tunables 0 0 0 : slabdata 0 0 0
UDP-Lite 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0
RAW 1530 1530 960 34 8 : tunables 0 0 0 : slabdata 45 45 0
UDP 1024 1024 1024 32 8 : tunables 0 0 0 : slabdata 32 32 0
tw_sock_TCP 10944 11328 256 64 4 : tunables 0 0 0 : slabdata 177 177 0
TCP 2886 3842 1920 17 8 : tunables 0 0 0 : slabdata 226 226 0
blkdev_queue 118 225 2088 15 8 : tunables 0 0 0 : slabdata 15 15 0
blkdev_requests 147485 350826 384 42 4 : tunables 0 0 0 : slabdata 8353 8353 0
blkdev_ioc 2262 2262 104 39 1 : tunables 0 0 0 : slabdata 58 58 0
fsnotify_event_holder 5440 5440 24 170 1 : tunables 0 0 0 : slabdata 32 32 0
fsnotify_event 15912 16252 120 68 2 : tunables 0 0 0 : slabdata 239 239 0
sock_inode_cache 12478 13260 640 51 8 : tunables 0 0 0 : slabdata 260 260 0
net_namespace 0 0 4608 7 8 : tunables 0 0 0 : slabdata 0 0 0
shmem_inode_cache 3264 3264 680 48 8 : tunables 0 0 0 : slabdata 68 68 0
Acpi-ParseExt 155680 155680 72 56 1 : tunables 0 0 0 : slabdata 2780 2780 0
Acpi-Namespace 16422 16422 40 102 1 : tunables 0 0 0 : slabdata 161 161 0
taskstats 1568 1568 328 49 4 : tunables 0 0 0 : slabdata 32 32 0
proc_inode_cache 12352 12544 656 49 8 : tunables 0 0 0 : slabdata 256 256 0
sigqueue 1632 1632 160 51 2 : tunables 0 0 0 : slabdata 32 32 0
bdev_cache 858 858 832 39 8 : tunables 0 0 0 : slabdata 22 22 0
sysfs_dir_cache 59580 59580 112 36 1 : tunables 0 0 0 : slabdata 1655 1655 0
inode_cache 15002 17050 592 55 8 : tunables 0 0 0 : slabdata 310 310 0
dentry 96235 273420 192 42 2 : tunables 0 0 0 : slabdata 6510 6510 0
iint_cache 0 0 80 51 1 : tunables 0 0 0 : slabdata 0 0 0
selinux_inode_security 22681 23205 80 51 1 : tunables 0 0 0 : slabdata 455 455 0
buffer_head 968560 1229592 104 39 1 : tunables 0 0 0 : slabdata 31528 31528 0
vm_area_struct 43185 43216 216 37 2 : tunables 0 0 0 : slabdata 1168 1168 0
mm_struct 860 860 1600 20 8 : tunables 0 0 0 : slabdata 43 43 0
files_cache 1887 1887 640 51 8 : tunables 0 0 0 : slabdata 37 37 0
signal_cache 3595 3724 1152 28 8 : tunables 0 0 0 : slabdata 133 133 0
sighand_cache 2373 2445 2112 15 8 : tunables 0 0 0 : slabdata 163 163 0
task_xstate 4920 5226 832 39 8 : tunables 0 0 0 : slabdata 134 134 0
task_struct 2303 2420 2944 11 8 : tunables 0 0 0 : slabdata 220 220 0
anon_vma 27367 27392 64 64 1 : tunables 0 0 0 : slabdata 428 428 0
shared_policy_node 5525 5525 48 85 1 : tunables 0 0 0 : slabdata 65 65 0
numa_policy 248 248 264 62 4 : tunables 0 0 0 : slabdata 4 4 0
radix_tree_node 321897 643216 584 56 8 : tunables 0 0 0 : slabdata 11486 11486 0
idr_layer_cache 953 975 2112 15 8 : tunables 0 0 0 : slabdata 65 65 0
dma-kmalloc-8192 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-4096 0 0 4096 8 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-2048 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-1024 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-512 0 0 512 64 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-256 0 0 256 64 4 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-128 0 0 128 64 2 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-64 0 0 64 64 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-32 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-16 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-8 0 0 8 512 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-192 0 0 192 42 2 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-96 0 0 96 42 1 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-8192 314 340 8192 4 8 : tunables 0 0 0 : slabdata 85 85 0
kmalloc-4096 983 1024 4096 8 8 : tunables 0 0 0 : slabdata 128 128 0
kmalloc-2048 4865 4928 2048 16 8 : tunables 0 0 0 : slabdata 308 308 0
kmalloc-1024 10084 10464 1024 32 8 : tunables 0 0 0 : slabdata 327 327 0
kmalloc-512 34318 88704 512 64 8 : tunables 0 0 0 : slabdata 1386 1386 0
kmalloc-256 35482 174592 256 64 4 : tunables 0 0 0 : slabdata 2728 2728 0
kmalloc-192 52022 85176 192 42 2 : tunables 0 0 0 : slabdata 2028 2028 0
kmalloc-128 30732 35392 128 64 2 : tunables 0 0 0 : slabdata 553 553 0
kmalloc-96 20418 35070 96 42 1 : tunables 0 0 0 : slabdata 835 835 0
kmalloc-64 375761 1018560 64 64 1 : tunables 0 0 0 : slabdata 15915 15915 0
kmalloc-32 34304 34304 32 128 1 : tunables 0 0 0 : slabdata 268 268 0
kmalloc-16 18432 18432 16 256 1 : tunables 0 0 0 : slabdata 72 72 0
kmalloc-8 25088 25088 8 512 1 : tunables 0 0 0 : slabdata 49 49 0
kmem_cache_node 683 704 64 64 1 : tunables 0 0 0 : slabdata 11 11 0
kmem_cache 576 576 256 64 4 : tunables 0 0 0 : slabdata 9 9 0
通过如上命令,我们可以确定哪些Slab缓存占用的内存最多。
从现场数据分析,发现Buddy分配出去了100多G的内存,Slab只使用了几G的内存。这说明泄露的内存并不是Slab及kmalloc泄露出去的,是从Buddy泄露的。
Buddy分配出去的内存可能会被Slab,大页内存、页面缓存、块缓存、驱动,应用程序缺页映射或mmap等需要以页为单位进行内存申请的内核代码使用。而这些部分中,最可能出现问题的依旧是驱动。