Skip to content

Latest commit

 

History

History
369 lines (276 loc) · 19.5 KB

meminfo.md

File metadata and controls

369 lines (276 loc) · 19.5 KB

学习/proc/meminfo

pic/meminfo_all.png

MemTotal

MemTotal = 物理内存 – firmware/BIOS 保留内存 - kernel 本身占用内存

MemFree

  • MemFree表示系统尚未使用的内存
  • 已被用掉的内存 = MemTotal - MemFree

MemAvailable

  • MemAvailable 即系统可用的内存
  • MemAvailable = 部分可回收的内存 + MemFree
    • 系统中有些内存虽然已被使用但是可以回收的,比如 cache/buffer、slab 都有一部分可以回收

内存黑洞

  • Kernel的动态内存分配通过以下几种接口:
    • alloc_pages/__get_free_page: 以页为单位分配
    • vmalloc: 以字节为单位分配虚拟地址连续的内存块
    • slab allocator
      • kmalloc: 以字节为单位分配物理地址连续的内存块,它是以 slab 为基础的,使用 slab 层的 general caches — 大小为2^n,名称是kmalloc-32kmalloc-64等(在老 kernel 上的名称是size-32size-64等)。
  • 通过alloc_pages分配的内存不会自动统计,除非调用alloc_pages的内核模块或驱动程序主动进行统计,否则我们只能看到 free memory 减少了,但从/proc/meminfo中看不出它们具体用到哪里去了。

内核

内核内存

Memory: 3918160K/4093400K available (18445K kernel code, 2373K rwdata, 5424K rodata, 1716K init, 1884K bss, 174980K reserved, 0K cma-reserved)
  • Reserved
    • 初始化代码 init(.initsection)
    • 内核代码及数据
      • 内核数据是动态变化的,这里所说的是引导阶段截至mem_init为止所产生的数据,包括存放页描述符struct pagemem_map[]数组等
    • initrd
      • initrd 和初始化代码 init 在引导完成之后会被释放掉,所以最终的可用内存会比dmesg显示的available更多一点
  • Absent
    • 表示不可用的物理内存大小。譬如有一些物理内存被 BIOS 保留、对 kernel 是不可用的,这部分物理内存被计入absent之中
  • Available
    • available = 物理内存 – absentreserved

SLAB

  • SReclaimable
    • slab 中可回收的部分
    • 调用kmem_getpages()时加上SLAB_RECLAIM_ACCOUNT标记,表明是可回收的,计入SReclaimable,否则计入SUnreclaim
  • SUnreclaim
    • slab 中不可回收的部分
  • Slab
    • slab = SReclaimable + SUnreclaim

VmallocUsed

  • 通过vmalloc分配的内存
    • VM_ALLOC操作分配的内存从/proc/vmallocinfo中的vmalloc记录看到
      • 一些 driver 以及网络模块和文件系统模块可能会调用vmalloc
      • 加载内核模块(kernel module)时也会用到
  • 还有VM_IOREMAPVM_MAP等操作的值分配的物理内存
    • VM_IOREMAP是把IO地址映射到内核空间,并未消耗物理内存

HardwareCorrupted

  • 当系统检测到内存的硬件故障时,会把有问题的页面删除掉,不再使用,/proc/meminfo中的HardwareCorrupted统计了删除掉的内存页的总大小
  • 相应的代码参见 mm/memory-failure.c:memory_failure()

PageTables

  • 页表占用的空间,属于内核占用的空间
  • 不包括页面描述符(struct page)所占用的空间
    • 每个物理页对应一个描述符(struct page),在内核的引导阶段就会分配好,保存在mem_map[]数组中,
    • mem_map[]所占用的内存被统计在dmesg显示的reserved中,/proc/meminfoMemTotal是不包含它们的
    • 在NUMA系统上可能会有多个mem_map数组,在node_data中或mem_section

KernelStack

  • Kernel stack(内核栈)是常驻内存的,既不包括在 LRU lists 里,也不包括在进程的 RSS/PSS 内存里,所以我们认为它是 kernel 消耗的内存

Bounce

有些老设备只能访问低端内存,比如 16M 以下的内存,当应用程序发出一个 I/O 请求,DMA 的目的地址却是高端内存时(比如在 16M 以上),内核将在低端内存中分配一个临时 buffer 作为跳转,把位于高端内存的缓存数据复制到此处。这种额外的数据拷贝被称为“bounce buffering”,会降低 I/O 性能。大量分配的 bounce buffers 也会占用额外的内存。

用户进程

pic/meminfo_userspace.png

Hugepages

  • 独立统计的,与其它统计项不重叠,既不计入进程的 RSS/PSS 中,又不计入 LRU Active/Inactive,也不会计入 cache/buffer。如果进程使用了 Hugepages,它的 RSS/PSS 不会增加
  • 一个进程通过mmap()申请并使用了 Hugepages,在/proc/<pid>/smaps中可以看到如下内存段,VmFlags包含的ht表示Hugepages,kernelPageSize2048kB,注意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
    ...
    

