linux内核内存使用分析

1. 内核预留内存

内核启动阶段,从设备树解析了ddr的起始地址和大小加入到memblock进行管理,这期间memblock需要将例如内核代码等重要部分进行预留,随后剩余可用内存交由buddy统一管理。因此我们先来看一下内核启动时可用和不可用都怎么来到哪里去了。

前提配置:uboot下 设置bootargs=memblock=debug
在这里插入图片描述
开启memblock的调试打印信息,方便我们跟踪memblock
在这里插入图片描述
内核启动后可以看到一连串的memblock打印:
在这里插入图片描述
首先是arm64_memblock_init预留的两部分:
0x22000000-0x2393ffff:内核代码(25856K)
0x31000000-0x31000fff:设备树预留中标志reserved-memory的部分
在这里插入图片描述
在这里插入图片描述
随后创建页表,也是通过memblock预留的方式,从内存底部开始预留
在这里插入图片描述
一直映射到
在这里插入图片描述
在这里插入图片描述
即0x38f4a000-0x38ffffff:B5FFF=728K页表映射
随后是为设备树解析预留的空间,都是小块不连续的内存申请
即0x38f2cba0-0x38f49fff:设备树解析预留
在这里插入图片描述
然后是为稀疏内存模型预留的内存,其中关键的几个关键数据定义已经打印出来了见下图

  • MAX_PHYSMEM_BITS:48 -> 内核要管理的内存范围(1<<48 = 256T)这么大(因此需要引入内存段的概念,正常情况64位的内存段大小是128MB)

  • SECTION_SIZE_BITS:24 -> 内存段大小(16MB这里是我这边人为改小的)

  • SECTIONS_SHIFT:24 -> 管理(1<<48这么大的内存需要多少个内存段呢?实际上就是1<<( MAX_PHYSMEM_BITS - SECTIONS_SHIFT) 即SECTIONS_SHIFT=48-24=24)

  • NR_MEM_SECTIONS:16777216 -> 这个值就是要管理的内存段的数量,16M很大了,实际上目前应该也没有256T这么大内存,因此内核使用2级指针,如果真的需要管理对应范围的内存段,在额外申请一个page,该page能管理 4096/16 = 256 个内存段

  • SECTIONS_PER_ROOT:256 -> 上面已经解释过一个page能管理256个内存段结构

  • NR_SECTION_ROOTS:65536 -> 要申请的二位数组的指针数量(指针大小是8字节,但是struct mem_section大小是16字节)

SECTIONS_PER_ROOT       (PAGE_SIZE / sizeof (struct mem_section))
#define NR_SECTION_ROOTS    DIV_ROUND_UP(NR_MEM_SECTIONS, SECTIONS_PER_ROOT)

也就是说最后光内存段的第一级指针需要申请65536*8=512K的内存,然后在加上一个page(4K)管理二级实际上管理的内存段
在这里插入图片描述

在这里插入图片描述
即0x38eaa000- 0x38f2cfff:稀疏内存模型预留(4K对齐了)
以及vmemmap申请了0x36000000-0x38dfffff后又释放了,0x36800000-0x38dfffff,也就是说,vmemmap使用的地址范围为:0x36000000-0x367fffff(8M)[注:尝试去掉vmemmap,也不会减少太多内存消耗,这里应该是内核真正要用的的page,怎么都省不掉的,见下图]

在这里插入图片描述
对于不使用vmemmap的page消耗为:
在这里插入图片描述
在这里插入图片描述

3888c000-38e7ffff共消耗5C0000=5888K = 1472page
在这里插入图片描述
关于SECTION_SIZE_BITS的解释如下图
在这里插入图片描述
内存段在内核中的管理如下图所示
在这里插入图片描述
memory34-56 对应的phys_index:0x22-0x38,也就是对应了实际物理内存(0x2200_0000~0x38ff_ffff),每一段都是16MB
关于稀疏内存模型这里不再具体分析。

