proc下meminfo的细节

续接十年前总结的前文linux的内存管理介绍

最近遇到了Serverless平台的管理节点上报内存不准确的问题,导致调度误判从而大量oom,所以不同于前文对meminfo一笔带过,需要对meminfo做一个系统的深入了解,并对其内容做一个分类,搞清楚存在的相互联系

首先贴一个/proc/meminfo的数据,可以看到,有49项,非常复杂

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
48
49
MemTotal:        1950928 kB
MemFree: 110512 kB
MemAvailable: 300776 kB
Buffers: 0 kB
Cached: 295128 kB
SwapCached: 0 kB
Active: 1431548 kB
Inactive: 159160 kB
Active(anon): 1293796 kB
Inactive(anon): 1916 kB
Active(file): 137752 kB
Inactive(file): 157244 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 2108 kB
Writeback: 0 kB
AnonPages: 1295424 kB
Mapped: 85936 kB
Shmem: 2188 kB
KReclaimable: 53172 kB
Slab: 132728 kB
SReclaimable: 53172 kB
SUnreclaim: 79556 kB
KernelStack: 9360 kB
PageTables: 14280 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 975464 kB
Committed_AS: 3710176 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
Percpu: 1136 kB
HardwareCorrupted: 0 kB
AnonHugePages: 397312 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
DirectMap4k: 430776 kB
DirectMap2M: 1583104 kB
DirectMap1G: 0 kB

前置知识

内核内存分配函数与内存黑洞

alloc_pages/__get_free_page:以页为单位分配,是全部内存分配的基础

  • kmalloc

    以字节为单位分配虚拟地址连续的小块内存块

    很接近我们使用的malloc,只是内核使用,所以有个k前缀

    他是基于slab allocator的

    • slab

      提供内存缓存管理,支持对象缓存功能,优化内存碎片,以提高内存重用性和性能

      参见/proc/meminfo中的Slab/SReclaimable/SUnreclaim

  • vmalloc

    以字节为单位分配虚拟地址连续的大块内存块

    和kmalloc的区别就在于为了分配大块的内存块,把物理上不连续的页转换为虚拟地址空间上连续的页,必须专门建立页表项,而且因为它们物理上是不连续的,所以必须一个一个进行映射,这会导致比直接内存映射大得多的 TLB 抖动。尽管在某些情况下才需要物理上的连续内存块,但出于性能的考虑,很多内核代码都使用 kmalloc()。除非在不得已时才使用 vmalloc(),比如动态装载模块时,需要获得大块内存。

    参见/proc/meminfo中的VmallocUsed 和 /proc/vmallocinfo

而alloc_pages作为一个非常底层的接口,是没有自动统计的,因此这一部分使用的内存会进入内存黑洞,这个无法追踪

MemTotal,MemFree,MemAvailable

这个很常见,free -m也能看到

其中MemTotal不是真实的整机容量,使用dmest -T|grep reserved可以看到内核预留了一些容量

1
Memory: 261120K/2013488K available (12292K kernel code, 2152K rwdata, 3852K rodata, 2408K init, 6468K bss, 93928K reserved, 0K cma-reserved)

MemFree就是剩下完全没有使用的内存,但是并不是最终的可用内存,因为有一部分内存是可以page out的,所以有一个预估的MemAvailable

内核使用

Slab,SReclaimable,SUnreclaim

这个非常容易理解,Slab的总量,可回收的和不可回收的

VmallocTotal,VmallocUsed,VmallocChunk

出于性能原因,这些字段在 Linux 4.4 中被清零。与 glibc 链接的程序在启动时会读取此文件,并且它会造成可衡量的影响。

mm: get rid of 'vmalloc_info' from /proc/meminfo,Linux本人在本文说道

but the whole vmalloc information of of rather dubious value to begin with, and people who actually want to know what the situation is wrt the vmalloc area should just look at the much more complete /proc/vmallocinfo instead.

但整个 vmalloc 信息的价值一开始就相当可疑,而那些真正想知道 vmalloc 区域情况的人应该查看更完整的 /proc/vmallocinfo

vmallocinfo的信息数据如下,其中可以看到每个vmalloc的调用者:

1
2
3
4
5
6
7
8
9
10
11
~ grep vmalloc /proc/vmallocinfo |head
0xffffa853c0005000-0xffffa853c0206000 2101248 alloc_large_system_hash+0x19b/0x25e pages=512 vmalloc N0=512
0xffffa853c0206000-0xffffa853c0307000 1052672 alloc_large_system_hash+0x19b/0x25e pages=256 vmalloc N0=256
0xffffa853c0307000-0xffffa853c0310000 36864 alloc_large_system_hash+0x19b/0x25e pages=8 vmalloc N0=8
0xffffa853c0310000-0xffffa853c0319000 36864 alloc_large_system_hash+0x19b/0x25e pages=8 vmalloc N0=8
0xffffa853c0319000-0xffffa853c031b000 8192 gen_pool_add_owner+0x3f/0xb0 pages=1 vmalloc N0=1
0xffffa853c031c000-0xffffa853c0321000 20480 _do_fork+0xe3/0x3a0 pages=4 vmalloc N0=4
0xffffa853c0321000-0xffffa853c0323000 8192 bpf_prog_alloc_no_stats+0x47/0xe0 pages=1 vmalloc N0=1
0xffffa853c0324000-0xffffa853c0329000 20480 _do_fork+0xe3/0x3a0 pages=4 vmalloc N0=4
0xffffa853c032c000-0xffffa853c0331000 20480 _do_fork+0xe3/0x3a0 pages=4 vmalloc N0=4
0xffffa853c0334000-0xffffa853c0339000 20480 _do_fork+0xe3/0x3a0 pages=4 vmalloc N0=4

所以统计vmalloc的使用量是:

1
2
~ grep vmalloc /proc/vmallocinfo | awk '{total+=$2}; END {print total}'
28016640

没什么卵用的冷知识:lsmod第二列就是内核模块所占内存的大小,但是是按page算的,1个字节也会取整到1个page,具体可以看内核模块

HardwareCorrupted

当系统检测到内存的硬件故障时,会把有问题的页面删除掉,不再使用

PageTables

Page Table用于将内存的虚拟地址翻译成物理地址,随着内存地址分配得越来越多,Page Table会增大,/proc/meminfo中的PageTables统计了Page Table所占用的内存大小

区分以下概念:

  • Page Cache:用于缓存磁盘上的文件数据,以加速文件读写操作
  • Page Frame(页帧):物理内存的最小单位是page frame,每个物理页对应一个描述符(struct page),在内核的引导阶段就会分配好。只会随着物理内存的变大而引导时变大,不会动态增长。

KernelStack

每一个用户线程都会分配一个kernel stack(内核栈),内核栈虽然属于线程,但用户态的代码不能访问,只有通过系统调用(syscall)、自陷(trap)或异常(exception)进入内核态的时候才会用到

Bounce

老设备用,已经很少见了

用户进程使用

参考资料:

内存管理补充

linux内核中的各种内存分配函数:kmalloc、vmalloc、slab、__get_free_pages、mempoll_alloc