/proc/meminfo
是了解Linux
系统内存使用状况的主要接口,我们最常用的”free
”、”vmstat
”等命令就是通过它获取数据的
/proc/meminfo
所包含的信息比”free
”等命令要丰富得多,然而真正理解它并不容易,
比如我们知道”Cached
”统计的是文件缓存页,manpage
上说是“In-memory cache for files read from the disk (the page cache)
”
那为什么它不等于[Active(file)+Inactive(file)]
?
AnonHugePages
与AnonPages
、HugePages_Total
有什么联系和区别?很多细节在手册中并没有讲清楚,本文对此做了一点探究。
负责输出/proc/meminfo
的源代码是:
fs/proc/meminfo.c : meminfo_proc_show()
MemTotal: 3809036 kB
MemFree: 282012 kB
MemAvailable: 865620 kB
Buffers: 0 kB
Cached: 854972 kB
SwapCached: 130900 kB
Active: 1308168 kB
Inactive: 1758160 kB
Active(anon): 1010416 kB
Inactive(anon): 1370480 kB
Active(file): 297752 kB
Inactive(file): 387680 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 4063228 kB
SwapFree: 3357108 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 2104412 kB
Mapped: 40988 kB
Shmem: 169540 kB
Slab: 225420 kB
SReclaimable: 134220 kB
SUnreclaim: 91200 kB
KernelStack: 5936 kB
PageTables: 35628 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 5967744 kB
Committed_AS: 5626436 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 351900 kB
VmallocChunk: 34359363652 kB
HardwareCorrupted: 0 kB
AnonHugePages: 139264 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 204484 kB
DirectMap2M: 3915776 kB
MemTotal
系统从加电开始到引导完成,firmware/BIOS
要保留一些内存,kernel
本身要占用一些内存,最后剩下可供kernel
支配的内存就是MemTotal
。这个值在系统运行期间一般是固定不变的。可参阅解读DMESG
中的内存初始化信息。
MemFree
表示系统尚未使用的内存。[MemTotal-MemFree]
就是已被用掉的内存。
MemAvailable
有些应用程序会根据系统的可用内存大小自动调整内存申请的多少,所以需要一个记录当前可用内存数量的统计值,MemFree
并不适用,因为MemFree
不能代表全部可用的内存
系统中有些内存虽然已被使用但是可以回收的,比如cache/buffer
、slab
都有一部分可以回收,所以这部分可回收的内存加上MemFree
才是系统可用的内存,即MemAvailable
。
/proc/meminfo
中的MemAvailable
是内核使用特定的算法估算出来的,要注意这是一个估计值,并不精确。
内存黑洞
追踪Linux
系统的内存使用一直是个难题,很多人试着把能想到的各种内存消耗都加在一起,kernel text
、kernel modules
、buffer
、cache
、slab
、page table
、process RSS
…等等,却总是与物理内存的大小对不上,这是为什么呢?
因为Linux kernel
并没有滴水不漏地统计所有的内存分配,kernel
动态分配的内存中就有一部分没有计入/proc/meminfo
中。
我们知道,Kernel
的动态内存分配通过以下几种接口:
alloc_pages/__get_free_page
: 以页为单位分配vmalloc
: 以字节为单位分配虚拟地址连续的内存块slab allocator
kmalloc
: 以字节为单位分配物理地址连续的内存块,它是以slab
为基础的,使用slab
层的general caches
— 大小为2^n
,名称是kmalloc-32
、kmalloc-64
等(在老kernel
上的名称是size-32
、size-64
等)。
通过slab
层分配的内存会被精确统计,可以参见/proc/meminfo
中的slab/SReclaimable/SUnreclaim
;
通过vmalloc
分配的内存也有统计,参见/proc/meminfo
中的VmallocUsed
和 /proc/vmallocinfo
(下节中还有详述);
而通过alloc_pages
分配的内存不会自动统计,除非调用alloc_pages
的内核模块或驱动程序主动进行统计,否则我们只能看到free memory
减少了,但从/proc/meminfo
中看不出它们具体用到哪里去了。
比如在VMware guest
上有一个常见问题,就是VMWare ESX
宿主机会通过guest
上的Balloon driver(vmware_balloon module)
占用guest
的内存
有时占用得太多会导致guest
无内存可用,这时去检查guest
的/proc/meminfo
只看见MemFree
很少
但看不出内存的去向,原因就是Balloon driver
通过alloc_pages
分配内存,没有在/proc/meminfo
中留下统计值,所以很难追踪。
内存都到哪里去了?
使用内存的,不是kernel
就是用户进程,下面我们就分类讨论。
注:page cache
比较特殊,很难区分是属于kernel
还是属于进程,其中被进程mmap
的页面自然是属于进程的了,而另一些页面没有被mapped
到任何进程,那就只能算是属于kernel
了。
1. 内核
内核所用内存的静态部分,比如内核代码、页描述符等数据在引导阶段就分配掉了,并不计入MemTotal
里,而是算作Reserved
(在dmesg
中能看到)。
而内核所用内存的动态部分,是通过上文提到的几个接口申请的,其中通过alloc_pages
申请的内存有可能未纳入统计,就像黑洞一样。
下面讨论的都是/proc/meminfo
中所统计的部分。
1.1 SLAB
通过slab
分配的内存被统计在以下三个值中:
SReclaimable
: slab
中可回收的部分。调用kmem_getpages()
时加上SLAB_RECLAIM_ACCOUNT
标记,表明是可回收的,计入SReclaimable
,否则计入SUnreclaim
。
SUnreclaim
:slab
中不可回收的部分。
Slab
:slab
中所有的内存,等于以上两者之和。
1.2 VmallocUsed
通过vmalloc
分配的内存都统计在/proc/meminfo
的 VmallocUsed
值中,但是要注意这个值不止包括了分配的物理内存,还统计了VM_IOREMAP
、VM_MAP
等操作的值
譬如VM_IOREMAP
是把IO
地址映射到内核空间、并未消耗物理内存,所以我们要把它们排除在外。
从物理内存分配的角度,我们只关心VM_ALLOC
操作,这可以从/proc/vmallocinfo
中的vmalloc
记录看到:
# grep vmalloc /proc/vmallocinfo
...
0xffffc90004702000-0xffffc9000470b000 36864 alloc_large_system_hash+0x171/0x239 pages=8 vmalloc N0=8
0xffffc9000470b000-0xffffc90004710000 20480 agp_add_bridge+0x2aa/0x440 pages=4 vmalloc N0=4
0xffffc90004710000-0xffffc90004731000 135168 raw_init+0x41/0x141 pages=32 vmalloc N0=32
0xffffc90004736000-0xffffc9000473f000 36864 drm_ht_create+0x55/0x80 [drm] pages=8 vmalloc N0=8
0xffffc90004744000-0xffffc90004746000 8192 dm_table_create+0x9e/0x130 [dm_mod] pages=1 vmalloc N0=1
0xffffc90004746000-0xffffc90004748000 8192 dm_table_create+0x9e/0x130 [dm_mod] pages=1 vmalloc N0=1
...