Linux内核:内存管理—— 内存统计

1. /proc/meminfo

/prox/meminfoLinux系统统计内存状态非常重要的接口,上层的free亦或者Android系统的lmk读取内存信息都来源于这个接口,其实现也非常简单,就是将内核中记录的各种内核数据打印出来,内存信息也非常全!

如下是kernel-5.10版本输出的信息:

MemTotal:        7334508 kB
MemFree:         1327068 kB
MemAvailable:    3464796 kB
Buffers:            2016 kB
Cached:          1888764 kB
SwapCached:         5848 kB
Active:           776884 kB
Inactive:        1769432 kB
Active(anon):     406908 kB
Inactive(anon):   394548 kB
Active(file):     369976 kB
Inactive(file):  1374884 kB
Unevictable:      128488 kB
Mlocked:          127256 kB
SwapTotal:       6291452 kB
SwapFree:        4613628 kB
Dirty:               740 kB
Writeback:             0 kB
AnonPages:        779480 kB
Mapped:           584580 kB
Shmem:             20476 kB
KReclaimable:     782960 kB
Slab:             504928 kB
SReclaimable:     128860 kB
SUnreclaim:       376068 kB
KernelStack:       74848 kB
ShadowCallStack:   18792 kB
PageTables:       129676 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     9958704 kB
Committed_AS:   141040480 kB
VmallocTotal:   262930368 kB
VmallocUsed:      193260 kB
VmallocChunk:          0 kB
Percpu:            10336 kB
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:      4096 kB
FilePmdMapped:      4096 kB
CmaTotal:         483328 kB
CmaFree:          139088 kB
  • MemTotal

上面提到,/proc/meminfo输出的是内核记录的内存信息,其和实际的物理内存信息是有差异的,MemTotal指的是内存管理的总物理内存,比如我这个设备配置的是8G的内存,但是其输出的总内存只有7334508,这是因为本身OS进行内存管理就需要占用到一定的内存,可参考memblock内存管理模块。内核通过_totalram_pages原子变量记录。

  • MemFree

系统中可以立即被使用的内存。

  • MemAvailable

系统可用内存,跟MemFree是有区分的,因为内核的一些缓存以及swap机制,部分内存可以通过”丢弃“、回写或者压缩,交换等动作而回收,然而,精确的判断系统可用内存是非常困难的,也并不需要精确判断,因为系统内存一直都是一个动态调整的过程,所以Linux内核采用一种估算的方法,估算的算法也非常简单,如下是kernel-5.10估算系统可用内存的方法:

long si_mem_available(void)
{
	long available;
	unsigned long pagecache;
	unsigned long wmark_low = 0;
	unsigned long pages[NR_LRU_LISTS];
	unsigned long reclaimable;
	struct zone *zone;
	int lru;

	for (lru = LRU_BASE; lru < NR_LRU_LISTS; lru++)
		pages[lru] = global_node_page_state(NR_LRU_BASE + lru);

	for_each_zone(zone)
		wmark_low += low_wmark_pages(zone);

	/*
	 * Estimate the amount of memory available for userspace allocations,
	 * without causing swapping.
	 */
	available = global_zone_page_state(NR_FREE_PAGES) - totalreserve_pages;

	/*
	 * Not all the page cache can be freed, otherwise the system will
	 * start swapping. Assume at least half of the page cache, or the
	 * low watermark worth of cache, needs to stay.
	 */
	pagecache = pages[LRU_ACTIVE_FILE] + pages[LRU_INACTIVE_FILE];
	pagecache -= min(pagecache / 2, wmark_low);
	available += pagecache;

	/*
	 * Part of the reclaimable slab and other kernel memory consists of
	 * items that are in use, and cannot be freed. Cap this estimate at the
	 * low watermark.
	 */
	reclaimable = global_node_page_state_pages(NR_SLAB_RECLAIMABLE_B) +
		global_node_page_state(NR_KERNEL_MISC_RECLAIMABLE);
	available += reclaimable - min(reclaimable / 2, wmark_low);

	if (available < 0)
		available = 0;
	return available;
}