HugePages_Total

  • 对应内核参数vm.nr_hugepages
  • 也可以在运行中的系统上直接修改/proc/sys/vm/nr_hugepages
  • 修改的结果会立即影响空闲内存MemFree的大小,因为 HugePages 在内核中独立管理,只要一经定义,无论是否被使用,都不再属于 free memory

使用 Hugepages 的三种方式

  1. mount一个特殊的 hugetlbfs 文件系统,在上面创建文件,然后用mmap()进行访问,如果要用read()访问也是可以的,但是write()不行
  2. 通过shmget/shmat也可以使用 Hugepages,调用shmget申请共享内存时要加上SHM_HUGETLB标志
  3. 通过mmap(),调用时指定MAP_HUGETLB标志也可以使用 Huagepages

HugePages_Rsvd

  • 用户程序在申请 Hugepages 的时候,其实是 reserve 了一块内存,并未真正使用,此时/proc/meminfo中的 HugePages_Rsvd会增加,而HugePages_Free不会减少

HugePages_Free

  • 等到用户程序真正读写 Hugepages 的时候,它才被消耗掉了,此时HugePages_Free会减少,HugePages_Rsvd也会减少

AnonHugePages

  • AnonHugePages统计的是 Transparent HugePages (THP)

  • AnonHugePages/proc/meminfo的其他统计项有重叠

    • 它被包含在AnonPages之中
    • /proc/<pid>/smaps中也有单个进程的统计
    • 与进程的RSS/PSS是有重叠的,如果用户进程用到了 THP,进程的RSS/PSS也会相应增加
  • THP 也可以用于 shared memory 和 tmpfs,缺省是禁止的

    • mount时加上huge=always等选项
    • 通过/sys/kernel/mm/transparent_hugepage/shmem_enabled来控制
  • 因为缺省情况下 shared memory 和 tmpfs 不使用 THP,所以进程之间不会共享AnonHugePages,于是就有以下等式:

    /proc/meminfo 的 AnonHugePages == 所有进程的 /proc/<pid>/smaps 中 AnonHugePages 之和
    

LRU

  • LRU 是 kernel 的页面回收算法(Page Frame Reclaiming)使用的数据结构
  • Page cache 和所有用户进程的内存(kernel stack 和 huge pages 除外)都在 LRU lists 上
  • LRU lists 包括如下几种:
    • 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 可以判断哪些内存页可以被优先回收
  • Unevictable LRU list 上是不能 page-out/swap-out 的内存页,包括
    • 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 中不包含HugePages_*
  • LRU 包含了CachedAnonPages

file-backed pages vs anonymous pages

  • 用户进程的内存页分为两种:file-backed pages(与文件对应的内存页),和 anonymous pages(匿名页)
    • file-backed:进程的代码、映射的文件等
    • anonymous: 进程的堆、栈都是不与文件相对应的,就属于匿名页
  • file-backed pages 在内存不足的时候可以直接写回对应的硬盘文件里,称为 page-out,不需要用到交换区(swap)
  • anonymous pages 在内存不足时就只能写到硬盘上的交换区(swap)里,称为 swap-out

Shmem

  • Shmem统计的内容包括
    • shared memory
      • SysV shared memory [shmget etc.]
      • POSIX shared memory [shm_open etc.]
      • shared anonymous mmap [mmap(...MAP_ANONYMOUS|MAP_SHARED...)]
    • tmpfs 和 devtmpfs
      • 所有 tmpfs 类型的文件系统占用的空间都计入共享内存
      • devtmpfs 是/dev文件系统的类型,/dev/下所有的文件占用的空间也属于共享内存。可以用lsdu命令查看
      • 如果文件在没有关闭的情况下被删除,空间仍然不会释放,shmem 不会减小,可以用lsof -a +L1 /<mount_point>命令列出这样的文件

shared memory

  • 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统计的是 已经分配的大小,而不是 创建时申请的大小

AnonPages

  • 所有 page cache 里的页面(Cached)都是 file-backed pages,不是 anonymous Pages。CachedAnonPages之间没有重叠。
    • 注: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

Mapped

Page cache 中(Cached)包含了文件的缓存页,其中有些文件当前已不在使用,page cache 仍然可能保留着它们的缓存页面;而另一些文件正被用户进程关联,比如 shared libraries、可执行程序的文件、mmap的文件等,这些文件的缓存页就称为 mapped

  • MappedCached的子集
  • 被 attached 的shared memory、以及 tmpfs上被 map 的文件都算做Mapped
  • 进程所占的内存页分为 anonymous pages 和 file-backed pages,理论上应该有:
    所有进程的PSS之和 == Mapped + AnonPages
    

Cached

  • Page Cache 里包括所有 file-backed pages,统计在/proc/meminfoCached
  • CachedMapped的超集
    • 就是说它不仅包括 mapped,也包括 unmapped 的页面,当一个文件不再与进程关联之后,原来在 page cache 中的页面并不会立即回收,仍然被计入Cached,还留在LRU中,但是Mapped统计值会减小
    • ummaped = Cached – Mapped
  • Cached包含Shmem
  • CachedSwapCached两个统计值是互不重叠的
    • 所以,Shared memory 和 tmpfs 在不发生 swap-out 的时候属于Cached
    • 而在 swap-out/swap-in 的过程中会被加进 swap cache 中,属于SwapCached,一旦进了SwapCached,就不再属于Cached

