温故而知新,可以为师矣。
1、top linux下的任务管理器
top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止.比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最“敏感”的任务列表.该命令可以按CPU使用.内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定。
$top
top - 09:14:56 up 264 days, 20:56, 1 user, load average: 0.02, 0.04, 0.00
Tasks: 87 total, 1 running, 86 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 0.2%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.2%st
Mem: 377672k total, 322332k used, 55340k free, 32592k buffers
Swap: 397308k total, 67192k used, 330116k free, 71900k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 2856 656 388 S 0.0 0.2 0:49.40 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root 20 0 0 0 0 S 0.0 0.0 7:15.20 ksoftirqd/0
4 root RT 0 0 0 0 S 0.0 0.0 0:00.00 migration/0
第一行
09:14:56 : 系统当前时间
264 days, 20:56 : 系统开机到现在经过了多少时间
1 users : 当前2用户在线
load average: 0.02, 0.04, 0.00: 系统1分钟、5分钟、15分钟的CPU负载信息
第二行
Tasks:任务;
87 total:很好理解,就是当前有87个任务,也就是87个进程。
1 running:1个进程正在运行
86 sleeping:86个进程睡眠
0 stopped:停止的进程数
0 zombie:僵死的进程数
第三行
Cpu(s):表示这一行显示CPU总体信息
0.0%us:用户态进程占用CPU时间百分比,不包含renice值为负的任务占用的CPU的时间。
0.7%sy:内核占用CPU时间百分比
0.0%ni:改变过优先级的进程占用CPU的百分比
99.3%id:空闲CPU时间百分比
0.0%wa:等待I/O的CPU时间百分比
0.0%hi:CPU硬中断时间百分比
0.0%si:CPU软中断时间百分比
注:这里显示数据是所有cpu的平均值,如果想看每一个cpu的处理情况,按1即可;折叠,再次按1;
第四行
Men:内存的意思
8175320kk total:物理内存总量
8058868k used:使用的物理内存量
116452k free:空闲的物理内存量
283084k buffers:用作内核缓存的物理内存量
第五行
Swap:交换空间
6881272k total:交换区总量
4010444k used:使用的交换区量
2870828k free:空闲的交换区量
4336992k cached:缓冲交换区总量
进程信息
再下面就是进程信息:
PID:进程的ID
USER:进程所有者
PR:进程的优先级别,越小越优先被执行
NInice:值
VIRT:进程占用的虚拟内存
RES: 进程占用的物理内存
SHR:进程使用的共享内存
S:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数
%CPU:进程占用CPU的使用率
%MEM:进程使用的物理内存和总内存的百分比
TIME+:该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值。
COMMAND:进程启动命令名称
top命令交互操作指令
下面列出一些常用的 top命令操作指令
q:退出top命令
<Space>:立即刷新
s:设置刷新时间间隔
c:显示命令完全模式
t::显示或隐藏进程和CPU状态信息
m:显示或隐藏内存状态信息
l:显示或隐藏uptime信息
f:增加或减少进程显示标志
S:累计模式,会把已完成或退出的子进程占用的CPU时间累计到父进程的MITE+
P:按%CPU使用率排行
T:按MITE+排行
M:按%MEM排行
e: 调整内存显示单位m/G
u:指定显示用户进程
r:修改进程renice值
kkill:进程
i:只显示正在运行的进程
W:保存对top的设置到文件^/.toprc,下次启动将自动调用toprc文件的设置。
h:帮助命令。
q:退出
注:强调一下,使用频率最高的是P、T、M,因为通常使用top,我们就想看看是哪些进程最耗cpu资源、占用的内存最多; 注:通过”shift + >”或”shift + <”可以向右或左改变排序列 如果只需要查看内存:可用free命令。只查看uptime信息(第一行),可用uptime命令;
2、free命令
查看内存是否存在瓶颈,使用top指令看比较麻烦,而free命令更为直观:
[
/
home
/
weber
#]free
total
used
free
shared
buffers
cached
Mem
:
501820
452028
49792
37064
5056
136732
-/+
buffers
/
cache
:
310240
191580
Swap
:
0
0
0
[
/
home
/
weber
#]
top
top
-
17
:
52
:
17
up
42
days
,
7
:
10
,
1
user
,
load
average
:
0.02
,
0.02
,
0.05
Tasks
:
80
total
,
1
running
,
79
sleeping
,
0
stopped
,
0
zombie
%
Cpu
(
s
):
0.0
us
,
0.0
sy
,
0.0
ni
,
100.0
id
,
0.0
wa
,
0.0
hi
,
0.0
si
,
0.0
st
KiB
Mem
:
501820
total
,
452548
used
,
49272
free
,
5144
buffers
KiB
Swap
:
0
total
,
0
used
,
0
free
.
136988
cached
Mem
top工具显示了free工具的第一行所有信息,但真实可用的内存,还需要自己计算才知道; 系统实际可用的内存为free工具输出第二行的free+buffer+cached;也就是第三行的free值191580;关于free命令各个值的详情解读,请参考这篇文章
free 查询可用内存
;
free -m
$ free -m
total used free shared buff/cache available
Mem: 1504 1491 13 0 855
792
Swap: 2047 6 2041
FO
[
2
][
1
]
=
24677460
FO
[
3
][
2
]
=
10321516
1
2
3
4
5
6
1 total
used
free
shared
buffers
cached
2 Mem
:
24677460
23276064
1401396
0
870540
12084008
3 -/+
buffers
/
cache
:
10321516
14355944
4 Swap
:
25151484
224188
24927296
说明:
shared:表示被几个进程共享的内存的,现在已经deprecated,其值总是0(当然在一些系统上也可能不是0,主要取决于free命令是怎么实现的)。
buffer:A buffer is something that has yet to be "written" to disk.
cache:A cache is something that has been "read" from the disk and stored for later use.
也就是说buffer是用于存放要输出到disk(块设备)的数据的,而cache是存放从disk上读出的数据。这二者是为了提高IO性能的,并由OS管理。
total:总内存大小。
used:已经使用的内存大小(这里面包含cached和buffers和shared部分)。
free:空闲的内存大小。
shared:进程间共享内存(一般不会用,可以忽略)。
buffers:内存中写完的东西缓存起来,这样快速响应请求,后面数据再定期刷到磁盘上。
cached:内存中读完缓存起来内容占的大小(这部分是为了下次查询时快速返回)。
-/+ buffers/cache看作两部分:
-buffers/cache:正在使用的内存大小(注意不是used部分,因为buffers和cached并不是正在使用的,组织和人民需要是它们是可以释放的),其值=used-buffers-cached。
+buffers/cache:可用的内存大小(同理也不是free表示的部分),其值=free+buffers+cached。
Swap:
硬盘上交换分区的使用大小。设计的目的就是当上面提到的+buffers/cache表示的可用内存都已使用完,新的读写请求过来后,会把内存中的部分数据写入磁盘,从而把磁盘的部分空间当做虚拟内存来使用。
等式:
FO[
Mem][
total] = FO[
Mem][
used] + FO[
Mem][
free]
FO[
-/+
buffers
/
cache
][
used
] = FO[
Mem
][
used
] - FO[
Mem
][
buffers
] - FO[
Mem
][
cached
]
FO[
-/+
buffers
/
cache
][
free
] = FO[
Mem
][
free
] + FO[
Mem
][
buffers
] + FO[
Mem
][
cached
]
Linux和其他成熟的操作系统(例如windows),
为了提高IO read的性能,总是要多cache一些数据,这也就是为什么FO[2][6](cached memory)比较大,而FO[2][3]比较小的原因
。我们可以做一个简单的测试:
释放掉被系统cache占用的数据:
echo
3
>/
proc
/
sys
/
vm
/
drop_caches
-
读一个大文件,并记录时间;
-
关闭该文件;
-
重读这个大文件,并记录时间;
第二次读应该比第一次快很多。原来我做过一个BerkeleyDB的读操作,大概要读5G的文件,几千万条记录。在我的环境上,第二次读比第一次大概可以快9倍左右。
free输出的第二行是从一个应用程序的角度看系统内存的使用情况。
-
对于FO[3][2],即-buffers/cache,表示一个应用程序认为系统被用掉多少内存;
-
对于FO[3][3],即+buffers/cache,表示一个应用程序认为系统还有多少内存;
因为被系统cache和buffer占用的内存可以被快速回收,所以通常FO[3][3]比FO[2][3]会大很多。
3.Buffer和Cache介绍
Cache(缓存),为了调高CPU和内存之间数据交换而设计,Buffer(缓冲)为了提高内存和硬盘(或其他I/O设备的数据交换而设计)。
Cache主要是针对读操作设计的
,不过Cache概念可能容易混淆,我理解为CPU本身就有Cache,包括一级缓存、二级缓存、三级缓存,我们知道CPU所有的指令操作对接的都是内存,而CPU的处理能力远高于内存速度,所以为了不让CPU资源闲置,Intel等公司在CPU内部集成了一些Cache,但毕竟不能放太多电路在里面,所以这部分Cache并不是很大,主要是用来存放一些常用的指令和常用数据,
真正大部分Cache的数据应该是占用内存的空间来缓存请求过的数据,即上面的Cached部分
(这部分纯属个人理解,正确与否有待考证)。
Buffer主要是针对写操作设计的
,更细的说是针对内存和硬盘之间的写操作来设计的,目的是将写的操作集中起来进行,减少磁盘碎片和硬盘反复寻址过程,提高性能。在Linux系统内部有一个守护进程会定期清空Buffer中的内容,将其写入硬盘内,当手动执行sync命令时也会触发上述操作。
3.1. 常见症状
症状一:在Linux中频繁存取文件,物理内存很快用光,而cached一直在增长。
解释:Linux会对每次请求过的数据缓存在cache里,好处就是CPU的处理速度远远高于内存,所以在CPU和内存通讯的时候可以快速从cache中命中结果返回。
症状二:Swap被占用。
解释:
内存可能不够了,才会占Swap
,所以Swap可以作为服务器监控的一项指标,引起注意。
3.2. 手动清理Swap和buffers/cache
(1) 清理Swap
swapoff -a && swapon -a
操作说明:如果已经使用了Swap,且当前清空下+buffers/cache还有空间,在执行swapoff -a操作时,会触发把Swap中的内容交换到内存中,数据不会丢失。
(2) 清理buffers/cache:
sync; sync; sync;&& echo 3 >/proc/sys/vm/drop_caches
sleep 2
echo 0 > /proc/sys/vm/drop_caches
操作说明:
sync-->将缓存的内从写回到硬盘中;
echo 3 >/proc/sys/vm/drop_caches-->修改drop_caches的值为3,
默认为0,改为3系统会清理缓存的内容
;
sleep 2 --> 等一下,防止上一步没执行完;
echo 0 >/proc/sys/vm/drop_caches --> 改回默认值
设置的值分别为1、2、3。它们所表示的含义为:
echo 1 >
/proc/sys/vm/drop_caches
:表示清除pagecache。
echo 2 > /proc/sys/vm/drop_caches:表示清除回收slab分配器中的对象(包括目录项缓存和inode缓存)。slab分配器是内核中管理内存的一种机制,其中很多缓存数据实现都是用的pagecache。
echo 3 > /proc/sys/vm/drop_caches:表示清除pagecache和slab分配器中的缓存对象。
4. 虚拟内存统计vmstat
vmstat [-V] [-n] [delay [count]]
-V表示打印出版本信息; -n表示在周期性循环输出时,输出的头部信息仅显示一次; delay是两次输出之间的延迟时间; count是指按照这个时间间隔统计的次数。
/root$vmstat 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
6 0 0 27900472 204216 28188356 0 0 0 9 1 2 11 14 75 0 0
9 0 0 27900380 204228 28188360 0 0 0 13 33312 126221 22 20 58 0 0
2 0 0 27900340 204240 28188364 0 0 0 10 32755 125566 22 20 58 0 0
如果是因为缺少内存,系统响应变慢很明显,因为这使得系统不停的做换入换出的工作;
5. /proc/meminfo
/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: 所有内存(RAM)大小,减去一些预留空间和内核的大小。
-
MemFree: 完全没有用到的物理内存,lowFree+highFree
-
MemAvailable: 在不使用交换空间的情况下,启动一个新的应用最大可用内存的大小,计算方式:MemFree+Active(file)+Inactive(file)-(watermark+min(watermark,Active(file)+Inactive(file)/2))
-
Buffers: 块设备所占用的缓存页,包括:直接读写块设备以及文件系统元数据(metadata),比如superblock使用的缓存页。
-
Cached: 表示普通文件数据所占用的缓存页。
-
SwapCached: swap cache中包含的是被确定要swapping换页,但是尚未写入物理交换区的匿名内存页。那些匿名内存页,比如用户进程malloc申请的内存页是没有关联任何文件的,如果发生swapping换页,这类内存会被写入到交换区。
-
Active: active包含active anon和active file
-
Inactive: inactive包含inactive anon和inactive file
-
Active(anon): anonymous pages(匿名页),用户进程的内存页分为两种:与文件关联的内存页(比如程序文件,数据文件对应的内存页)和与内存无关的内存页(比如进程的堆栈,用malloc申请的内存),前者称为file pages或mapped pages,后者称为匿名页。
-
Inactive(anon): 见上
-
Active(file): 见上
-
Inactive(file): 见上
-
SwapTotal: 可用的swap空间的总的大小(swap分区在物理内存不够的情况下,把硬盘空间的一部分释放出来,以供当前程序使用)
-
SwapFree: 当前剩余的swap的大小
-
Dirty: 需要写入磁盘的内存去的大小
-
Writeback: 正在被写回的内存区的大小
-
AnonPages: 未映射页的内存的大小
-
Mapped: 设备和文件等映射的大小
-
Slab: 内核数据结构slab的大小
-
SReclaimable: 可回收的slab的大小
-
SUnreclaim: 不可回收的slab的大小
-
PageTables: 管理内存页页面的大小
-
NFS_Unstable: 不稳定页表的大小
-
VmallocTotal: Vmalloc内存区的大小
-
VmallocUsed: 已用Vmalloc内存区的大小
-
VmallocChunk: vmalloc区可用的连续最大快的大小
MemTotal
系统从加电开始到引导完成,
f
irmware/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等)。
(1)通过slab层分配的内存会被精确统计,可以参见/proc/meminfo中的slab/SReclaimable/SUnreclaim;
(2)通过vmalloc分配的内存也有统计,参见/proc/meminfo中的VmallocUsed 和 /proc/vmallocinfo(下节中还有详述);
(3)而
通过
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中留下统计值,所以很难追踪。
6. 内存都到哪里去了?
使用内存的,不是kernel就是用户进程,下面我们就分类讨论。
注:page cache比较特殊,很难区分是属于kernel还是属于进程,其中被进程mmap的页面自然是属于进程的了,而
另一些页面没有被mapped到任何进程,那就只能算是属于kernel了
。
6.1. 内核
内核所用内存的静态部分,比如内核代码、页描述符等数据在引导阶段就分配掉了,并不计入MemTotal里,而是算作Reserved(在dmesg中能看到)。而内核所用内存的动态部分,是通过上文提到的几个接口申请的,其中通过alloc_pages申请的内存有可能未纳入统计,就像黑洞一样。
下面讨论的都是/proc/meminfo中所统计的部分。
6.1.1 SLAB
通过slab分配的内存被统计在以下三个值中:
SReclaimable: slab中可回收的部分。调用kmem_getpages()时加上SLAB_RECLAIM_ACCOUNT标记,表明是可回收的,计入SReclaimable,否则计入SUnreclaim。
SUnreclaim: slab中不可回收的部分。
Slab: slab中所有的内存,等于以上两者之和。
6.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
.
.
.
|
注:/proc/vmallocinfo中能看到vmalloc来自哪个调用者(caller),那是vmalloc()记录下来的,相应的源代码可见:
mm/vmalloc.c: vmalloc > __vmalloc_node_flags > __vmalloc_node > __vmalloc_node_range > __get_vm_area_node > setup_vmalloc_vm
通过vmalloc分配了多少内存,可以统计/proc/vmallocinfo中的vmalloc记录,例如:
# grep vmalloc /proc/vmallocinfo | awk '{total+=$2}; END {print total}'
23375872
|
一些driver以及网络模块和文件系统模块可能会调用vmalloc,加载内核模块(kernel module)时也会用到,可参见 kernel/module.c。
6.1.3 kernel modules (内核模块)
系统已经加载的内核模块可以用 lsmod 命令查看,注意第二列就是内核模块所占内存的大小,通过它可以统计内核模块所占用的内存大小,但这并不准,因为”lsmod”列出的是[init_size+core_size],而实际给kernel module分配的内存是以page为单位的,不足 1 page的部分也会得到整个page,此外每个module还会分到一页额外的guard page。下文我们还会细说。
# lsmod | less
Module
Size
Used
by
rpcsec_gss
_krb5
31477
0
auth
_rpcgss
59343
1
rpcsec_gss_krb5
nfsv4
474429
0
dns
_resolver
13140
1
nfsv4
nfs
246411
1
nfsv4
lockd
93977
1
nfs
sunrpc
295293
5
nfs
,
rpcsec_gss_krb5
,
auth_rpcgss
,
lockd
,
nfsv4
fscache
57813
2
nfs
,
nfsv4
.
.
.
|
lsmod的信息来自/proc/modules,它显示的size包括init_size和core_size,相应的源代码参见:
// kernel/module.c
static
int
m_show
(
struct
seq_file
*
m
,
void
*
p
)
{
.
.
.
seq_printf
(
m
,
"%s %u"
,
mod
->
name
,
mod
->
init_size
+
mod
->
core_size
)
;
.
.
.
}
|
注:我们可以在 /sys/module/<module-name>/ 目录下分别看到coresize和initsize的值。
kernel module的内存是通过vmalloc()分配的(参见下列源代码),所以在/proc/vmallocinfo中会有记录,也就是说我们可以不必通过”lsmod”命令来统计kernel module所占的内存大小,通过/proc/vmallocinfo就行了,而且还比lsmod更准确,为什么这么说呢?
// kernel/module.c
static
int
move_module
(
struct
module
*
mod
,
struct
load_info
*
info
)
{
.
.
.
ptr
=
module_alloc_update_bounds
(
mod
->
core_size
)
;
.
.
.
if
(
mod
->
init_size
)
{
ptr
=
module_alloc_update_bounds
(
mod
->
init_size
)
;
.
.
.
}
// 注:module_alloc_update_bounds()最终会调用vmalloc_exec()
|
因为给kernel module分配内存是以page为单位的,不足 1 page的部分也会得到整个page,此外,每个module还会分到一页额外的guard page。
详见:mm/vmalloc.c: __get_vm_area_node()
而”lsmod”列出的是[init_size+core_size],比实际分配给kernel module的内存小。我们做个实验来说明:
# 先卸载floppy模块
$
modprobe
-
r
floppy
# 确认floppy模块已经不在了
$
lsmod
|
grep
floppy
# 记录vmallocinfo以供随后比较
$
cat
/
proc
/
vmallocinfo
>
vmallocinfo
.
1
# 加载floppy模块
$
modprobe
-
a
floppy
# 注意floppy模块的大小是69417字节:
$
lsmod
|
grep
floppy
floppy
69417
0
$
cat
/
proc
/
vmallocinfo
>
vmallocinfo
.
2
# 然而,我们看到vmallocinfo中记录的是分配了73728字节:
$
diff
vmallocinfo
.
1
vmallocinfo
.
2
68a69
>
0xffffffffa03d7000
-
0xffffffffa03e9000
73728
module_alloc_update_bounds
+
0x14
/
0x70
pages
=
17
vmalloc
N0
=
17
# 为什么lsmod看到的内存大小与vmallocinfo不同呢?
# 因为给kernel module分配内存是以page为单位的,而且外加一个guard page
# 我们来验证一下:
$
bc
-
q
69417
%
4096
3881
<
--
-
不能被
4096整除
69417
/
4096
16
<
--
-
相当于
16
pages,加上面的
3881字节,会分配
17
pages
18
*
4096
<
--
-
17
pages
加上
1个
guard
page
73728
<
--
-
正好是
vmallocinfo记录的大小
|
所以结论是kernel module所占用的内存包含在/proc/vmallocinfo的统计之中,不必再去计算”lsmod”的结果了,而且”lsmod”也不准。
6.1.4 HardwareCorrupted
当系统检测到内存的硬件故障时,会把有问题的页面删除掉,不再使用,/proc/meminfo中的HardwareCorrupted统计了删除掉的内存页的总大小。相应的代码参见 mm/memory-failure.c: memory_failure()。
6.1.5 PageTables
Page Table用于将内存的虚拟地址翻译成物理地址,随着内存地址分配得越来越多,Page Table会增大,/proc/meminfo中的PageTables统计了Page Table所占用的内存大小。
注:请把Page Table与Page Frame(页帧)区分开,物理内存的最小单位是page frame,每个物理页对应一个描述符(struct page),在内核的引导阶段就会分配好、保存在mem_map[]数组中,mem_map[]所占用的内存被统计在dmesg显示的reserved中,/proc/meminfo的MemTotal是不包含它们的。(在NUMA系统上可能会有多个mem_map数组,在node_data中或mem_section中)。
而Page Table的用途是翻译虚拟地址和物理地址,它是会动态变化的,要从MemTotal中消耗内存。
6.1.6 KernelStack
每一个用户线程都会分配一个kernel stack(内核栈),内核栈虽然属于线程,但用户态的代码不能访问,只有通过系统调用(syscall)、自陷(trap)或异常(exception)进入内核态的时候才会用到,也就是说内核栈是给kernel code使用的。在x86系统上Linux的内核栈大小是固定的8K或16K(可参阅我以前的文章:
内核栈溢出
)。
Kernel stack(内核栈)是常驻内存的,既不包括在LRU lists里,也不包括在进程的RSS/PSS内存里,所以我们认为它是kernel消耗的内存。统计值是/proc/meminfo的KernelStack。
6.1.7 Bounce
有些老设备只能访问低端内存,比如16M以下的内存,当应用程序发出一个I/O 请求,DMA的目的地址却是高端内存时(比如在16M以上),内核将在低端内存中分配一个临时buffer作为跳转,把位于高端内存的缓存数据复制到此处。这种额外的数据拷贝被称为“bounce buffering”,会降低I/O 性能。大量分配的bounce buffers 也会占用额外的内存。
6.2. 用户进程
/proc/meminfo统计的是系统全局的内存使用状况,单个进程的情况要看/proc/<pid>/下的smaps等等。
6.2.1 Hugepages
Hugepages在/proc/meminfo中是被独立统计的,与其它统计项不重叠,既不计入进程的RSS/PSS中,又不计入LRU Active/Inactive,也不会计入cache/buffer。如果进程使用了Hugepages,它的RSS/PSS不会增加。
注:不要把 Transparent HugePages (THP)跟 Hugepages 搞混了,THP的统计值是/proc/meminfo中的”AnonHugePages”,在/proc/<pid>/smaps中也有单个进程的统计,这个统计值与进程的RSS/PSS是有重叠的,如果用户进程用到了THP,进程的RSS/PSS也会相应增加,这与Hugepages是不同的。
在/proc/meminfo中与Hugepages有关的统计值如下:
MemFree
:
570736
kB
.
.
.
HugePages_Total
:
0
HugePages_Free
:
0
HugePages_Rsvd
:
0
HugePages_Surp
:
0
Hugepagesize
:
2048
kB
|
HugePages_Total 对应内核参数 vm.nr_hugepages,也可以在运行中的系统上直接修改 /proc/sys/vm/nr_hugepages,修改的结果会立即影响空闲内存 MemFree的大小,因为HugePages在内核中独立管理,只要一经定义,无论是否被使用,都不再属于free memory。在下例中我们设置256MB(128页)Hugepages,可以立即看到Memfree立即减少了262144kB(即256MB):
# echo 128 > /proc/sys/vm/nr_hugepages
# cat /proc/meminfo
.
.
.
MemFree
:
308592
kB
.
.
.
HugePages_Total
:
128
HugePages_Free
:
128
HugePages_Rsvd
:
0
HugePages_Surp
:
0
Hugepagesize
:
2048
kB
|
使用Hugepages有三种方式:
mount一个特殊的 hugetlbfs 文件系统,在上面创建文件,然后用mmap() 进行访问,如果要用 read() 访问也是可以的,但是 write() 不行。
通过shmget/shmat也可以使用Hugepages,调用shmget申请共享内存时要加上 SHM_HUGETLB 标志。
通过 mmap(),调用时指定MAP_HUGETLB 标志也可以使用Huagepages。
用户程序在申请Hugepages的时候,其实是reserve了一块内存,并未真正使用,此时/proc/meminfo中的 HugePages_Rsvd 会增加,而 HugePages_Free 不会减少。
HugePages_Total
:
128
HugePages_Free
:
128
HugePages_Rsvd
:
128
HugePages_Surp
:
0
Hugepagesize
:
2048
kB
|
等到用户程序真正读写Hugepages的时候,它才被消耗掉了,此时HugePages_Free会减少,HugePages_Rsvd也会减少。
HugePages_Total
:
128
HugePages_Free
:
0
HugePages_Rsvd
:
0
HugePages_Surp
:
0
Hugepagesize
:
2048
kB
|
我们说过,Hugepages是独立统计的,如果进程使用了Hugepages,它的RSS/PSS不会增加。下面举例说明,一个进程通过mmap()申请并使用了Hugepages,在/proc/<pid>/smaps中可以看到如下内存段,VmFlags包含的”ht”表示Hugepages,kernelPageSize是2048kB,注意RSS/PSS都是0:
.
.
.
2aaaaac00000
-
2aaabac00000
rw
-
p
00000000
00
:
0c
311151
/
anon_hugepage
(
deleted
)
Size
:
262144
kB
Rss
:
0
kB
Pss
:
0
kB
Shared_Clean
:
0
kB
Shared_Dirty
:
0
kB
Private_Clean
:
0
kB
Private_Dirty
:
0
kB
Referenced
:
0
kB
Anonymous
:
0
kB
AnonHugePages
:
0
kB
Swap
:
0
kB
KernelPageSize
:
2048
kB
MMUPageSize
:
2048
kB
Locked
:
0
kB
VmFlags
:
rd
wr
mr
mw
me
de
ht
.
.
.
|
6.2.2 AnonHugePages
AnonHugePages统计的是Transparent HugePages (THP),THP与Hugepages不是一回事,区别很大。
上一节说过,Hugepages在/proc/meminfo中是被独立统计的,与其它统计项不重叠,既不计入进程的RSS/PSS中,又不计入LRU Active/Inactive,也不会计入cache/buffer。如果进程使用了Hugepages,它的RSS/PSS不会增加。
而AnonHugePages完全不同,它与/proc/meminfo的其他统计项有重叠,首先它被包含在AnonPages之中,而且在/proc/<pid>/smaps中也有单个进程的统计,与进程的RSS/PSS是有重叠的,如果用户进程用到了THP,进程的RSS/PSS也会相应增加,这与Hugepages是不同的。下例截取自/proc/<pid>/smaps中的一段:
7efcf0000000
-
7efd30000000
rw
-
p
00000000
00
:
00
0
Size
:
1048576
kB
Rss
:
313344
kB
Pss
:
313344
kB
Shared_Clean
:
0
kB
Shared_Dirty
:
0
kB
Private_Clean
:
0
kB
Private_Dirty
:
313344
kB
Referenced
:
239616
kB
Anonymous
:
313344
kB
AnonHugePages
:
313344
kB
Swap
:
0
kB
KernelPageSize
:
4
kB
MMUPageSize
:
4
kB
Locked
:
0
kB
VmFlags
:
rd
wr
mr
mw
me
dc
ac
hg
mg
|
THP也可以用于shared memory和tmpfs,缺省是禁止的,打开的方法如下(详见
https://www.kernel.org/doc/Documentation/vm/transhuge.txt
):
mount时加上”huge=always”等选项
通过/sys/kernel/mm/transparent_hugepage/shmem_enabled来控制
因为缺省情况下shared memory和tmpfs不使用THP,所以进程之间不会共享AnonHugePages,于是就有以下等式:
【/proc/meminfo的AnonHugePages】==【所有进程的/proc/<pid>/smaps中AnonHugePages之和】
举例如下:
# grep AnonHugePages /proc/[1-9]*/smaps | awk '{total+=$2}; END {print total}'
782336
# grep AnonHugePages /proc/meminfo
AnonHugePages
:
782336
kB
|
6.2.3 LRU
LRU是Kernel的页面回收算法(Page Frame Reclaiming)使用的数据结构,在
解读vmstat中的Active/Inactive memory
一文中有介绍。Page cache和所有用户进程的内存(kernel stack和huge pages除外)都在LRU lists上。
LRU lists包括如下几种,在/proc/meminfo中都有对应的统计值:
LRU_INACTIVE_ANON – 对应 Inactive(anon)
LRU_ACTIVE_ANON – 对应 Active(anon)
LRU_INACTIVE_FILE – 对应 Inactive(file)
LRU_ACTIVE_FILE – 对应 Active(file)
LRU_UNEVICTABLE – 对应 Unevictable
注:
Inactive list里的是长时间未被访问过的内存页,Active list里的是最近被访问过的内存页,LRU算法利用Inactive list和Active list可以判断哪些内存页可以被优先回收。
括号中的 anon 表示匿名页(anonymous pages)。
用户进程的内存页分为两种:file-backed pages(与文件对应的内存页),和anonymous pages(匿名页),比如进程的代码、映射的文件都是file-backed,而进程的堆、栈都是不与文件相对应的、就属于匿名页。file-backed pages在内存不足的时候可以直接写回对应的硬盘文件里,称为page-out,不需要用到交换区(swap);而anonymous pages在内存不足时就只能写到硬盘上的交换区(swap)里,称为swap-out。
括号中的 file 表示 file-backed pages(与文件对应的内存页)。
Unevictable LRU list上是不能pageout/swapout的内存页,包括VM_LOCKED的内存页、SHM_LOCK的共享内存页(又被统计在”Mlocked”中)、和ramfs。在unevictable list出现之前,这些内存页都在Active/Inactive lists上,vmscan每次都要扫过它们,但是又不能把它们pageout/swapout,这在大内存的系统上会严重影响性能,设计unevictable list的初衷就是避免这种情况,参见:
LRU与/proc/meminfo中其他统计值的关系:
LRU中不包含HugePages_*。
LRU包含了 Cached 和 AnonPages。
6.2.4 Shmem
/proc/meminfo中的Shmem统计的内容包括:
shared memory
tmpfs。
此处所讲的shared memory又包括:
SysV shared memory [shmget etc.]
POSIX shared memory [shm_open etc.]
shared anonymous mmap [ mmap(…MAP_ANONYMOUS|MAP_SHARED…)]
因为shared memory在内核中都是基于tmpfs实现的,参见:
也就是说它们被视为基于tmpfs文件系统的内存页,既然基于文件系统,就不算匿名页,所以不被计入/proc/meminfo中的AnonPages,而是被统计进了:
Cached (i.e. page cache)
Mapped (当shmem被attached时候)
然而它们背后并不存在真正的硬盘文件,一旦内存不足的时候,它们是需要交换区才能swap-out的,所以在LRU lists里,它们被放在:
Inactive(anon) 或 Active(anon)
注:虽然它们在LRU中被放进了anon list,但是不会被计入 AnonPages。这是shared memory & tmpfs比较拧巴的一个地方,需要特别注意。
或 unevictable (如果被locked的话)
注意:
当shmget/shm_open/mmap创建共享内存时,物理内存尚未分配,要直到真正访问时才分配。/proc/meminfo中的 Shmem 统计的是已经分配的大小,而不是创建时申请的大小。
6.2.5 AnonPages
前面提到用户进程的内存页分为两种:file-backed pages(与文件对应的内存页),和anonymous pages(匿名页)。Anonymous pages(匿名页)的数量统计在/proc/meminfo的AnonPages中。
以下是几个事实,有助于了解Anonymous Pages:
所有page cache里的页面(Cached)都是file-backed pages,不是Anonymous Pages。”Cached”与”AnoPages”之间没有重叠。
注:shared memory 不属于 AnonPages,而是属于Cached,因为shared memory基于tmpfs,所以被视为file-backed、在page cache里,上一节解释过。
mmap private anonymous pages属于AnonPages(Anonymous Pages),而mmap shared anonymous pages属于Cached(file-backed pages),因为shared anonymous mmap也是基于tmpfs的,上一节解释过。
Anonymous Pages是与用户进程共存的,一旦进程退出,则Anonymous pages也释放,不像page cache即使文件与进程不关联了还可以缓存。
AnonPages统计值中包含了Transparent HugePages (THP)对应的 AnonHugePages 。参见:
fs
/
proc
/
meminfo
.
c
:
static
int
meminfo_proc_show
(
struct
seq_file *
m
,
void
*
v
)
{
.
.
.
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
K
(
global_page_state
(
NR_ANON_PAGES
)
+
global_page_state
(
NR_ANON_TRANSPARENT_HUGEPAGES
)
*
HPAGE_PMD_NR
)
,
.
.
.
|
6.2.6 Mapped
上面提到的用户进程的file-backed pages就对应着/proc/meminfo中的”Mapped”。Page cache中(“Cached”)包含了文件的缓存页,其中有些文件当前已不在使用,page cache仍然可能保留着它们的缓存页面;而另一些文件正被用户进程关联,比如shared libraries、可执行程序的文件、mmap的文件等,这些文件的缓存页就称为mapped。
/proc/meminfo中的”Mapped”就统计了page cache(“Cached”)中所有的mapped页面。”Mapped”是”Cached”的子集。
因为Linux系统上shared memory & tmpfs被计入page cache(“Cached”),所以被attached的shared memory、以及tmpfs上被map的文件都算做”Mapped”。
进程所占的内存页分为anonymous pages和file-backed pages,理论上应该有:
【所有进程的PSS之和】 == 【Mapped + AnonPages】。
然而我实际测试的结果,虽然两者很接近,却总是无法精确相等,我猜也许是因为进程始终在变化、采集的/proc/[1-9]*/smaps以及/proc/meminfo其实不是来自同一个时间点的缘故。
6.2.7 Cached
Page Cache里包括所有file-backed pages,统计在/proc/meminfo的”Cached”中。
Cached是”Mapped”的超集,就是说它不仅包括mapped,也包括unmapped的页面,当一个文件不再与进程关联之后,原来在page cache中的页面并不会立即回收,仍然被计入Cached,还留在LRU中,但是 Mapped 统计值会减小。【ummaped = (Cached – Mapped)】
Cached包含tmpfs中的文件,POSIX/SysV shared memory,以及shared anonymous mmap。
注:POSIX/SysV shared memory和shared anonymous mmap在内核中都是基于tmpfs实现的,参见:
“Cached”和”SwapCached”两个统计值是互不重叠的,源代码参见下一节。所以,Shared memory和tmpfs在不发生swap-out的时候属于”Cached”,而在swap-out/swap-in的过程中会被加进swap cache中、属于”SwapCached”,一旦进了”SwapCached”,就不再属于”Cached”了。
6.2.8 SwapCached
我们说过,匿名页(anonymous pages)要用到交换区,而shared memory和tmpfs虽然未统计在AnonPages里,但它们背后没有硬盘文件,所以也是需要交换区的。也就是说需要用到交换区的内存包括:”AnonPages”和”Shmem”,我们姑且把它们统称为匿名页好了。
交换区可以包括一个或多个交换区设备(裸盘、逻辑卷、文件都可以充当交换区设备),每一个交换区设备都对应自己的swap cache,可以把swap cache理解为交换区设备的”page cache”:page cache对应的是一个个文件,swap cache对应的是一个个交换区设备,kernel管理swap cache与管理page cache一样,用的都是radix-tree,唯一的区别是:page cache与文件的对应关系在打开文件时就确定了,而一个匿名页只有在即将被swap-out的时候才决定它会被放到哪一个交换区设备,即匿名页与swap cache的对应关系在即将被swap-out时才确立。
并不是每一个匿名页都在swap cache中,只有以下情形之一的匿名页才在:
匿名页即将被swap-out时会先被放进swap cache,但通常只存在很短暂的时间,因为紧接着在pageout完成之后它就会从swap cache中删除,毕竟swap-out的目的就是为了腾出空闲内存;
【注:参见mm/vmscan.c: shrink_page_list(),它调用的add_to_swap()会把swap cache页面标记成dirty,然后它调用try_to_unmap()将页面对应的page table mapping都删除,再调用pageout()回写dirty page,最后try_to_free_swap()会把该页从swap cache中删除。】
曾经被swap-out现在又被swap-in的匿名页会在swap cache中,直到页面中的内容发生变化、或者原来用过的交换区空间被回收为止。
【注:当匿名页的内容发生变化时会删除对应的swap cache,代码参见mm/swapfile.c: reuse_swap_page()。】
/proc/meminfo中的SwapCached背后的含义是:系统中有多少匿名页曾经被swap-out、现在又被swap-in并且swap-in之后页面中的内容一直没发生变化。也就是说,如果这些匿名页需要被swap-out的话,是无需进行I/O write操作的。
“SwapCached”不属于”Cached”,两者没有交叉。参见:
fs
/
proc
/
meminfo
.
c
:
static
int
meminfo_proc_show
(
struct
seq_file *
m
,
void
*
v
)
{
.
.
.
cached
=
global_page_state
(
NR_FILE_PAGES
)
-
total_swapcache_pages
(
)
-
i
.
bufferram
;
.
.
.
}
|
“SwapCached”内存同时也在LRU中,还在”AnonPages”或”Shmem”中,它本身并不占用额外的内存。
6.2.9 Mlocked
“Mlocked”统计的是被mlock()系统调用锁定的内存大小。被锁定的内存因为不能pageout/swapout,会从Active/Inactive LRU list移到Unevictable LRU list上。也就是说,当”Mlocked”增加时,”Unevictable”也同步增加,而”Active”或”Inactive”同时减小;当”Mlocked”减小的时候,”Unevictable”也同步减小,而”Active”或”Inactive”同时增加。
“Mlocked”并不是独立的内存空间,它与以下统计项重叠:LRU Unevictable,AnonPages,Shmem,Mapped等。
6.2.10 Buffers
“Buffers”表示块设备(block device)所占用的缓存页,包括:直接读写块设备、以及文件系统元数据(metadata)比如SuperBlock所使用的缓存页。它与“Cached”的区别在于,”Cached”表示普通文件所占用的缓存页。参见我的另一篇文章
http://linuxperf.com/?p=32
“Buffers”所占的内存同时也在LRU list中,被统计在Active(file)或Inactive(file)。
注:通过阅读源代码可知,块设备的读写操作涉及的缓存被纳入了LRU,以读操作为例,do_generic_file_read()函数通过 mapping->a_ops->readpage() 调用块设备底层的函数,并调用 add_to_page_cache_lru() 把缓存页加入到LRU list中。参见:
filemap.c: do_generic_file_read > add_to_page_cache_lru
7. 其它问题
DirectMap
/proc/meminfo中的DirectMap所统计的不是关于内存的使用,而是一个反映TLB效率的指标。TLB(Translation Lookaside Buffer)是位于CPU上的缓存,用于将内存的虚拟地址翻译成物理地址,由于TLB的大小有限,不能缓存的地址就需要访问内存里的page table来进行翻译,速度慢很多。为了尽可能地将地址放进TLB缓存,新的CPU硬件支持比4k更大的页面从而达到减少地址数量的目的, 比如2MB,4MB,甚至1GB的内存页,视不同的硬件而定。”DirectMap4k”表示映射为4kB的内存数量, “DirectMap2M”表示映射为2MB的内存数量,以此类推。所以DirectMap其实是一个反映TLB效率的指标。
Dirty pages到底有多少?
/proc/meminfo 中有一个Dirty统计值,但是它未能包括系统中全部的dirty pages,应该再加上另外两项:NFS_Unstable 和 Writeback,NFS_Unstable是发给NFS server但尚未写入硬盘的缓存页,Writeback是正准备回写硬盘的缓存页。即:
系统中全部dirty pages = ( Dirty + NFS_Unstable + Writeback )
注1:NFS_Unstable的内存被包含在Slab中,因为nfs request内存是调用kmem_cache_zalloc()申请的。
注2:anonymous pages不属于dirty pages。
参见mm/vmscan.c: page_check_dirty_writeback()
“Anonymous pages are not handled by flushers and must be written from reclaim context.”
为什么【Active(anon)+Inactive(anon)】不等于AnonPages?
因为Shmem(即Shared memory & tmpfs) 被计入LRU Active/Inactive(anon),但未计入 AnonPages。所以一个更合理的等式是:
【Active(anon)+Inactive(anon)】 = 【AnonPages + Shmem】
但是这个等式在某些情况下也不一定成立,因为:
如果shmem或anonymous pages被mlock的话,就不在Active(non)或Inactive(anon)里了,而是到了Unevictable里,以上等式就不平衡了;
当anonymous pages准备被swap-out时,分几个步骤:先被加进swap cache,再离开AnonPages,然后离开LRU Inactive(anon),最后从swap cache中删除,这几个步骤之间会有间隔,而且有可能离开AnonPages就因某些情况而结束了,所以在某些时刻以上等式会不平衡。
【注:参见mm/vmscan.c: shrink_page_list():
它调用的add_to_swap()会把swap cache页面标记成dirty,然后调用try_to_unmap()将页面对应的page table mapping都删除,再调用pageout()回写dirty page,最后try_to_free_swap()把该页从swap cache中删除。】
为什么【Active(file)+Inactive(file)】不等于Mapped?
因为LRU Active(file)和Inactive(file)中不仅包含mapped页面,还包含unmapped页面;
Mapped中包含”Shmem”(即shared memory & tmpfs),这部分内存被计入了LRU Active(anon)或Inactive(anon)、而不在Active(file)和Inactive(file)中。
为什么【Active(file)+Inactive(file)】不等于 Cached?
因为”Shmem”(即shared memory & tmpfs)包含在Cached中,而不在Active(file)和Inactive(file)中;
Active(file)和Inactive(file)还包含Buffers。
如果不考虑mlock的话,一个更符合逻辑的等式是:
【Active(file) + Inactive(file) + Shmem】== 【Cached + Buffers】
如果有mlock的话,等式应该如下(mlock包括file和anon两部分,/proc/meminfo中并未分开统计,下面的mlock_file只是用来表意,实际并没有这个统计值):
【Active(file) + Inactive(file) + Shmem +
mlock_file】== 【Cached + Buffers】
注:
测试的结果以上等式通常都成立,但内存发生交换的时候以上等式有时不平衡,我猜可能是因为有些属于Shmem的内存swap-out的过程中离开Cached进入了Swapcached,但没有立即从swap cache删除、仍算在Shmem中的缘故。
8. Linux的内存都用到哪里去了?
尽管不可能精确统计Linux系统的内存,但大体了解还是可以的。
kernel内存的统计方式应该比较明确,即
【Slab+
VmallocUsed
+ PageTables + KernelStack + HardwareCorrupted + Bounce + X】
注1:VmallocUsed其实不是我们感兴趣的,因为它还包括了VM_IOREMAP等并未消耗物理内存的IO地址映射空间,我们只关心VM_ALLOC操作,(参见1.2节),所以实际上应该统计/proc/vmallocinfo中的vmalloc记录,例如(此处单位是byte):
# grep vmalloc /proc/vmallocinfo | awk '{total+=$2}; END {print total}'
23375872
|
注2:kernel module的内存被包含在VmallocUsed中,见1.3节。
注3:X表示直接通过alloc_pages/__get_free_page分配的内存,没有在/proc/meminfo中统计,不知道有多少,就像个黑洞。
用户进程的内存主要有三种统计口径:
围绕LRU进行统计
【(Active + Inactive + Unevictable) + (HugePages_Total * Hugepagesize)】
围绕Page Cache进行统计
当SwapCached为0的时候,用户进程的内存总计如下:
【(Cached + AnonPages + Buffers) + (HugePages_Total * Hugepagesize)】
当SwapCached不为0的时候,以上公式不成立,因为SwapCached可能会含有Shmem,而Shmem本来被含在Cached中,一旦swap-out就从Cached转移到了SwapCached,可是我们又不能把SwapCached加进上述公式中,因为SwapCached虽然不与Cached重叠却与AnonPages有重叠,它既可能含有Shared memory又可能含有Anonymous Pages。
围绕RSS/PSS进行统计
把/proc/[1-9]*/smaps 中的 Pss 累加起来就是所有用户进程占用的内存,但是还没有包括Page Cache中unmapped部分、以及HugePages,所以公式如下:
ΣPss + (Cached – mapped) + Buffers + (HugePages_Total * Hugepagesize)
所以系统内存的使用情况可以用以下公式表示:
MemTotal = MemFree +【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】+【Active + Inactive + Unevictable + (HugePages_Total * Hugepagesize)】
MemTotal = MemFree +【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】+【Cached + AnonPages + Buffers + (HugePages_Total * Hugepagesize)】
MemTotal = MemFree +【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】+【ΣPss + (Cached – mapped) + Buffers + (HugePages_Total * Hugepagesize)】