也就是:
MemAvailable = MemFree - Reserve + pagecache - min(pagecache / 2, wmark_low) + reclaimable - min(reclaimable / 2, wmark_low)
其中:pagecache 为 File LRU的总大小,reclaimable则是slab以及KReclaimable
可以看出来,内核中认为可用内存,并没有将Anon内存可以被压缩到Zram中的部分纳入考虑

  • Buffers`Cached\Swapcached`

这三个都是和文件页相关的缓存,Cached的计算公式也非常简单:

cached = global_node_page_state(NR_FILE_PAGES) - total_swapcache_pages() - i.bufferram;

其中,global_node_page_state(NR_FILE_PAGES)即为系统文件页的总大小,分三部分:

Cached:普通文件的 page cached
Buffers
:块设备的 page cached
Swapcached
:这个是为了内存中的文件页与磁盘中的文件读入\读出设置的一个内存区域
  • Active`Inactive\Active(anon)\Inactive(anon)\Active(file)\Inactive(file)\Unevictable`

这几个都是和内核中的LRU链表有关,是对应各个链表的大小

  • SwapTotal`SwapFree`

swap相关的内存大小,现在比较流行的就是使用Zram去充当SwapZram是通过压缩算法将内存压缩,然后依旧保存在主存中,只会设计cpu计算,不会设计IO交换

  • Shmem
val->sharedram = global_node_page_state(NR_SHMEM);
NR_SHMEM,		/* shmem pages (included tmpfs/GEM pages) */ 

包含共享内存以及tmpfs文件系统占用的内存

  • Slab`SReclaimable\SUnreclaim`

这个是统计slab占用的内存,slab相关的内存释放可以查看shrink_slab()

为什么slab相关的page不统计到LRU中, 猜测是slab管理的内存较小,以slab object为单位,而LRU是以page为单位管理的

  • KReclaimable

KReclaimable就是在SReclaimable的基础上加上MISC_RECLAIMABLE,比如Android”free“归还给ion page pool的内存。

show_val_kb(m, "KReclaimable:   ", sreclaimable + global_node_page_state(NR_KERNEL_MISC_RECLAIMABLE));
  • VmallocTotal`VmallocUsed`

VmallocTotal其实就是内核空间中Vmalloc区域的大小,范围是VMALLOC_END - VMALLOC_START

VmallocUsed是内核通过vmalloc申请的内存总大小,部分操作仅仅是申请或者是映射,并未分配具体的物理内存,所以该数值不能代表物理内存大小消耗

  • PageTables

内核保存页表(PTE)所耗费的内存

  • CmaTotal`CmaFree`

常见疑问:

  • Inactive/active(file)为什么不等于Cache

Cache的计算公式是系统所有File类型的页面减去SwapCached以及Buffers,部分File类型的page会被加入到Mlock以及Unevictable

  • Inactive/active(anon)为什么不等于AnonPages

这个原因类似于File类型page,但是并不是说Inactive(anon)+active(anon);因为Shmem会被记录到anon LRU当中

  • Inactive/active(file)为什么不等于Mapped

对于文件页,分为Mapped以及Unmapped,所谓的Mapped就是被进程所映射的文件页,也就是关联到一个或者多个进程的文件页,Unmapped即为无关联进程的文件页,有点类似于引用计数的flag,被Unmapped的文件页虽然无关联的进程,但是不会立马被回收,因为回收的动作触发并不是针对某一个页面而言,而是继续驻留在LRU中,可能会被置换到Inactive,然后被回收

  • Inactive/active(anon/file) + Unevictable能代表整机已经使用的内存吗?什么样的page会挂载到LRU上?操作系统有多少组LRU