SwapCached

  • 交换区可以包括一个或多个交换区设备(裸盘、逻辑卷、文件都可以充当交换区设备)
  • 每一个交换区设备都对应自己的 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时才确立
  • /proc/meminfo中的SwapCached背后的含义是:
    • 系统中有多少匿名页曾经被 swap-out,现在又被 swap-in
    • 并且 swap-in 之后页面中的内容一直没发生变化
    • 也就是说,如果这些匿名页需要被 swap-out 的话,是无需进行 I/O write 操作的
  • SwapCached不属于Cached,两者没有交叉

Buffers

  • Buffers表示 块设备(block device)所占用的缓存页
    • 直接读写块设备
    • 文件系统元数据(metadata),比如 SuperBlock 所使用的缓存页
  • 它与Cached的区别在于,Cached表示 普通文件 所占用的缓存页
  • Buffers所占的内存同时也在 LRU list 中,被统计在Active(file)Inactive(file)

Mlocked

  • Mlocked统计的是被mlock()系统调用锁定的内存大小
  • 被锁定的内存因为不能 page-out/swap-out,会从 Active/Inactive LRU list 移到 Unevictable LRU list 上
    • 也就是说,当Mlocked增加时,Unevictable也同步增加,而ActiveInactive同时减小
    • Mlocked减小的时候,Unevictable也同步减小,而ActiveInactive同时增加
  • Mlocked并不是独立的内存空间,它与以下统计项重叠:LRU UnevictableAnonPagesShmemMapped

其它问题

DirectMap

  • /proc/meminfo中的DirectMap所统计的不是关于内存的使用,而是一个反映TLB效率的指标
    • 为了尽可能地将地址放进 TLB 缓存,新的 CPU 硬件支持比4k更大的页面从而达到减少地址数量的目的,比如2MB4MB,甚至1GB的内存页,视不同的硬件而定
    • DirectMap4k表示映射为4kB的内存数量,DirectMap2M表示映射为2MB的内存数量,以此类推。所以DirectMap其实是一个反映TLB效率的指标

Dirty pages 到底有多少?

  • 系统中全部 dirty pages = ( Dirty + NFS_Unstable + Writeback )
    • NFS_Unstable是发给 NFS server 但尚未写入硬盘的缓存页
    • Writeback是正准备回写硬盘的缓存页

为什么 Active(anon) + Inactive(anon) 不等于 AnonPages?

  • 因为Shmem(即 shared memory & tmpfs)被计入 LRU Active/Inactive(anon),但未计入AnonPages
  • 所以一个更合理的等式是:
    Active(anon) + Inactive(anon) =  AnonPages + Shmem
    

为什么 Active(file) + Inactive(file) 不等于 Mapped?

  1. 因为 LRU Active(file) 和 Inactive(file) 中不仅包含 mapped 页面,还包含 unmapped 页面
  2. Mapped中包含Shmem(即 shared memory & tmpfs),这部分内存被计入了 LRU Active(anon) 或 Inactive(anon),而不在 Active(file) 和 Inactive(file) 中

为什么 Active(file) + Inactive(file) 不等于 Cached?

  1. 因为Shmem(即 shared memory & tmpfs)包含在Cached中,而不在 Active(file) 和 Inactive(file) 中
  2. 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
    

Linux的内存都用到哪里去了?

kernel 内存的统计方式

Slab + VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X
  • 注1:VmallocUsed其实不是我们感兴趣的,因为它还包括了VM_IOREMAP等并未消耗物理内存的 IO 地址映射空间,我们只关心VM_ALLOC操作,所以实际上应该统计/proc/vmallocinfo中的vmalloc记录,例如(此处单位是byte
    # grep vmalloc /proc/vmallocinfo | awk '{total+=$2}; END {print total}'
    
  • kernel module 的内存被包含在VmallocUsed
  • X 表示直接通过alloc_pages/__get_free_page分配的内存,没有在/proc/meminfo中统计,不知道有多少,就像个黑洞

用户进程的内存主要有三种统计口径

  1. 围绕 LRU 进行统计

    (Active + Inactive + Unevictable) + (HugePages_Total * Hugepagesize)
    
  2. 围绕 Page Cache 进行统计

    • SwapCached0的时候,用户进程的内存总计如下:
    (Cached + AnonPages + Buffers) + (HugePages_Total * Hugepagesize)
    
    • SwapCached不为0的时候,以上公式不成立,因为SwapCached可能会含有Shmem,而Shmem本来被含在Cached中,一旦 swap-out 就从Cached转移到了SwapCached,可是我们又不能把SwapCached加进上述公式中,因为SwapCached虽然不与Cached重叠却与AnonPages有重叠,它既可能含有 shared memory 又可能含有 anonymous pages。
  3. 围绕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 + (Cachedmapped) + Buffers + (HugePages_Total * Hugepagesize))

References