如下图,正式进入了zone管理内存,并且memblock打印出了目前为止全部的预留内存大小,这里包括了CMA的预留(16M)
在这里插入图片描述
然后是PCP预留,每个CPU预留了29个page,共4个CPU
在这里插入图片描述
最后使用了0x38e34000~0x38ea7fff:PCP预留(424K)
在这里插入图片描述
然后还有两个hash表占用的内存大小,Dcache+Icache:0x38d73600(0x38d73000-4K对齐)~0x38e34000,共消耗770 K (772K)
在这里插入图片描述
统计一下上面全部大块内存消耗,36614K,基本没差。这里reserved不计算cma预留是因为cma的内存最后会被伙伴系统接管
在这里插入图片描述
最后还有内核的init段,最后也会被释放掉归还给伙伴系统
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

因此我们可以得出最后内核真正预留的内存大小为:36648-6592=30056K。376832-30056=346776K(最后伙伴系统接管的,内核可以完全使用的内存)
在这里插入图片描述
我们也可以通过iomem,回过头来继续看内核预留内存情况
在这里插入图片描述
在这里插入图片描述

2. 内核内存使用

/proc/meminfo是了解Linux系统内存状态的主要接口,里面统计了当前系统各类内存的使用状况,需要注意的是:这是从内核的角度来统计。
源码如下图,在fs/proc/meminfo

在这里插入图片描述
实际效果如图:
在这里插入图片描述
MemTotal:表示伙伴系统管理的总物理内存大小

MemFree:表示系统尚未使用的内存大小。【MemTotal-MemFree】就是已被用掉的内存

MemAvailable:表示系统可用内存大小,该参数跟MemFree是有区别的。MemFree表示的是系统尚未使用的内存,但不代表全部可用的内存。系统中有些内存虽然已经被使用,但是可以回收的,这些内存也算是可用的内存,比如cache、buffer、slab都有一部分内存可以回收,将这部分可回收的内存+MemFree才是MemAvailable。内核采用一种可用内存估算算法来得到MemAvailable。由于是个估计值,因此并不精确,不过考虑到系统内存本身就是一个动态调整的过程,实际上也不需要特别精确。下面这个是估算的函数si_mem_available
在这里插入图片描述

Buffers:表示块设备所占用的内存大小,包括:直接读写块设备、以及文件系统元数据(metadata),比如SuperBlock所使用的缓存页(page cache)。因为Buffers所占的内存属于page cache的一种,所以也在LRU list中,被统计在Active(file)或Inactive(file)
在这里插入图片描述

Cached:表示系统用户空间所有普通文件页(file page,也被成为file-backed page)的大小,包括用户进程正在使用(Mapped)和未在使用(ummaped)的文件页

在这里插入图片描述

SwapCached:表示内存与交换区设备的”中间层“的内存,用于匿名页换入换出时的缓存,我手里的设备没开swap,不用管