这原本是三个疑问,但是都是彼此关联的,合到一起分析;当系统完成完成初始化之后,BuddySystem会负责管理系统内存管理,所以LRU相关的页面都是通过BuddySystem申请的,而__alloc_page是BuddySystem开放的底层接口,通过这个接口衍生出很多内存的申请:

malloc:用户空间堆内存申请
文件页的相关操作: open/ mmap
vmalloc:内核空间 VMALLOC段内存申请
ION/ GPU内存:这个主要看自定义的申请接口,可以看到这些“第三方”的内存申请其实是在 BuddySystem基础上再做了一层内存管理
Slab:内核中避免内部碎片的内存管理模块

这里,只有一个(1)/(2)会挂载到LRU,这两个中内存的申请都有一个特点,使用过程可能会发生缺页异常(Page Fault),而挂载LRU就是在处理缺页异常的过程中实现的,所以LRU相关的内存并不能代表整个系统的内存使用,可以参考__pagevec_lru_add_fn的调用过程,这是加入LRU的唯一入口

操作系统有多少组LRU,这个跟MEMCG相关:

对于未使能 MEMCG,一个 Node会有一组 LRU,即 lruvec
对于使能 MEMCG,每一组 memcg都会有一组 LRU
  • 一个系统具有多少个BuddySystem?系统所有的内存都交由BuddySystem管理吗?

BuddySystem管理的内存单位是一个Zone,即每一个Zone都会有一个BuddySystem

# cat /proc/buddyinfo
Node 0, zone    DMA32      9     17    126     71    157    560    317     34      8     25      0 
Node 0, zone   Normal    106   3411    514    189     62     18     19      9      2      1      0 

并非所有的内存都交由BuddySystem管理,因为在bootTimeBuddySystem未建立之前,也需要内存管理,从内核打印的信息可以看BuddySystem管理的内存总数:

	pr_info("Memory: %luK/%luK available (%luK kernel code, %luK rwdata, %luK rodata, %luK init, %luK bss, %luK reserved, %luK cma-reserved"
#ifdef	CONFIG_HIGHMEM
		", %luK highmem"
#endif
		"%s%s)\n",
		nr_free_pages() << (PAGE_SHIFT - 10),
		physpages << (PAGE_SHIFT - 10),
		codesize >> 10, datasize >> 10, rosize >> 10,
		(init_data_size + init_code_size) >> 10, bss_size >> 10,
		(physpages - totalram_pages() - totalcma_pages) << (PAGE_SHIFT - 10),
		totalcma_pages << (PAGE_SHIFT - 10),
#ifdef	CONFIG_HIGHMEM
		totalhigh_pages() << (PAGE_SHIFT - 10),
#endif
		str ? ", " : "", str ? str : "");

adb shell dmesg | grep "Memory:"
[    0.000000] Memory: 6796336K/8131584K available (28800K kernel code, 2082K rwdata, 10608K rodata, 3008K init, 975K bss, 851920K reserved, 483328K cma-reserved)

其中6796336K即为BuddySystem管理的所有内存???这个是一个BuddySystem初始化完成之后,系统剩余的内存

  • 内核空间分配的内存会区分FileAnon

2. free

c@M:~$ adb shell free -h
		total        used        free      shared     buffers
Mem:    6.9G         6.6G        365M      4.3M       11M
-/+ buffers/cache:   6.6G        376M
Swap:   6.0G         234M        5.7G

free命令比较简单,仅列出当前系统总内存,已使用内存、剩余内存以及合并缓存之后系统内存情况

3. dumpsys mmeinfo -S

dumpsysAndroid系统提供的工具,可以查看系统的各种信息,其中dump meminfo -S可以看到系统每一个进程的内存使用情况(包含RSS\PSS\Swap),还可以通过分优先级查看,同时还能查看当前系统总内存情况:

.....
Total RAM: 7,334,496K (status normal)
 Free RAM: 4,300,869K (  327,225K cached pss + 3,435,420K cached kernel +   538,224K free)
