在日常的问题排查过程中,Linux内存相关的问题也非常多,OOM、内存泄漏 都是比较头疼的而且非常常见一些问题。如下图,我们都知道Linux 内存将内存做了以下划分(如: Node、Zone、Page),这里我们先简单看一些内存相关的名词解释。
1. Node
什么是Node?我们先看下多路CPU是怎么工作的,如图所示是两个物理CPU,我们现在的服务器通常都会有多路CPU在一个主板上,不同的内存器件和CPU核心从属于不同的Node,每个Node都有自己的集成内存控制器。
而在Node内部,其架构类似于UMA,几个核心共享该Node的内存,并且通过IMC Bus进行不同核心之间的通信;不同Node之间则通过QPI(Quick Path Interconnect——又名CSI,Common System Interface公共系统接口,是一种可以实现芯片间直接互联的架构)进行通信。
Linux内核把物理内存按照CPU节点划分为不同的node, 每个node作为某个cpu结点的本地内存, 而作为其他CPU节点的远程内存, 而UMA结构下, 则任务系统中只存在一个内存node, 这样对于UMA结构来说, 内核把内存当成只有一个内存node节点的伪NUMA。
如何查看主机有多少个Node,执行numactl --hardware,我们可以看到node的情况,当前测试机只有一个node,node0 中包含0-3 总共四个核。
2. Zone
什么是Zone呢?其实划分Zone来管理内存也是有一定历史原因的,由于地址数据线位宽的限制,32位处理器通常最多支持4GB(未开启LPAE特性)。在4GB的地址空间中,通常内核空间只有1GB大小,因此对于大小为4GB的物理内存是无法进行一一线性映射的。Linux内核的做法就是将物理内存分成两部分,其中一部分是线性映射的。对应就是ZONE_NORMAL。剩余部分叫做高阶内存(high memory), 对应ZONE_HIGHMEM。
内存管理区的分布和架构也有关系,x86架构中,ISA设备只能访问物理内存的前16MB,所以在x86架构中会多一个ZONE_DMA的zone。在x86_64中,由于有足够大的内核空间可以线性映射物理内存,则不需要有ZONE_HIGHMEM这个zone。
常见的几个ZONE:
- ZONE_DMA:用于ISA设备的DMA操作,范围是0-16MB,只适用于Intel x86架构。
- ZONE_DMA32:用于地域4GB的内存访问的设备,如只支持32位的DMA设备。
- ZONE_NORMAL:4GB以后的物理内存,用于线性映射物理内存。如果内存小于4GB,则没有这个ZONE。实测低于等于4GB的时候 这个zone是有少量内存的。
- ZONE_HIGHMEM:用于管理高端内存。64位的系统中没有这个zone。
3. Page
虽然内存访问的最小单位是byte或者word,但MMU是以page为单位来查找页表的,page也就成了Linux中内存管理的重要单位。包括换出(swap out)、回收(relcaim)、映射等操作,都是以page为粒度的。
所以在前面提到的zone的概念中,我们可以进一步看到每个zone里面的page情况,执行 cat /proc/pagetypeinfo
这里就记录了每个Zone的page的情况,如红字对应的,从左往右依次是4kB 8kB…的page。
4. 内存分配
在内核中,内存的分配方式也有很多种,这里我们也简单介绍几个常用的方式,对于后面内存泄漏相关的问题的理解有一定帮助。
4.1 vmalloc
vmalloc是一个接口函数, 内核代码使用它来分配在虚拟内存中连续但在物理内存中不一定连续的内存。比如在模块加载时,由于内核模块数据一般比较多,linux系统中往往无法为其提供如此大块的连续内存块,就可以用vmalloc函数为其分配物理地址不连续的内存块。
4.2 slab
前面我们提到 Linux内存以页为单位进行内存管理,但是页的粒度还是太大,Linux下是4KB大小,也就是4096个字节,而kernel本身有很多数据结构时时刻刻都需要分配或者释放,这些数据的大小又往往小于4KB大小,一般只有几个几十个字节这样的大小。所以会造成内存的浪费。SLAB是一种分配器,目的就是用来解决上述问题的。
依托伙伴系统,slab 分配器则提供了小内存的分配能力(虽然也兼容大内存分配)。slab分配器从伙伴系统"批发"大内存,然后把大内存分隔成许多小块内存,一个小块内存块我们称为object, 最后把object "零售"给其他内核组件使用。主要API有kmalloc/kmem_cache_alloc。
4.3 allocpages
alloc_pages是内核中常用的分配物理内存页面的函数, 函数定义在[include/linux/gfp.h], 用于分配2^order 个连续的物理页。
需要注意的是,这部分是直接从伙伴系统申请内存,不像page fault这种申请内存,很多审计计数都是没有的,这也会导致一个问题,我们的/proc/meminfo是统计不到这部分内存的,也就会导致我们常用的free、top这种命令无法统计到这部分内存。
4.4 others
其他的内存就比如PageTable、Reserve、KernelStack。