Active:表示Active list中所有page占用的内存大小(包括active(file)和active(anon))。Active list里面包含的都是最近被访问过的内存页。(注: file page是进程的代码、映射的文件对应的page,而这些代码,映射的文件直接就保存在磁盘,被加载到内存,所以可回收,需要的话直接从磁盘重新读取即可。anon page是应用程序动态分配的heap,stack对应的page,由于可能再次被访问,当然就不能直接回收,直接释放)( 这部分跟Buffers、Cached、Shmem、AnonPages、Mapped等都有重叠)
在这里插入图片描述
Inactive:表示Inactive list中所有page占用的内存大小(包括Inactive(file)和Inactive(anon))( 这部分跟Buffers、Cached、Shmem、AnonPages、Mapped等都有重叠)
在这里插入图片描述
Unevictable:不可驱逐LRU页列表,详见(https://www.cnblogs.com/pengdonglin137/p/17876210.html)

Mlocked:表示被系统调用函数mlock()锁住的物理内存大小。被锁定的内存因为不能page out/swap out,会从Active/Inactive list移到Unevictable list上。注意:Mlocked并不是独立的内存空间,它与以下统计项重叠:Unevictable,AnonPages,Shmem,Mapped,Cached等。在msm-kernel/mm/mlock.c中有相应的mlock系列函数

Dirty:表示等待被写回磁盘的脏页占用的内存大小,但并没有包含系统中全部dirty page。

Writeback:表示正在被写回磁盘的脏页占用的内存大小,这些页也是脏页。从这两个参数也可以看出,系统中的总的dirty page应该包括:Dirty,NFS_Unstable和Writeback。Dirty表示等待被写回磁盘的page,NFS_Unstable表示发给NFS server但尚未写入硬盘的page,Writeback表示正在写回硬盘的page。注意:(1)NFS_Unstable的内存被包含在Slab中,因为nfs request内存是调用kmem_cache_zalloc()申请的。(2)anon page不属于dirty page。根据mm/vmscan.c: page_check_dirty_writeback()里面的条件判断,可知anon page不属于Dirty和Writeback,所以anon page不是脏页。

AnonPages:前面说过用户进程所占内存页可以分为文件页和匿名页,这里表示用户进程正在使用的所有匿名页占用内存的大小,由此也可知,其跟Cached是不相交的。注意:(1)shared memory 不属于 Anonpages,而是属于Cached,因为shared memory基于tmpfs文件系统实现的。(2)mmap private anon page属于AnonPages,而mmap shared anonymous pages属于Cached(file-backed pages),因为shared anonymous mmap也是基于tmpfs的。
在这里插入图片描述

Mapped:表示用户进程正在使用的普通文件页占用的内存大小,比如:进程对应的so、shared libraries、可执行程序的文件和mmap的文件等,一旦进程内存被回收,就不会算到Mapped。Mapped算是Cached的子集。因为Linux系统上shared memory & tmpfs被计入Cached,所以被attached的shared memory、以及tmpfs上被map的文件都算做Mapped。结合AnonPages,系统所有进程的PSS内存之和理论上应该等于AnonPages + Mapped。(什么是PSS,详见:https://blog.csdn.net/m0_51504545/article/details/119685325)

在这里插入图片描述
Shmem:表示shared memory(共享内存)、tmpfs文件系统和devtmpfs文件系统占用的内存大小(统计的是已经分配的大小而不是进程申请的大小)。文件系统中的文件,由于不是直接与硬盘映射,所以对其进行换入换出时需要swap分区,这行为和匿名页很类似,故这些页也被放入到Active(anon)/Inactive(anon)中。注意:虽然要用到swap,但是依然是文件页,它们不会被记录到AnonPages。因此,Shmem与Mapped、Cached统计有重叠,与AnonPages没有重叠。同时devtmpfs是/dev文件系统的类型,/dev/下所有的文件占用的空间也属于Shmem(ramfs下都东西都算在Shmem中)。

Kreclaimable:表示部分内核态可被回收的内存大小,包括slab和非slab可回收内核page。在计算MemAvailable时使用了这部分内存的一部分。

Slab:表示用于slab的所有内存大小,包括slab可回收部分和不可回收部分。
Sreclaimable:表示slab中可回收部分的内存大小。
Sunreclaim:表示slab中不可回收部分的内存大小。

KernelStack:表示内核栈内存大小,包括内核态进程自身使用的栈空间和线程的内核栈空间。内核栈是内存是常驻的,这部分内存不计算到LRU中,也不包括进程的PSS/RSS内存中。每个用户线程都会分配一个kernel stack(内核栈),内核栈虽然属于线程,但是用户态的代码无法直接访问,只能通过syscall,trap或者exception进入内核态才能使用,相当于内核栈是给kernel层代码使用的,所以我们一般认为这部分是kernel消耗的内存

PageTables:表示page table(PTE)占用的内存大小。PTE用于将内存的虚拟地址翻译成物理地址,随着内存地址分配的越多,PTE会增大。注意:Page Table(页表)与Page Frame(页帧)是不一样的。物理内存的最小单位是page frame,每个物理页对应一个描述符(struct page),而且在内核的启动引导阶段就会配好,保存在mam_map数组中,这个数组占用的内存最后是会被统计在dmesg显示的reserved中,不包含在MemTotal中。在NUMA系统上,可能会有多个mem_map数组,在node_data中或mem_section中。而对于Page Table,内存大小是会动态变化的,要从MemTotal中消耗内存。

NFS_Unstable:表示已经写入NFS server但尚未写入硬盘的page cache占用的内存大小,由于NFS request的内存是调用kmem_cache_zalloc()申请的,因此,NFS_Unstable的内存被包含在Slab中。默认是0,因为正常是没有NFS server的。

Bounce:出于保持兼容性,适配老设备的内存,考虑到部分老设备只能访问低端内存,比如16M以下的内存。如果用户进程发出一个I/O请求,但是目的地址是16M以上的,此时内核会在低端内存中分配一个临时buffer作为跳转,将数据拷贝到此处,多个请求就会有多个临时buffer。这种额外的数据拷贝被称为“bounce buffering”,显然这个操作会降低I/O 性能。因此,Bounce表示在低端内存中针对高端内存访问用于跳转的临时buffer占用的内存大小。不过当前的设备,一般默认是0,不用过多考量这个。

WritebackTmp:表示FUSE用于temporary writeback buffers而占用的内存大小。FUSE是Filesystem in Userspace的缩写,是一种用户空间文件框架,其文件系统的核心逻辑是在用户空间实现的,由内核模块(fuse.ko),用户空间库(libfuse.*)和挂载实用程序(fusermount)组成。由于其主要实现代码位于用户空间中,因而不需要重新编译内核,相对便利,但同样存在访问路径长,需要两个态之间频繁切换;IO吞吐量较低;增加了数据的copy。

CommitLimit:表示系统允许所有用户进程申请的最大内存大小
在这里插入图片描述
VmallocTotal:表示内核空间中vmalloc 虚拟地址空间的大小,值为(VMALLOC_END - VMALLOC_START)。当前64位内核系统,这里有260多G。

VmallocUsed:表示内核通过vmalloc申请的虚拟内存大小,注意:仅仅涉及申请或者映射,并未涉及到物理内存的具体分配,因此该数值不代表实际物理内存消耗。

Percpu:表示用于percpu分配的的内存大小

HardwareCorrupted:当系统检测到内存的硬件故障时,会把有问题的页面删除掉,不再使用,HardwareCorrupted表示kernel标记为已经损坏的且被删除掉的内存页的总大小

内存黑洞:如前面内存地图所描绘的,里面还有一个内存黑洞,这个内存黑洞在/proc/meminfo里面是看不到。一般kernel的动态内存分配涉及的接口大概就如下三种:以页为分配单位的alloc_page/_get_free_page,以字节为单位分配连续虚拟地址内存块的vmalloc和以字节为单位分配连续物理地址内存块的slab allocator(kmalloc)。对于slab,我们可以用/proc/meminfo里面的slab/SReclaimable/SUnreclaim看到。对于vmalloc,我们可以用/proc/meminfo里面的VmallocUsed或者/proc/vmallocinfo看到。但是对于alloc_page,一般是不会主动去统计的,因此,一旦是某些进程以alloc_page的方式分配内存,在/proc/meminfo只看到MemFree减少,但是具体去向不知道。

在这里插入图片描述
在这里插入图片描述

3. 内存统计

  1. 内核预留占用:Memory: 323800K/376832K available (12478K kernel code, 1804K rwdata, 4220K rodata, 6592K init, 625K bss, 36648K reserved, 16384K cma-reserved) 36648-6592=30056K
  2. 内核内存使用:echo 3 > /proc/sys/vm/drop_caches ; cat proc/meminfo ;grep vmalloc /proc/vmallocinfo | awk ‘{total+=$2}; END {print total}’

在这里插入图片描述

4.参考文档

  1. https://zhuanlan.zhihu.com/p/551368056
  2. https://blog.csdn.net/weixin_45337360/article/details/125544275
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜暝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值