目录
[显示]

1.摘要

最近小伙伴们在排查一个线上关于linux内存oom的问题,前些天来问我某篇文章里的一句话是什么含义,问题比较难用几句话说明,在这里梳理一下。

2.背景

最近小伙伴们在排查一个线上关于内存oom的问题,前些天来问我某篇文章里的一句话是什么含义:

每次申请的block大小比较有讲究,Linux内核分为LowMemroy和HighMemroy,LowMemory为内存紧张资源,LowMemroy有个阀值,通过free -lm和/proc/sys/vm/lowmem_reserve_ratio来查看当前low大小和阀值low大小。低于阀值时候才会触发oom killer,所以这里block的分配小雨默认的256M,否则如果每次申请512M(大于128M),malloc可能会被底层的brk这个syscall阻塞住,内核触发page cache回写或slab回收。

出自:

http://blog.csdn.net/gugemichael/article/details/24017515

感觉这句话里混淆了好几个概念,并且一些概念跟自己的理解有冲突,在此说一下自己的理解。对内核研究不深,一些概念可能理解有误,因此文章里给出了比较可靠的参考文档,有兴趣可以进一步了解详情。

3.lowmem与highmem

关于lowmem和highmem的定义在这里就不详细展开了,推荐两篇文章:

链接内讲的比较清楚,这里只说结论:

  1. 当系统的物理内存 > 内核的地址空间范围时,才需要引入highmem概念。
  2. x86架构下,linux默认会把进程的虚拟地址空间(4G)按3:1拆分,0~3G user space通过页表映射,3G-4G kernel space线性映射到进程高地址。就是说,x86机器的物理内存超过1G时,需要引入highmem概念。
  3. 内核不能直接访问1G以上的物理内存(因为这部分内存没法映射到内核的地址空间),当内核需要访问1G以上的物理内存时,需要通过临时映射的方式,把高地址的物理内存映射到内核可以访问的地址空间里。
  4. 当lowmem被占满之后,就算剩余的物理内存很大,还是会出现oom的情况。对于linux2.6来说,oom之后会根据score杀掉一个进程(oom的话题这里不展开了)。
  5. x86_64架构下,内核可用的地址空间远大于实际物理内存空间,所以目前没有上面讨论的highmem的问题。

4.linux的物理内存管理

接下来的问题是,linux是如何实现highmem的概念的?

Linux把物理内存划分为三个层次来管理:存储节点(Node)、管理区(Zone)和页面(Page)。

在NUMA架构下,系统根据CPU的物理颗数,将物理内存分成对应的Node,这里也不展开了,可以参考NUMA (Non-Uniform Memory Access): An Overview

每一个Node,系统又将其分为多个Zone,x86架构下,node被分为ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM,而64位x86架构下有ZONE_DMA(ZONE_DMA32)和ZONE_NORMAL。它们之间的是树状的包含关系:

zone

可以通过以下命令查看numa node信息:

可以通过以下命令查看zone信息,注意单位是page(4k):

结合之前关于highmem的说明,对于x86架构的系统来说,物理内存被划分为:

类型 地址范围
ZONE_DMA 前16MB内存
ZONE_NORMAL 16MB – 896MB
ZONE_HIGHMEM 896 MB以上

ZONE_DMA位于低端的内存空间,用于某些旧的ISA设备。ZONE_NORMAL的内存直接映射到Linux内核线性地址空间的高端部分。

对于x86_64架构的系统来说:

类型 地址范围
ZONE_DMA 前16MB内存
ZONE_DMA32 16MB – 4G
ZONE_NORMAL 4G以上

和x86相比,多了ZONE_DMA32,少了ZONE_HIGHMEM.

5.linux如何分配内存

这里也不详细展开了,推荐两篇文章:

结论:

  1. malloc属于glic的库函数,分配的是虚拟地址。
  2. linux的malloc分配时,如果申请内存小于MMAP_THRESHOLD(默认128K),使用brk分配,否则使用mmap分配。
  3. 通过brk分配的地址空间,当堆尾的空闲内存超过M_TRIM_THRESHOLD(默认128K)时,执行内存缩紧操作,这里指的也是虚拟地址。
  4. 读写内存时,触发缺页中断,此时才会分配物理内存。
  5. 分配物理内存时,kernel由high zone到low zone依次查找是否有足够的内存可以分配,找到可用的内存后映射到虚拟地址上。

关于系统分配内存的详细介绍,可以参考:Memory Mapping and DMA

6.lowmem_reserve_ratio

6.1.为什么要调整lowmem_reserve_ratio

在有高端内存的机器上,从低端内存域给应用层进程分配内存是很危险的,因为这些内存可以通过mlock()系统锁定,或者变成不可用的swap空间。

在有大量高端内存的机器上,缺少可以回收的低端内存是致命的。因此如果可以使用高端内存,Linux页面分配器不会使用低端内存。这意味着,内核会保护一定数量的低端内存,避免被用户空间锁定。“lowmem_reserve_ratio”参数可以调整内核对于lower zone的保护力度。

6.2.lowmem_reserve_ratio参数的含义

lowmem_reserve_ratio是一个数组,可以通过以下命令查看:

数组的长度=内存zone数量-1,其中每个数并不是绝对值,而是一个比例,代表1/256或1/32。

再次搬出zoneinfo,这里以zone_dma和zone_dma32举例:

linux尝试在zone中分配page时,会判断当前zone的page_free与高位zone的page_present的关系。

例如在dma中尝试申请dma32的page时,会计算一个protection值:

protection[dma,dma32] = zone_dma32.present/lowmem_reserve_ratio[dma(1)] = 813848/256 = 3179,这个结果对应上面DMA段中,protection数组的第二个元素。

然后需要比较zone_dma.free的值(3933) 与 protectiondma,dma32 + zone_dma.watermarkhigh的大小:如果free>protection+watermark[high],则可以分配page;否则不能分配,内核继续查找下一个lower zone。也就是说只有在higher zone内存不足时才会尝试从lower zone继续申请。

更详细的文档可以参考:Documentation/sysctl/vm.txt

根据公式可以看出:

  • lowmem_reserve_ratio越大,低级的zone中被保护的内存就越小;
  • lowmem_reserve_ratio越小,低级的zone中被保护的内存就越大;
  • 当lowmem_reserve_ratio=1(100%)时代表对low zone的最大保护强度。