本文将分享androiddumpsysmeminfo详解的详细内容,并且还将对androiddumphprof进行详尽解释,此外,我们还将为大家带来关于/proc/meminfo、/PROC/MEMI
本文将分享android dumpsys meminfo 详解的详细内容,并且还将对android dump hprof进行详尽解释,此外,我们还将为大家带来关于/proc/meminfo、/PROC/MEMINFO 之谜、/proc/meminfo 分析 (一)、ADB dumpsys View Hierarchy - 试图理解视图输出例如:V.ED..C........I.的相关知识,希望对你有所帮助。
本文目录一览:- android dumpsys meminfo 详解(android dump hprof)
- /proc/meminfo
- /PROC/MEMINFO 之谜
- /proc/meminfo 分析 (一)
- ADB dumpsys View Hierarchy - 试图理解视图输出例如:V.ED..C........I.
android dumpsys meminfo 详解(android dump hprof)
dumpsys meminfo详解
adb shell dumpsys meminfo [pkg/pid] 可以用来查看指定进程包名的内存使用情况
dumpsys meminfo -h 帮助
meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]
-a: include all available information for each process.
-d: include dalvik details.
-c: dump in a compact machine-parseable representation.
-s: dump only summary of application memory usage.
-S: dump also SwapPss.
--oom: only show processes organized by oom adj.
--local: only collect details locally, don't call process.
--package: interpret process arg as package, dumping all
processes that have loaded that package.
--checkin: dump data for a checkin
If [process] is specified it can be the name or
pid of a specific process to dump.
名词概念:
- 虚拟内存:进程空间内的虚拟内存地址,理论上32位cpu一个进程有4GB的虚拟内存可以使用。
- 物理内存:就是真正写的到内存条上的,真实地址对进程不可见,由操作系统把虚拟内存地址映射到物理内存地址。
- Size:指的就是分配了多少虚拟内存
- RSS、Pss指的是实际物理内存使用的大小,由于这个内存段是纯new出来的,没有共享库,所以这两个值是一样的。由于只给4MB的数组赋值,操作系统只给分配了4MB的真实物理内存。
- Objects是统计App内部组件对象个数,其中Views、ViewRootImpl以及Activities个数,在Activity onDestroy后应该都会回收清零,如果onDestroy调用后这几个对象个数没有清零,就可能发生了内存泄漏。
- android程序内存被分为2部分:native和dalvik,dalvik就是java堆,普通java对象是在java堆分配,而bitmap是直接在native上分配,对于内存的限制是 native+dalvik 不能超过最大限制。
名词解释:
名词 | 说明 | 补充 |
Uptime |
表示启动到现在的时长,不包含休眠的时间,单位毫秒(ms) |
|
Realtime |
表示启动到现在的时长,包含休眠的时间,单位毫秒(ms) |
|
Native Heap |
指c 中malloc出来的堆空间 |
扩展:c++申请的内存为native process,java申请的内存:java process |
Dalvik Heap |
指java中new出来的java堆空间 |
只是占用的虚拟内存的空间 |
Pss Total | 指占用了真实的物理内存的空间 | |
private dirty | 指私有驻留内存 | 扩展:进程内存空间是虚拟内存,区分于物理内存,进程无法直接操作物理内存RAM。必要时,操作系统对其进行映射,使进程能应用到物理内存 |
Heap Size | 指占用总内存(Heap 堆) | |
Heap Alloc | 指在虚拟地址中分配了这么多空间 | |
Heap Free | 空闲内存 |
注:因为Android系统对dalvik的vm heapsize作了硬性限制,当java进程申请的java空间超过阈值时,就会抛出OOM异常(这个阈值可以是48M、24M、16M等,视机型而定)
查看单个应用最大内存限制,输入命令:getprop|grep heapgrowthlimit 得到结果该机型为192M。dalvik process 超过就会抛OOM异常
Applications Memory Usage (in Kilobytes): Uptime: 246353123 Realtime: 292602983 ** MEMINFO in pid 8410 [com.doctopia.zeroe] ** Pss Private Private SwapPss Heap Heap Heap Total Dirty Clean Dirty Size Alloc Free ------ ------ ------ ------ ------ ------ ------ Native Heap 52421 52336 24 0 82432 61868 20563 Dalvik Heap 31844 31816 4 0 38652 30460 8192 Dalvik Other 14051 14048 0 12 Stack 3640 3640 0 28 Ashmem 80 48 0 0 Gfx dev 12848 12288 560 0 Other dev 76 0 76 0 .so mmap 2292 936 252 95 .jar mmap 0 0 0 0 .apk mmap 1642 76 1064 0 .ttf mmap 0 0 0 0 .dex mmap 6073 5860 132 4 .oat mmap 2153 0 0 0 .art mmap 2682 2408 8 82 Other mmap 312 8 184 0 EGL mtrack 432 432 0 0 GL mtrack 26160 26160 0 0 UnkNown 14579 14576 0 18 TOTAL 171524 164632 2304 239 121084 92328 28755 App Summary Pss(KB) ------ Java Heap: 34232 Native Heap: 52336 Code: 8320 Stack: 3640 Graphics: 39440 Private Other: 28968 System: 4588 TOTAL: 171524 TOTAL SWAP PSS: 239 Objects Views: 821 ViewRootImpl: 2 AppContexts: 6 Activities: 3 Assets: 6 AssetManagers: 3 Local Binders: 62 Proxy Binders: 36 Parcel memory: 24 Parcel count: 98 Death Recipients: 2 OpenSSL Sockets: 25 WebViews: 1 sql MEMORY_USED: 279 PAGECACHE_OVERFLOW: 76 MALLOC_SIZE: 62 DATABASES pgsz dbsz Lookaside(b) cache dbname 4 24 38 1254/33/7 /data/user/0/com.doctopia.zeroe/databases/sensorsdata 4 60 136 141/56/20 /data/user/0/com.doctopia.zeroe/databases/bugly_db_ Asset Allocations zip:/data/app/com.doctopia.zeroe-1/base.apk:/resources.arsc: 972K
其他常用服务信息查询
- 内存 adb shell dumpsys meminfo
- cpu adb shell dumpsys cpuinfo
- 帧率 adb shell dumpsys gfxinfo
- 显示 adb shell dumpsys display
- 电源 adb shell dumpsys power
- 电池状态 adb shell dumpsys batterystats
- 电池 adb shell dumpsys battery
- 闹钟 adb shell dumpsys alarm
- 位置 adb shell dumpsys location
/proc/meminfo
/proc/meminfo 可以查看自己服务器 物理内存
注意这个文件显示的单位是kB而不是KB,1kB=1000B,但是实际上应该是KB,1KB=1024B
这个显示是不精确的,是一个已知的没有被更正的历史遗留问题。因为很多程序依赖这个文件查看内存使用的是kB字符
服务器物理内存总大小 MemTotal
MemTotal是可使用内存的总量,单位是KB,物理内存减去一些保留内存和内核二进制代码占用的内存
MemFree 是服务器空闲内存,可用内存
MemFree 剩下没有被使用的物理内存,单位是kibibytes,即KB
cat /proc/meminfo
MemTotal: 32869848 kB
MemFree: 2526688 kB
Buffers: 315540 kB
Cached: 2313660 kB
SwapCached: 12472 kB
Active: 26041716 kB
Inactive: 3295808 kB
Active(anon): 24943784 kB
Inactive(anon): 1765616 kB
Active(file): 1097932 kB
Inactive(file): 1530192 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 4194296 kB
SwapFree: 2069908 kB
Dirty: 868 kB
Writeback: 0 kB
AnonPages: 26695932 kB
Mapped: 67316 kB
Shmem: 1004 kB
Slab: 639372 kB
SReclaimable: 576880 kB
SUnreclaim: 62492 kB
KernelStack: 5280 kB
PageTables: 145288 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 20629220 kB
Committed_AS: 237472752 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 331056 kB
VmallocChunk: 34342004412 kB
HardwareCorrupted: 0 kB
AnonHugePages: 18964480 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 5632 kB
DirectMap2M: 2082816 kB
DirectMap1G: 31457280 kB
/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()
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 |
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 记录看到:
1 2 3 4 5 6 7 8 9 |
# 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 记录,例如:
1 2 |
# grep vmalloc /proc/vmallocinfo | awk ''{total+=$2}; END {print total}'' 23375872 |
一些 driver 以及网络模块和文件系统模块可能会调用 vmalloc,加载内核模块 (kernel module) 时也会用到,可参见 kernel/module.c。
1.3 kernel modules (内核模块)
系统已经加载的内核模块可以用 lsmod 命令查看,注意第二列就是内核模块所占内存的大小,通过它可以统计内核模块所占用的内存大小,但这并不准,因为”lsmod” 列出的是 [init_size+core_size],而实际给 kernel module 分配的内存是以 page 为单位的,不足 1 page 的部分也会得到整个 page,此外每个 module 还会分到一页额外的 guard page。下文我们还会细说。
1 2 3 4 5 6 7 8 9 10 11 |
# 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,相应的源代码参见:
1 2 3 4 5 6 7 8 |
// 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 更准确,为什么这么说呢?
1 2 3 4 5 6 7 8 9 10 11 12 |
// 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 的内存小。我们做个实验来说明:
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 |
# 先卸载 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” 也不准。
1.4 HardwareCorrupted
当系统检测到内存的硬件故障时,会把有问题的页面删除掉,不再使用,/proc/meminfo 中的 HardwareCorrupted 统计了删除掉的内存页的总大小。相应的代码参见 mm/memory-failure.c: memory_failure()。
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 中消耗内存。
1.6 KernelStack
每一个用户线程都会分配一个 kernel stack(内核栈),内核栈虽然属于线程,但用户态的代码不能访问,只有通过系统调用 (syscall)、自陷 (trap) 或异常 (exception) 进入内核态的时候才会用到,也就是说内核栈是给 kernel code 使用的。在 x86 系统上 Linux 的内核栈大小是固定的 8K 或 16K(可参阅我以前的文章:内核栈溢出)。
Kernel stack(内核栈)是常驻内存的,既不包括在 LRU lists 里,也不包括在进程的 RSS/PSS 内存里,所以我们认为它是 kernel 消耗的内存。统计值是 /proc/meminfo 的 KernelStack。
1.7 Bounce
有些老设备只能访问低端内存,比如 16M 以下的内存,当应用程序发出一个 I/O 请求,DMA 的目的地址却是高端内存时(比如在 16M 以上),内核将在低端内存中分配一个临时 buffer 作为跳转,把位于高端内存的缓存数据复制到此处。这种额外的数据拷贝被称为 “bounce buffering”,会降低 I/O 性能。大量分配的 bounce buffers 也会占用额外的内存。
2. 用户进程
/proc/meminfo 统计的是系统全局的内存使用状况,单个进程的情况要看 /proc/<pid>/ 下的 smaps 等等。
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 有关的统计值如下:
1 2 3 4 5 6 7 |
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):
1 2 3 4 5 6 7 8 9 10 |
# 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 有三种方式:
(详见 https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt)
- mount 一个特殊的 hugetlbfs 文件系统,在上面创建文件,然后用 mmap () 进行访问,如果要用 read () 访问也是可以的,但是 write () 不行。
- 通过 shmget/shmat 也可以使用 Hugepages,调用 shmget 申请共享内存时要加上 SHM_HUGETLB 标志。
- 通过 mmap (),调用时指定 MAP_HUGETLB 标志也可以使用 Huagepages。
用户程序在申请 Hugepages 的时候,其实是 reserve 了一块内存,并未真正使用,此时 /proc/meminfo 中的 HugePages_Rsvd 会增加,而 HugePages_Free 不会减少。
1 2 3 4 5 |
HugePages_Total: 128 HugePages_Free: 128 HugePages_Rsvd: 128 HugePages_Surp: 0 Hugepagesize: 2048 kB |
等到用户程序真正读写 Hugepages 的时候,它才被消耗掉了,此时 HugePages_Free 会减少,HugePages_Rsvd 也会减少。
1 2 3 4 5 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
... 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 ... |
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 中的一段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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 之和】
举例如下:
1 2 3 4 |
# grep AnonHugePages /proc/[1-9]*/smaps | awk ''{total+=$2}; END {print total}'' 782336 # grep AnonHugePages /proc/meminfo AnonHugePages: 782336 kB |
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 的初衷就是避免这种情况,参见:
https://www.kernel.org/doc/Documentation/vm/unevictable-lru.txt
LRU 与 /proc/meminfo 中其他统计值的关系:
- LRU 中不包含 HugePages_*。
- LRU 包含了 Cached 和 AnonPages。
2.4 Shmem
/proc/meminfo 中的 Shmem 统计的内容包括:
- shared memory
- tmpfs 和 devtmpfs。
注:所有 tmpfs 类型的文件系统占用的空间都计入共享内存,devtmpfs 是 /dev 文件系统的类型,/dev/ 下所有的文件占用的空间也属于共享内存。可以用 ls 和 du 命令查看。如果文件在没有关闭的情况下被删除,空间仍然不会释放,shmem 不会减小,可以用 “lsof -a +L1 /<mount_point>” 命令列出这样的文件。
此处所讲的 shared memory 又包括:
- SysV shared memory [shmget etc.]
- POSIX shared memory [shm_open etc.]
- shared anonymous mmap [ mmap(…MAP_ANONYMOUS|MAP_SHARED…)]
因为 shared memory 在内核中都是基于 tmpfs 实现的,参见:
https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt
也就是说它们被视为基于 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 统计的是已经分配的大小,而不是创建时申请的大小。
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 。参见:
1 2 3 4 5 6 7 8 9 10 |
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), ... |
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 其实不是来自同一个时间点的缘故。
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 实现的,参见:
https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt
“Cached” 和”SwapCached” 两个统计值是互不重叠的,源代码参见下一节。所以,Shared memory 和 tmpfs 在不发生 swap-out 的时候属于”Cached”,而在 swap-out/swap-in 的过程中会被加进 swap cache 中、属于”SwapCached”,一旦进了”SwapCached”,就不再属于”Cached” 了。
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”,两者没有交叉。参见:
1 2 3 4 5 6 7 8 |
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” 中,它本身并不占用额外的内存。
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 等。
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
其它问题
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 中的缘故。
Linux 的内存都用到哪里去了?
尽管不可能精确统计 Linux 系统的内存,但大体了解还是可以的。
kernel 内存的统计方式应该比较明确,即
【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】
- 注 1:VmallocUsed 其实不是我们感兴趣的,因为它还包括了 VM_IOREMAP 等并未消耗物理内存的 IO 地址映射空间,我们只关心 VM_ALLOC 操作,(参见 1.2 节),所以实际上应该统计 /proc/vmallocinfo 中的 vmalloc 记录,例如(此处单位是 byte):
1 2 |
# 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)】
/proc/meminfo 分析 (一)
本文主要分析 /proc/meminfo 文件的各种输出信息的具体含义。
一、MemTotal
MemTotal 对应当前系统中可以使用的物理内存。
这个域实际是对应内核中的 totalram_pages 这个全局变量的,定义如下:
unsigned long totalram_pages __read_mostly;
该变量表示当前系统中 Linux 内核可以管理的所有的 page frame 的数量。注意:这个值并不是系统配置的内存总数,而是指操作系统可以管理的内存总数。
内核是如何得到 totalram_pages 这个值的呢?是在初始化的过程中得到的,具体如下:我们知道,在内核初始化的时候,我们可以通过 memblock 模块来管理内存布局,从而得到了 memory type 和 reserved type 的内存列表信息。在初始化阶段如果有内存分配的需求,那么也可以通过 memblock 来完成,直到伙伴系统初始化完成,而在这个过程中,memory type 的那些 page frame 被一个个的注入到各个 zone 的 free list 中,同时用 totalram_pages 来记录那个时间点中空闲的 page frame 数量。这个点也就是伙伴系统的一个初始内存数量的起点。
举一个实际的例子:我的 T450 的计算机,配置的内存是 8G,也就是 8388608KB。但是 MemTotal 没有那么多,毕竟 OS 本身(正文段、数据段等等)就会占用不少内存,此外还有系统各种保留的内存,把这些都去掉,Linux 能管理的 Total memory 是 7873756KB。看来 OS 内存管理的开销也不小啊。
二、MemFree
启动的时候,系统确定了 MemTotal 的数目,但是随着系统启动过程,内核会动态申请内存,此外,用户空间也会不断创建进程,也会不断的消耗内存,因此 MemTotal 可以简单分成两个部分:正在使用的和空闲的。MemFree 表示的就是当前空闲的内存数目,这些空闲的 page 应该是挂在各个 node 的各个 zone 的 buddy 系统中。
具体 free memory 的计算如下:
freeram = global_page_state(NR_FREE_PAGES);
也就是把整个系统中在当前时间点上空闲的内存数目统计出来。
三、MemAvailable
所谓 memory available,其实就是不引起 swapping 操作的情况下,我们能使用多少的内存。即便是 free 的,也不是每一个 page 都可以用于内存分配。例如 buddy system 会设定一个水位,一旦 free memory 低于这个水位,系统就会启动 page reclaim,从而可能引起 swapping 操作。因此,我们需要从 MemFree 这个数值中去掉各个节点、各个 zone 的预留的内存(WMARK_LOW)数目。当然,也是不是说那些不是空闲的页面我们就不能使用,例如 page cache,虽然不是空闲的,但是它也不过是为了加快性能而使用,其实也可以回收使用。当然,也不是所有的 page cache 都可以计算进入 MemAvailable,有些 page cache 在回收的时候会引起 swapping,这些 page cache 就不能算数了。此外,reclaimable slab 中也有一些可以使用的内存,MemAvailable 也会考虑这部分内存的情况。
总而言之,MemAvailable 就是不需要额外磁盘操作(开销较大)就可以使用的空闲内存的数量。
四、Buffers
其实新的内核已经没有 buffer cache 了,一切都统一到了 page cache 的框架下了。因此,所谓的 buffer cache 就是块设备的 page cache。具体的统计过程是通过 nr_blockdev_pages 函数完成,代码如下:
long nr_blockdev_pages(void)
{
struct block_device *bdev;
long ret = 0;
spin_lock(&bdev_lock);
list_for_each_entry(bdev, &all_bdevs, bd_list) {
ret += bdev->bd_inode->i_mapping->nrpages;
}
spin_unlock(&bdev_lock);
return ret;
}
我们知道,内核是通过 address_space 来管理 page cache 的,那么块设备的 address_space 在哪里呢?这个稍微复杂一点,涉及多个 inode,假设 /dev/aaa 和 /dev/bbb 都是指向同一个物理块设备,那么 open/dev/aaa 和 /dev/bbb 会分别产生两个 inode,我们称之 inode_aaa 和 inode_bbb,但是最后一个块设备的 page cache 还是需要统一管理起来,不可能分别在 inode_aaa 和 inode_bbb 中管理。因此,Linux 构造了一个 bdev 文件系统,保存了系统所有块设备的 inode,我们假设该物理块设备在 bdev 文件系统中的 inode 是 inode_bdev。上面讲了这么多的 inode,其实块设备的 page cache 就是在 inode_bdev 中管理的。
一般来说,buffers 的数量不多,因为产生 buffer 的操作包括:
1、打开该 block device 的设备节点,直接进行读写操作(例如 dd 一个块设备)
2、mount 文件系统的时候,需要从相应的 block device 上直接把块设备上的特定文件系统的 super block 相关信息读取出来,这些 super block 的 raw data 会保存在该 block device 的 page cache 中
3、文件操作的时候,和文件元数据相关的操作(例如读取磁盘上的 inode 相关的信息)也是通过 buffer cache 进行访问。
Linux 中最多处理的是 2 和 3 的操作,1 的操作很少有。
五、Cached
读写普通文件的时候,我们并不会直接操作磁盘,而是通过 page cache 来加速文件 IO 的性能。Cached 域描述的就是用于普通文件 IO 的 page cache 的数量,具体计算过程如下:
cached = global_page_state(NR_FILE_PAGES) -
total_swapcache_pages() - i.bufferram;
系统中所有的 page cache 都会被记录在一个全局的状态中,通过 global_page_state (NR_FILE_PAGES) 可以知道这个数据,这个数据包括:
1、普通文件的 page cache
2、block device 的 page cache。参考上一节的描述。
3、swap cache。下一节会进一步描述。
对于 Cached 这个域,我们只关心普通文件的 page cache,因此要从 page cache 的 total number 中减去 buffer cache 和 swap cache。
六、SwapCached
其实上一节已经提及 swap cache 了,也归属于 page cache 中,具体计算如下:
unsigned long total_swapcache_pages(void)
{
int i;
unsigned long ret = 0;for (i = 0; i < MAX_SWAPFILES; i++)
ret += swapper_spaces[i].nrpages;
return ret;
}
和其他的 page cache 不一样,swap cache 并不是为了加快磁盘 IO 的性能,它是为了解决 page frame 和 swap area 之间的同步问题而引入的。例如:一个进程准备 swap in 一个 page 的时候,内核的内存回收模块可能同时也在试图将这个 page swap out。为了解决这些这些同步问题,内核引入了 swap cache 这个概念,在任何模块进行 swap in 或者 swap out 的时候,都必须首先去 swap cache 中去看看,而借助 page descriptor 的 PG_locked 的标记,我们可以避免 swap 中的 race condition。
swap cache 在具体实现的时候,仍然借用了 page cache 的概念,每一个 swap area 都有一个 address space,管理该 swap device(或者 swap file)的 page cache。因此,一个 swap device 的所有 swap cache 仍然是保存在对应 address space 的 radix tree 中(仍然是熟悉的配方,仍然是熟悉的味道啊)。
ADB dumpsys View Hierarchy - 试图理解视图输出例如:V.ED..C........I.
如何解决ADB dumpsys View Hierarchy - 试图理解视图输出例如:V.ED..C........I.
所以当你运行命令时:
adb shell dumpsys activity <my_package>
它向屏幕输出了一大堆有趣的输出,其中一个部分是 “View Hierarchy” 部分,它显示了一个可爱的嵌套视图层次结构,每行包含一个 android 视图或视图组。
这是一个示例行:
androidx.appcompat.widget.AppCompatimageView{43dece0 VFED..C.. ......I. 0,0-0,0 #7f0a00cc app:id/my_button}
构成这条线的大部分内容都有意义...
- androidx.appcompat.widget.AppCompatimageView = 视图类
- 43dece0 = 视图的哈希码(或任何正确的名称)
- app:id/my_button = 视图 ID
- 0,0 = 视图的边界(我假设是从左上到右下的坐标)
- #7f0a00cc = 甚至可能是视图的背景颜色
然而,我遇到的真正问题(我一生都无法在任何地方找到答案,我什至不得不查看 adb 的源代码)是我不明白这一部分:
VFED..C.. ......I.
这个东西是什么意思?
我忙得不可开交,想看看当我更改视图的某些内容时,这些字母和圆点是否会发生变化。我使视图“GONE”和末尾的“I”出现,当视图为“VISIBLE”时,“我”走了,所以也许我要处理“隐形”视图,但这只是一个猜测。那么也许这些是视图标志?或查看状态?
我想清楚地知道这些是什么意思。是否有词汇表可以清楚地解释这些“标志”中的每一个及其含义?
请有人在这里帮助我,我在网络上找不到任何解释这些“标志”的地方,我很想真正而完全地理解它们,而不必弄乱我的代码并尝试任何可以想象的迭代查看。
谢谢!
今天关于android dumpsys meminfo 详解和android dump hprof的讲解已经结束,谢谢您的阅读,如果想了解更多关于/proc/meminfo、/PROC/MEMINFO 之谜、/proc/meminfo 分析 (一)、ADB dumpsys View Hierarchy - 试图理解视图输出例如:V.ED..C........I.的相关知识,请在本站搜索。
本文标签: