2019独角兽企业重金招聘Python工程师标准>>>
问题说明
在一台CEPH存储节点上,随着运行时间的增加,可用内存越来越少。在应用程序全部退出后,释放全部缓存,可用内存依旧没有增加。重启节点后,所有内存占用恢复正常,运行一段时间后(约一周)又会出现相同情况。另外,这个问题在我们搭建的自测服务器上无法重现,只能凭借分析生产环境数据来进行无侵入性的诊断。
(额外说明:由于现场调试数据并未保存,下面命令显示的数据仅供演示!!!)
问题确认
- 统计所有应用程序占用的内存
U
(单位是K,参考:怎样统计所有进程总共占用多少内存?):
$ grep Pss /proc/[1-9]*/smaps | awk '{total+=$2}; END {print total}'
1721106
- 查看系统内存总数
T
、空闲内存F
, 共享内存S
,缓存C
(参考: Linux 内存查看方法meminfo\maps\smaps\status 文件解析):
$ 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
,结果发现内核占用内存异常之大。整个故障节点有128G内存,内核占用了100多G,因此可以初步推断,内核发生了内存泄露。
原理分析
根据经验,一般内存泄露并耗尽内存的代码,一定是频繁申请释放内存的部分。
内核中可能会出现频繁申请释放的内存可能有:
- 内核管理数据结构,如
task_struct
,inode
等,而这些代码一般都经过大量测试,出现问题的可能性不大。 - 内核
IO
子系统或者驱动,比如块设备的BIO
,网络协议栈的SKB
,存储网络设备驱动。
这里最可能出现问题的地方便是存储或者网络设备的驱动,向相关研发人员询问近期内核及驱动变动情况,最终得知近期更新了X710
网卡的i40e
驱动程序,初步推断问题应该出现在网卡驱动上。
现场分析
Linux内核使用层次化内存管理的方法,每一层解决不同的问题,从下至上的关键部分如下:
- 物理内存管理,主要用于描述内存的布局和属性,主要有
Node
、Zone
和Page
三个结构,使内存按照Page
为单位来进行管理; Buddy
内存管理,主要解决外部碎片问题,使用get_free_pages
等函数以Page
的N
次方为单位进行申请释放;Slab
内存管理,主要解决内部碎片问题,可以按照使用者指定的大小批量申请内存(需要先创建对象缓存池);- 内核缓存对象,使用Slab预先分配一些固定大小的缓存,使用
kmalloc
、vmalloc
等函数以字节为单位进行内存申请释放。
接下来,我们首先要看内存是从哪个层次上泄露的(额外说明:还有很多诸如如大页内存,页缓存,块缓存等相关内存管理技术,他们都是从这几个层次里面申请内存,不是关键,这里全部忽略掉。)。
- 查看Buddy内存使用情况(参考:Linux /proc/buddyinfo 理解 ):
$ 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 : tunables : slabdata
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
等需要以页为单位进行内存申请的内核代码使用。而这些部分中,最可能出现问题的依旧是驱动。
源码分析
通常在高速网卡驱动中,为了实现高性能,都会直接从Buddy
中按照页的N次方为单位划分内存(大页内存也是从Buddy
里获取的,只是在向用户层映射时使用的大页表而已,这里不细区分),然后IO
映射给网卡,同时使用RingBuffer
数组或者DMA
链组成多队列。而X710
网卡是一块比较高端的网卡,应该也是具备这方面的功能的,其实现也脱离不了这些基本方法。而从Buddy
分配内存的函数主要是__get_free_pages
(不同内核版本,还有一些宏定义和变种,但都是大同小异,一定是以Pages
的N
次方分配内存,N
用参数order
输入)。
快速分析:
- 从下面的输出可以快速推断出,在收发数据中的确使用了直接从
Buddy
分配页面函数,虽然他使用了一个”变种“函数alloc_pages_node
(这个函数肯定也是间接调用Buddy
内存分配函数,因为有order
参数,这里就不细说了)。
$ grep -rHn pages
src/Makefile:107: @echo "Copying manpages..."
src/kcompat.h:5180:#ifndef dev_alloc_pages
src/kcompat.h:5181:#define dev_alloc_pages(_order) alloc_pages_node(NUMA_NO_NODE, (GFP_ATOMIC | __GFP_COLD | __GFP_COMP | __GFP_MEMALLOC), (_order))
src/kcompat.h:5184:#define dev_alloc_page() dev_alloc_pages(0)
src/kcompat.h:5620: __free_pages(page, compound_order(page));
src/i40e_txrx.c:1469: page = dev_alloc_pages(i40e_rx_pg_order(rx_ring));
src/i40e_txrx.c:1485: __free_pages(page, i40e_rx_pg_order(rx_ring));
src/i40e_txrx.c:1858: * Also address the case where we are pulling data in on pages only
src/i40e_txrx.c:1942: * For small pages, @truesize will be a constant value, half the size
src/i40e_txrx.c:1951: * For larger pages, @truesize will be the actual space used by the
src/i40e_txrx.c:1955: * space for a buffer. Each region of larger pages will be used at
src/i40e_lan_hmc.c:295: * This will allocate memory for PDs and backing pages and populate
src/i40e_lan_hmc.c:394: /* remove the backing pages from pd_idx1 to i */
- 从下面的输出可以快速推断出,驱动使用了
kmalloc
和vmalloc
函数,这都属于内核缓存对象,使用Slab
中分配出来的,而从之前的分析中可以得知Slab
是没问题的,所以这些部分理论上也不会有问题(实在想追查的话,可以实际计算出这些malloc
的内存大小,比如是100字节,那么就看上面的kmalloc-128
这个Slab
缓存是否正常即可)。
$ grep -rHn malloc
src/kcompat.c:672: buf = kmalloc(len, gfp);
src/kcompat.c:683: void *ret = kmalloc(size, flags);
src/kcompat.c:746: adapter->config_space = kmalloc(size, GFP_KERNEL);
src/kcompat.h:52:#include
src/kcompat.h:1990:#ifndef vmalloc_node
src/kcompat.h:1991:#define vmalloc_node(a,b) vmalloc(a)
src/kcompat.h:1992:#endif /* vmalloc_node*/
src/kcompat.h:3587: void *addr = vmalloc_node(size, node);
src/kcompat.h:3596: void *addr = vmalloc(size);
src/kcompat.h:4011: p = kmalloc(len + 1, GFP_KERNEL);
src/kcompat.h:5342:static inline bool page_is_pfmemalloc(struct page __maybe_unused *page)
src/kcompat.h:5345: return page->pfmemalloc;
src/i40e_txrx.c:1930: !page_is_pfmemalloc(page);
src/i40e_main.c:11967: buf = kmalloc(INFO_STRING_LEN, GFP_KERNEL);
- 由于内存泄露一定是发生在频繁申请释放的地方,对上述疑点进行快速排查后,只有其中收发队列申请释放内存的地方是最可能出现问题的。从如下代码可见他果然没有使用内核网络协议栈的
SKB
来收发数据,而是自己直接使用Buddy
的页面内存,然后映射给DMA
(这其实也是高速网卡驱动最不好写的地方之一)。
// src/i40e_txrx.c
/**
* i40e_alloc_mapped_page - recycle or make a new page
* @rx_ring: ring to use
* @bi: rx_buffer struct to modify
*
* Returns true if the page was successfully allocated or
* reused.
**/
static bool i40e_alloc_mapped_page(struct i40e_ring *rx_ring,
struct i40e_rx_buffer *bi)
{
struct page *page = bi->page;
dma_addr_t dma;
/* since we are recycling buffers we should seldom need to alloc */
if (likely(page)) {
rx_ring->rx_stats.page_reuse_count++;
return true;
}
/* alloc new page for storage */
page = dev_alloc_pages(i40e_rx_pg_order(rx_ring));
if (unlikely(!page)) {
rx_ring->rx_stats.alloc_page_failed++;
return false;
}
/* map page for use */
dma = dma_map_page_attrs(rx_ring->dev, page, 0,
i40e_rx_pg_size(rx_ring),
DMA_FROM_DEVICE,
I40E_RX_DMA_ATTR);
/* if mapping failed free memory back to system since
* there isn't much point in holding memory we can't use
*/
if (dma_mapping_error(rx_ring->dev, dma)) {
__free_pages(page, i40e_rx_pg_order(rx_ring));
rx_ring->rx_stats.alloc_page_failed++;
return false;
}
bi->dma = dma;
bi->page = page;
bi->page_offset = i40e_rx_offset(rx_ring);
/* initialize pagecnt_bias to 1 representing we fully own page */
bi->pagecnt_bias = 1;
return true;
}
根据过往经验,再继续看下去,就得看这个网卡的Data Sheet
和Programming Guide
了,很显然短期内Intel
也没有打算开放这些资料。如果真要细心分析,难度不小,估计得一个月以上的时间,这个分析方向暂时停止。但是也基本和之前的内存分析的结论不矛盾。
资料查找
接下来就去内核的Mail List
或者源码仓库上查看一番:
- 在驱动的发布网站上(Intel Ethernet Drivers and Utilities),他说他们修复了
Memory Leak
Bug(我们出问题的驱动是2.3.6
版本):
https://sourceforge.net/projects/e1000/files/i40e%20stable/2.3.6/
Changelog for i40e-linux-2.3.6
===========================================================================
- Fix mac filter removal timing issue
- Sync i40e_ethtool.c with upstream
- Fixes for TX hangs
- Some fixes for reset of VFs
- Fix build error with packet split disabled
- Fix memory leak related to filter programming status
- Add and modify branding strings
- Fix kdump failure
- Implement an ethtool private flag to stop LLDP in FW
- Add delay after EMP reset for firmware to recover
- Fix incorrect default ITR values on driver load
- Fixes for programming cloud filters
- Some performance improvements
- Enable XPS with QoS on newer kernels
- Enable support for VF VLAN tag stripping control
- Build fixes to force perl to load specific ./SpecSetup.pm file
- Fix the updating of pci.ids
- Use 16 byte descriptors by default
- Fixes for DCB
- Don't close client in debug mode
- Add change MTU log in VF driver
- Fix for adding multiple ethtool filters on the same location
- Add new branding strings for OCP XXV710 devices
- Remove X722 Support for Destination IP Cloud Filter
- Allow turning off offloads when the VF has VLAN set
- 在内核源码仓库里查一下,这就是那个所说解决内存泄露问题的
Patch
,但实际上并没有:
https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/drivers/net/ethernet/intel/i40e/i40e_txrx.c?id=2b9478ffc550f17c6cd8c69057234e91150f5972
author Alexander Duyck 2017-10-04 08:44:43 -0700
committer Jeff Kirsher 2017-10-10 08:04:36 -0700
commit 2b9478ffc550f17c6cd8c69057234e91150f5972 (patch)
tree 3c3478f6c489db75c980a618a44dbd0dc80fc3ef /drivers/net/ethernet/intel/i40e/i40e_txrx.c
parent e836e3211229d7307660239cc957f2ab60e6aa00 (diff)
download net-2b9478ffc550f17c6cd8c69057234e91150f5972.tar.gz
i40e: Fix memory leak related filter programming status
It looks like we weren't correctly placing the pages from buffers that had
been used to return a filter programming status back on the ring. As a
result they were being overwritten and tracking of the pages was lost.
This change works to correct that by incorporating part of
i40e_put_rx_buffer into the programming status handler code. As a result we
should now be correctly placing the pages for those buffers on the
re-allocation list instead of letting them stay in place.
Fixes: 0e626ff7ccbf ("i40e: Fix support for flow director programming status")
Reported-by: Anders K. Pedersen
Signed-off-by: Alexander Duyck
Tested-by: Anders K Pedersen
Signed-off-by: Jeff Kirsher
diff --git a/drivers/net/ethernet/intel/i40e/i40e_txrx.c b/drivers/net/ethernet/intel/i40e/i40e_txrx.c
index 1519dfb..2756131 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_txrx.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_txrx.c
@@ -1038,6 +1038,32 @@ reset_latency:
}
/**
+ * i40e_reuse_rx_page - page flip buffer and store it back on the ring
+ * @rx_ring: rx descriptor ring to store buffers on
+ * @old_buff: donor buffer to have page reused
+ *
+ * Synchronizes page for reuse by the adapter
+ **/
+static void i40e_reuse_rx_page(struct i40e_ring *rx_ring,
+ struct i40e_rx_buffer *old_buff)
+{
+ struct i40e_rx_buffer *new_buff;
+ u16 nta = rx_ring->next_to_alloc;
+
+ new_buff = &rx_ring->rx_bi[nta];
+
+ /* update, and store next to alloc */
+ nta++;
+ rx_ring->next_to_alloc = (nta < rx_ring->count) ? nta : 0;
+
+ /* transfer page from old buffer to new buffer */
+ new_buff->dma = old_buff->dma;
+ new_buff->page = old_buff->page;
+ new_buff->page_offset = old_buff->page_offset;
+ new_buff->pagecnt_bias = old_buff->pagecnt_bias;
+}
+
+/**
* i40e_rx_is_programming_status - check for programming status descriptor
* @qw: qword representing status_error_len in CPU ordering
*
@@ -1071,15 +1097,24 @@ static void i40e_clean_programming_status(struct i40e_ring *rx_ring,
union i40e_rx_desc *rx_desc,
u64 qw)
{
- u32 ntc = rx_ring->next_to_clean + 1;
+ struct i40e_rx_buffer *rx_buffer;
+ u32 ntc = rx_ring->next_to_clean;
u8 id;
/* fetch, update, and store next to clean */
+ rx_buffer = &rx_ring->rx_bi[ntc++];
ntc = (ntc < rx_ring->count) ? ntc : 0;
rx_ring->next_to_clean = ntc;
prefetch(I40E_RX_DESC(rx_ring, ntc));
+ /* place unused page back on the ring */
+ i40e_reuse_rx_page(rx_ring, rx_buffer);
+ rx_ring->rx_stats.page_reuse_count++;
+
+ /* clear contents of buffer_info */
+ rx_buffer->page = NULL;
+
id = (qw & I40E_RX_PROG_STATUS_DESC_QW1_PROGID_MASK) >>
I40E_RX_PROG_STATUS_DESC_QW1_PROGID_SHIFT;
@@ -1639,32 +1674,6 @@ static bool i40e_cleanup_headers(struct i40e_ring *rx_ring, struct sk_buff *skb,
}
/**
- * i40e_reuse_rx_page - page flip buffer and store it back on the ring
- * @rx_ring: rx descriptor ring to store buffers on
- * @old_buff: donor buffer to have page reused
- *
- * Synchronizes page for reuse by the adapter
- **/
-static void i40e_reuse_rx_page(struct i40e_ring *rx_ring,
- struct i40e_rx_buffer *old_buff)
-{
- struct i40e_rx_buffer *new_buff;
- u16 nta = rx_ring->next_to_alloc;
-
- new_buff = &rx_ring->rx_bi[nta];
-
- /* update, and store next to alloc */
- nta++;
- rx_ring->next_to_alloc = (nta < rx_ring->count) ? nta : 0;
-
- /* transfer page from old buffer to new buffer */
- new_buff->dma = old_buff->dma;
- new_buff->page = old_buff->page;
- new_buff->page_offset = old_buff->page_offset;
- new_buff->pagecnt_bias = old_buff->pagecnt_bias;
-}
-
-/**
* i40e_page_is_reusable - check if any reuse is possible
* @page: page struct to check
*
- 然后再看看这个驱动在内核
Mail List
上的反馈,遇到这个问题的人很多,我们并不孤独:
https://www.mail-archive.com/[email protected]&q=subject:%22Re%5C%3A+Linux+4.12%5C%2B+memory+leak+on+router+with+i40e+NICs%22&o=newest
...
Upgraded and looks like problem is not solved with that patch
Currently running system with
https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/
kernel
Still about 0.5GB of memory is leaking somewhere
Also can confirm that the latest kernel where memory is not
leaking (with
use i40e driver intel 710 cards) is 4.11.12
With kernel 4.11.12 - after hour no change in memory usage.
also checked that with ixgbe instead of i40e with same
net.git kernel there
is no memleak - after hour same memory usage - so for 100%
this is i40e
driver problem.
....
至此,所有的疑点都指向这个网卡的收发队列中的内存申请释放。
问题验证
由于我们无法在开发测试环境中重现问题,而并未升级网卡驱动的主机都正常。因此找了一台出现此问题的主机上把驱动降级到2.2.4
版本,其他的不变,运行两周,一切正常,问题算是被粗暴的解决了。后续可能需要继续跟进官网驱动的更新,待稳定和验证后再升级。
总结建议
- 整个生产环境应该具备更完善的日志及监控,方便及时发现问题及故障诊断。
- 硬件、驱动、内核和系统等变更应该得到严格控制和验证,最好和内核相关开发人员进行相应讨论和评估。
- 解决问题时,应该原理分析、源码分析、现场分析和测试对比等方法相结合,不要一条路走到黑。