8.17 自适应水位算法分析
调整时机:
调用 frameworks/base/services/core/java/com/android/server/am/ProcessList.java:updateOomLevels 函数时会进行调整,物理硬件参数更新时会调整,比如分辨率更新。
【影响范围推断】集中于需要进行分辨率调整的feature,多媒体,游戏相关
水位调整定义:
watermark_scale 水位会依据 extra_free_kbytes 去调整
Script implements watermark_scale calculation which results in the same low watermark as if extra_free_kbytes tunable were to be used.
调整目的:
以前仅仅用 extra_free_kbytes 这一个参数去调整了高低水位,于是存在两个考虑不充分的点:
现在想要通过 extra_free_pages 去计算迭代出来 watermark_scale,从而同时解决以上两个问题
Because Android uses extra_free_kbytes to adjust the low watermark, we ignore the difference in how watermark_scale and extra_free_kbytes affect the high watermark and will match the low watermark only.
调整算法过程如下:
extra_free_pages = extra_free_kbytes / page_size
extra_share = extra_free_pages * managed_pages / vm_total_pages
low = min + max(min / 4, managed_pages * (watermark_scale / 10000)) + extra_share
high = min + 2 * max(min / 4, managed_pages * (watermark_scale / 10000)) + extra_share
下面是根据 max 函数去迭代 watermark_scale_new,
max(min/4,managed_pages⋅(watermark_scale/10000))+extra_share=max(min/4,managed_pages⋅ (watermark_scale_new/10000))−−−−−−①max(min / 4, managed\_pages \cdot (watermark\_scale / 10000)) + extra\_share = max(min / 4, managed\_pages \cdot (watermark\_scale\_new / 10000)) ------ ① max(min/4,managed_pages⋅(watermark_scale/10000))+extra_share=max(min/4,managed_pages⋅ (watermark_scale_new/10000))−−−−−−①
根据 max 函数要么大,要么小,很容易分成两种计算方式
A. managed_pages * (watermark_scale / 10000) > min / 4
则 ① 易得:
managed_pages * (watermark_scale / 10000) + extra_share = managed_pages * (watermark_scale_new / 10000)
=> watermark_scale_new = watermark_scale + extra_free_pages / vm_total_pages * 10000
B. managed_pages * (watermark_scale / 10000) < min / 4
则 ① 易得:
min / 4 + extra_share = max(min / 4, managed_pages * (watermark_scale_new / 10000))
等式右边只能取 managed_pages * (watermark_scale_new / 10000)
=> watermark_scale_new = (min / 4 + extra_share) / managed_pages * 10000
=> watermark_scale_new = (min / 4) * 10000 / managed_pages + extra_free_pages / vm_total_pages * 10000
不妨设:watermark_delta = extra_free_pages / vm_total_pages * 10000
则不难得出以下迭代逻辑:
if (managed_pages * (watermark_scale / 10000) > min / 4)
watermark_scale_new = watermark_scale + watermark_delta
else
watermark_scale_new = (min / 4) * 10000 / managed_pages + watermark_delta
适配要求
以目前现有的 kernel 版本讨论
kernel 4.19、kernel 5.10、kernel 5.4 存在 extra_free_kbytes节点,kernel 5.15 删除此节点
- 存在extra_free_kbytes节点时,在源码 sysctl.c 中会存在此 node 的相关信息,
- 并且 adb shell 在 /proc/system/vm/extra_free_kbytes 也可以查询到节点信息
/bsp/kernel/kernel5.4/kernel/sysctl.c
extern int extra_free_kbytes;
.procname = "extra_free_kbytes",
.data = &extra_free_kbytes,
.maxlen = sizeof(extra_free_kbytes)
1 从 andriod T 开始 init.rc 中通过属性 sys.sysctl.extra_free_kbytes 触发此开机脚本,而 android S 上此部分是直接写入 /proc/sys/vm/extra_free_kbytes 节点
android T --->
/system/core/rootdir/init.rc
on property:sys.sysctl.extra_free_kbytes=*
exec -- /system/bin/extra_free_kbytes.sh ${sys.sysctl.extra_free_kbytes}
android S --->
on property:sys.sysctl.extra_free_kbytes=*
write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}
extra_free_kbytes 和 watermarker_scale 简述
extra_free_kbytes 从framework层中会不断调用 updateOomLevels 来更新值,在这个文件中
frameworks/base/services/core/java/com/android/server/am/ProcessList.java:updateOomLevels
基于屏幕尺寸以及机器的CPU位數,更新水位线的
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
...
int reserve = displayWidth * displayHeight * 4 * 3 / 1024;
int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust);
int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute);
...
SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
}
从下面这张图可以看出:
- 如果空闲页数目min值,则该zone非常缺页,页面回收压力很大,应用程序写内存操作就会被阻塞,直接在应用程序的进程上下文中进行回收,即direct reclaim。
- 如果空闲页数目小于low值,kswapd线程将被唤醒,并开始释放回收页面。
- 如果空闲页面的值大于high值,则该zone的状态很完美, kswapd线程将重新休眠。
在内存分配时,只有"low"与"min"之间之间的这段区域才是kswapd的活动空间,低于了"min"会触发direct reclaim,高于了"low"又不会唤醒kswapd,而Linux中默认的"low"与"min"之间的差值确实显得小了点。
「extra_free_kbytes」:
源于此,安卓在linux水位的基础上增加了extra_free_kbytes的变量,这个extra时额外加在low和min之间的,它在min不变的情况下,让low值有所增大。想要知道extra_free_kbytes的引入是否取得效果,可以通过/proc/vmstat中的pageoutrun和allocstall来看,两者分别代表了kswapd和direct reclaim启动的次数。
「watermark_scale_factor」:
内核总是在进步的,在linux内核4.6版本中,又诞生了一种新的调节水位的方式,即watermark_scale_factor系数,其默认值是10,对应内存占比10/10000=0.1%,可通过/proc/sys/vm/watermark_scale_factor设置,最大值是1000。举个例子:当其被设为1000时,意味着min与low之间的差值,low与high之间的差值都将是内存大小的10%(1000/10000)。前面讲的extra_free_kbytes的方式只增大了min和low之间的差值,而watermark_scale_factor则同时增大了min和low,low和high之间的差值。
补充 extra_free_kbytes.sh 调整逻辑
略