type
status
date
slug
summary
tags
category
icon
password
URL
一、Zone type
因此Linux内核对不同区域的内存需要采用不同的管理方式和映射方式
为了解决这些制约条件,Linux使用了三种区:
- ZONE_DMA : 这个区包含的页用来执行DMA操作
- ZONE_NOMAL : 这个区包含的都是能正常映射的页
- ZONE_HIGHEM : 这个区包"高端内存",其中的页能不永久地映射到内核地址空间
而为了兼容一些设备的热插拔支持以及内存碎片化的处理, 内核也引入一些逻辑上的内存区
- ZONE_MOVABLE : 内核定义了一个伪内存域ZONE_MOVABLE, 在防止物理内存碎片的机制memory migration中需要使用该内存域. 供防止物理内存碎片的极致使用
- ZONE_DEVICE : 为支持热插拔设备而分配的Non Volatile Memory非易失性内存
这部分的定义可以在
mmzone.h
中的enum zone_type
中得到二、struct zone结构体
内核将每个簇所对应的node又被分成的称为管理区(zone)的块,它们各自描述在内存中的范围。一个管理区(zone)由
struct zone
结构体来描述下面详细介绍一下这个结构体的知识点:
2.1 watermark
每个zone都有三个水线标准,如下enum定义,分别叫做WMARK_MIN,WMARK_LOW,WMARK_HIGH,借助水线值与zone的free pages对比,可以知道当前zone的内存压力有多大。这三个值在系统启动阶段memory init时会计算得出,首先是通过init_per_zone_wmark_min函数计算出min_free_kbytes,后面根据这个值,计算出这三个水线标准值,这块后面会细讲。这三个水线标准在内存分配和kswapd线程进行内存回收时会用到。

我们可以通过 cat /proc/vmstat,其中的"pageoutrun"和"allocstall_(dma/dma32/normal/movable)",分别查看kswapd和各个zone的direct reclaim启动的次数。
2.1.1 __setup_per_zone_wmarks
现在开始讲是如何设置这三个水线值的。函数的调用关系是:init_per_zone_wmark_min->setup_per_zone_wmarks->__setup_per_zone_wmarks
init_per_zone_wmark_min
calculate_min_free_kbytes
总结:
- lowmem_kbytes: 代表的意思是lowmem中超过高水位的页的总和,这里的单位是kbytes, 这就是lowmem中超过high水位的页乘以4这就是lowmem_kbytes
- new_min_free_kbytes = sqrt(lowmem_kbytes * 16) = √(lowmem_kbytes * 16)
- min_free_kbytes最小不能小于128K,最大不能超过65536K
setup_per_zone_wmarks
一个zone的"low"和"high"的值都是根据它的"min"值算出来的,"low"比"min"的值大1/4左右,"high"比"min"的值大1/2左右,三者的比例关系大致是4:5:6。
下面以一个项目来验证
2.1.2 实例计算水位
首先明确几点:
- WATERMARK_MIN是由min_free_kbytes计算出
- WATERMARK_LOW和WATERMARK_HIGH由WATERMARK_MIN计算得来
- min_free_kbytes是在calculate_min_free_kbytes函数中得到
根据calculate_min_free_kbytes函数,
- 计算min_free_kbytes
lowmem_kbytes = 949265*(PAGE_SIZE>>10) = 3797060 min_free_kbytes = 4 * int_sqrt(lowmem_kbytes) = 7794
计算值与设备查询的值相近

- 计算watermark[WMARK_MIN]
pages_min = min_free_kbytes >> (PAGE_SHIFT - 10)=7794/4=1948 watermark[WMARK_MIN]=pages_min*zone_managed_pages(zone)/lowmem_pages= 1948
由于当前手机只有ZONE_NORMAL,所以zone_managed_pages(zone)=lowmem_pages,
所以watermark[WMARK_MIN] = 1948
- 计算watermark[WMARK_LOW]和计算watermark[WMARK_HIGH]

tmp=max_t(u64, 1948>>2, 949265*60/10000)=5695 watermark[WMARK_LOW]=watermark[WMARK_MIN]+tmp=1948+5695=7643 watermark[WMARK_HIGH]=watermark[WMARK_MIN]+tmp*2=1948+5695*2=13338
由此我们计算出三条水位为:
watermark[WMARK_MIN]=1948
watermark[WMARK_LOW]=7643
watermark[WMARK_HIGH]=13338

这与我们从设备中查询到的也是符合的!
引入
watermark_scale_factor
这个参数原因:避免突然遇到网络流量增大,需要短时间内申请大量的内存来存放网络请求数据,导致 kswapd
回收内存的速度可能赶不上内存分配的速度,从而达到watermark min,造成直接内存回收 direct reclaim
,影响系统性能,解决直接内存回收导致的性能抖动问题2.1.3 watermark_boost
该参数通过细粒度控制内存回收的尺度,使系统可动态提升三个watermark的值,使kswapd线程提前开始回收内存而且也可以回收更多内存。这样一方面可以减少内存规整(memory compact)的次数,另一方面发生内存规整,也可以更快的整合出大的连续物理内存,从而优化内存碎片对内存分配的影响。
对于watermark_boost,在boost_watermark()函数中,会对其设置,可以临时修改该值。跟其直接相关的参数是watermark_boost_factor,位于/proc/sys/vm下面。