DMA-BUF:   586,304K (   44,928K mapped +   541,376K unmapped)
DMA-BUF Heaps:   586,304K
DMA-BUF Heaps pool:   292,736K
      GPU:   697,736K
 Used RAM: 3,639,797K (2,393,417K used pss + 1,246,380K kernel)
 Lost RAM:   790,816K
     ZRAM:   338,440K physical used for 1,165,540K in swap (6,291,452K total swap)
   Tuning: 256 (large 512), oom   322,560K, restore limit   107,520K (high-end-gfx)
  • cached pss

这个名字会导致误解,看源码实现则很清晰,其实这就是cached优先级进程所占用的PSS内存

  • cached kernel

这个才是真正的cache内存:

mInfos[Debug.MEMINFO_BUFFERS] + kReclaimable + + mInfos[Debug.MEMINFO_CACHED]

所以你会发现其实都是/proc/meminfo下各种数据的组合

  • used pss

就是把系统中除去cached优先级的所有进程的PSS相加起来

  • kernel

这相当于kernel的统计内存,包含ShmemSUnreclaim、通过vmalloc分配且为VM_ALLOC的部分…:

 /**
     * Amount of RAM that is in use by the kernel for actual allocations.
     */
    public long getKernelUsedSizeKb() {
        long size = mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB_UNRECLAIMABLE]
                + mInfos[Debug.MEMINFO_VM_ALLOC_USED] + mInfos[Debug.MEMINFO_PAGE_TABLES];
        if (!Debug.isVmapStack()) {
            size += mInfos[Debug.MEMINFO_KERNEL_STACK];
        }
        return size;
    }

同时还会将Unmapped类型的dmabuf或者是gpuPrivateUsage

  • ZRAM

可以看到ZRAM的配置大小,使用大小,占用的实际内存大小,可以看到ZRAM的大概压缩率为25%,即可剩下75%的内存

4. dumpsys meminfo

分析单个进程内存占用情况的工具,

c@M:~$ adb shell dumpsys meminfo 10643
Applications Memory Usage (in Kilobytes):
Uptime: 62386647 Realtime: 62386647
                   Pss  Private  Private  SwapPss      Rss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty    Total     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------   ------
  Native Heap   189744   189584      160   231032   189744        0        0        0
  Dalvik Heap        0        0        0        0        0        0        0        0
        Stack     6960     6700      260     1044     6960                           
    Other dev       19        0        4        0      480                           
     .so mmap    83064    18904    60296     2064    94164                           
   Other mmap       73       48       24        0      512                           
      Unknown    18916    18884       32     7304    18916                           
        TOTAL   540220   234120    60776   241444   310776        0        0        0
 
 App Summary
                       Pss(KB)                        Rss(KB)
                        ------                         ------
           Java Heap:        0                              0
         Native Heap:   189584                         189744
                Code:    79200                          94164
               Stack:     6700                           6960
            Graphics:        0                              0
       Private Other:    19412
              System:   245324
             Unknown:                                   19908
 
           TOTAL PSS:   540220            TOTAL RSS:   310776       TOTAL SWAP PSS:   241444 

主要看App Summary PSS,该进程属于Native进程,所以Java heap为0,Native heap主要是一些堆内存,System则是ToTal PSS -Total Private Clean - Total Private Dirty,其中Total PSS包含了Total SWAP PSS;Privte即为该进程独占的内存

值得注意的是,dumpsys meminfo 并不能代表该进程所以得内存占用,对于分析一些非Graphics内存还比较准确,其数据来源是分析/proc//smaps,但是对于Graphics则需要分析其他文件,比如高通平台的GPU以及ION内存就不全包含其中。

5. memcg(Memory Cgroup)

6. dmabuf内存统计

参考文章

meminfo内核文档

原文作者:ora___

原文地址:http://t.csdn.cn/K3I3L(版权归原文作者所有,侵权留言联系删除)

你可能感兴趣的:(linux,内存管理,Linux内核)