博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
android内存碎片问题优化梳理
阅读量:2351 次
发布时间:2019-05-10

本文共 7948 字,大约阅读时间需要 26 分钟。

内存碎片对相机性能的影响

这里说的碎片是物理内存碎片,而且是外部碎片问题。先说下为什么要关注内存碎片,因为手机系统的内存碎片严重会对相机性能带来了如下不好的影响:

1: 首先是相机的内存分配性能会受影响,会变得耗时很多。

具体体现在相机发出大块连续物理内存(order>0)分配需求时,会受阻,会长时间陷入到direct reclaim中。

相机场景下,ion和gpu内存分配, 创建子线程分配内核栈操作等都有这种连续物理内存分配需求。

2: kswapd和kcompactd会异常活跃,会时常跑到cpu大核上跟前台相机进程抢占cpu资源,干扰相机的正常运行。

之前wiki:手机kswapd活跃原因调查里面调研过大部分相机场景下,kswapd频繁活跃原因是:内存碎片化

为什么内存碎片会带来性能问题

内存碎片从表面上看是正常的

随着系统的运行,页面被分配给各种任务,随着时间的推移内存会逐步碎片化,最终正常运行时间较长的繁忙系统可能只有很少的物理页面是连续的。

由于 Linux 内核支持虚拟内存管理,物理内存碎片通常不是问题,因为在页表的帮助下,物理上分散的内存在虚拟地址空间仍然是连续的 (除非使用大页)。

结论:

有了mmu和虚拟内存的存在,进程用户态的内存分配和管理不太需要关注物理内存碎片,因为我用户态cpu寻址的始终是一片平坦大片连续的虚拟内存。

你有物理内存碎片,我顶多实际访问那片虚拟内存时,一个缺页异常下去只需要分配出一个物理page,然后众多的离散物理page拼接在一起就可以了。

其实性能问题出在内核态

内核态经常会有连续物理内存分配需求, 所以物理内存碎片严重时,这种分配需求就会受阻,就会出性能问题。

主要的分配需求来自于内核线性映射区。

这个区域常见的分配需求有常见的内核kmalloc, slab分配,新创建进程线程的内核栈分配,还有其他的模块,比如文件系统里面不想用vmalloc的那些分配等,这些都会大量发出高阶(order>0)物理内存分配需求。

一旦满足不了需求,内核栈分配耗时会导致相机创建子线程耗时,kmalloc和文件系统里面内存分配耗时会导致相机写文件存图耗时。

为什么要搞线性映射区,为什么这些分配不能像用户态那样搞成离散物理page拼接分配,还非得要搞成连续物理内存分配

首先arm64平台上,线性映射区不仅搞成了,而且空间很大,从从ffff000000000000到ffff7fffffffffff,最大支持128TB的物理地址空间,这样android上肯定可以映射整个物理内存空间了,还不像在32位系统上(物理内存较少的系统除外),只有一部分物理内存可以映射。

线性映射区里面的order>0的物理内存分配,肯定得是连续的。

搞线性映射区和连续物理内存分配的目的:

内核态内存分配性能是系统最看重的

内核态内存页表是系统所有进程共享的,内核态的内存分配属于最基础层面的工作,它的性能是系统最关注的,比用户态内存分配还重要。用户态内存还可以搞成延迟分配,并且内存不用时,可以被交换到zram中,内核态内存不行。

所以为了提升内核态内存分配性能,android/linux系统搞了个线性映射区,有如下好处:

1: 系统刚开机时,内核在初始化时,已经建立好了线性映射区的虚拟内存和物理内存映射关系,当然了,此时物理内存并未真正分配,但是映射关系建好了。

然后在往后的该区域内的物理内存分配时,就不需要再像vmalloc那样,去费劲建立映射关系了(更改页表有时候也比较缓慢),只需要专注于物理内存分配就行。搞到实际物理内存后,想让cpu使用,直接用page_address -> page_to_virt宏就能转换成虚拟地址了。

2: 如果系统分配连续的物理内存,则对应的线性地址一般也是连续的,这样在多数情况下,连续的线性地址可以通过相同的页目录项、页表来转换成物理地址,这就提高了访问TLB的命中率,同时,连续的物理地址也可以提高系统cache的命中率。

所以内核里面线性映射区要承担很多内核内存分配工作。

然后还有相当一部分需求来自于相机场景下大量ion和gpu内存分配

ion和gpu由于dma实时传输大量数据的性能需求,也有分配连续物理内存的需求,即使现在器件进步有iommu了。

详细见:kernel/msm-4.19/drivers/staging/android/ion/ion_heap.c

里面有order=9和=4的分配需求的,上面下发ion内存分配请求时,首先尝试分配order=9或者4的大块连续物理内存,分配不出来,才想到order=0的内存分配。

不过这部分连续物理内存搞到手后,有意思的是还会让用户空间进程去访问到,做下remap_pfn_range或者pagefualt映射就可以了。

这部分物理内存分配没有来自于内核线性映射区,但也是属于内核态的内存分配。

产生性能问题的原因

假设系统里面都是用户空间分配的内存,假设我要将某个直接页表项中对应的物理页面换走,只需要分配一个新页面,将旧页面的数据拷贝到新页面,然后修改此直接直接页表项的值为新的页帧号即可,而不会改变原来的虚拟地址,

这样的页面是moveable的,可以随便迁移的。所以如果内核有连续物理内存分配需求,即使碎片化严重,可以使用内核的内存compact功能,把众多已分配页面聚合到一起,那么剩下的依然还会有连续free物理内存空间可供分配。

但是问题出在:

对于线性映射区,虚拟地址 = 物理地址 + 常量,我们若修改物理地址,必然会导致虚拟地址也发生变化,所有继续访问原虚拟地址的行为就出 bug 了,这样的页面是unmoveable的,显然不宜迁移。

当然还有上面的Ion,gpu驱动内存分配的也是unmoveable页面。

所以当通过页表访问的物理页面和通过线性映射的页面混合在一起管理时,就很容易出现内存碎片。由于unmoveable页面不能迁移,内核内存compact对它没有用,

所以对于长时间运行的系统,unmoveable页面离散并且多的时候,就出现严重的内存碎片,导致内核的连续物理内存分配请求受阻。

原生andorid的内存碎片优化方案介绍

一 页面迁移类型优化

前面说了内存碎片是外部碎片问题,这个外部碎片其实就是内核buddy系统在分配物理内存时产生的,内核buddy系统也有一定的局限性。

buddy局限性

buddy的碎片防止机制寄托于内存使用者会及时释放掉内存的情况,如果使用者长期不释放内存,或者说在使用者还没有释放内存的这一段时间期间,碎片将是存在的,并且可能还会导致很大的问题。

比如在物理内存中间分配了一页面,然而仅因为分配的这一个页面不可移动,在它被释放之前,系统可用的最大的连续物理内存就只有不到一半物理内存总大小了。

究其根源,这种问题的根源在于buddy系统仅仅释放页面时的合并操作防止了碎片的产生,不管页面从哪里被分配,只要它能有效被释放,碎片就是可以避免的,也就是说,buddy系统对于分配并没有更多的约束,仅仅满足在10个free_area中从小到大的顺序扫描即可。

引入页面迁移类型对其的改善

既然找到了buddy的问题,那么只要对分配动作采取一定的约束,碎片就可以进一步避免了。

最简单而又不引入过多复杂性的办法就是将页面按照“可移动”属性分类,将不可移动的页面分为一类,将可以移动的页面分为一类,它们各自占据一块足够大的连续物理空间,不可移动的页面分配需求则尽量在它自己的页面类中分配,可移动的页面也一样。

这样一来,不可移动的页面的不可移动性仅仅影响它自身的类别而不会导致一个不可移动的页面两边都是可移动的页面。这就是MIGRATE_TYPE被引入的目的。MIGRATE_TYPE限制了内存页面的分配地点从而避免碎片,而不再仅仅寄希望于它们被释放时通过合并避免碎片。

具体实施时需要考虑的因素:

1:可以说MIGRATE_TYPE仅仅是一种防止碎片的策略,不应该因为它的存在而影响到内存分配的结果,也就是说,如果在一个MIGRATE_TYPE链表中没有内存可以分配了,那么也还是可以从别的链表中“暂时抢”一些的(对应内核的__rmqueue_fallback函数)。抢的时候,不是抢刚好够用的,而是一次尽量抢一个page block(伙伴系统所能容纳的最大连续物理内存), 只有一次性分配一大块内存,才不至于引入碎片。

2:另外,还有一个问题,内核载初始化的时候如何为“不可移动类”或者“可移动类”页面指定初始大小呢?也就是说,一开始,系统的free_area中的这些类别链表的页面各该是多少个呢?

 事实上,内核从来没有指定过初始大小,而是一开始将所有页面都归到“可移动”组当中,而别的组全部都是空的,等到真的有不可移动页面需求的时候再从可移动组中拨一批给不可移动组链表,想一下这也是合理的,毕竟只是一些“不可移动”的页面造成了内存的长期碎片化,如果没有这些长期使用的不可移动页面,碎片的问题是不大的。

二 Migrate_highatomic的推出

Migratetype具体使用时的一点坑

内存碎片化严重或者有碎页问题时,会发现系统中内存页面有很多都是unmoveable页面。

而应用层都是申请的一个一个的moveable页面,发现申请不到,也不好去steal,所以系统中很多都是Unmoveable页面。

Migrate_highatomic对于抗碎页的好处

这个Migrate_highatomic是新增加的一个页面迁移类型,好处是如果分配Migrate_highatomic类型的页面时,分配不到时,不会去steal。分配到时,会调用reserve_highatomic_pageblock直接去搞出来一个大的pageblock页块(有点像ion page pool),就是本次多分配些,避免下次再分配再产生内存碎片。

当系统内存紧张时,之前reserved的还会被释放的。然后其他Migratetype的不会到这个类型页面去steal,避免相互steal的影响。

目前内核中限制只有gfp_flags为__GFP_ATOMIC类型的分配,才会有可能走Migrate_highatomic类型的页面分配路径,才会发挥出Migrate_highatomic抗碎页的好处。

当前在中断、软中断、spinlock等原子上下文里面,申请内存,都会使用GFP_ATOMIC标记,意思是此上下文不能睡眠,譬如内核中有大量的kmalloc/GFP_ATOMIC的例子。

另外一些紧急在内存紧急的路径上(比如不想睡眠,或者是对于前台app的一些内存分配,要求低延迟,对性能敏感),哪怕是进程上下文,我们也建议可以考虑使用GFP_ATOMIC。

三 高通ion page pool优化

其实思路跟上面的Migrate_highatomic有点类似,前面讲了ion驱动里面申请的内存页面都是unmoveable的,更容易造成碎片问题。那好办,直接再加个缓存池。

系统内存充足或者压力小时,就相机每次分配完ion内存后,就会触发下该缓存池的蓄水工作,会在系统里面再搞出很多连续free页面(这个时候搞着也容易),加到该池子里面。那么下次再分配ion内存时,直接从池子里面取,

这样就带来一个很大的好处:

每次ion内存分配都会聚合在一个地方(page pool)进行分配, unmoveable页面都会聚合在一起,不会再发散地分布在系统内存各个地方。

然后等到系统的内存分配压力大时,这个page pool里面的缓存随时都可以被释放出来,满足系统其他地方的内存分配请求。

这个是一个比较好的内存抗碎片的优化方案,详见代码:

kernel/msm-4.19/drivers/staging/android/ion/ion_page_pool.c

四 boost watermark方案优化

[PATCH 3/5] mm: Reclaim small amounts of memory when an external fragmentation event occurs

(该patch在8250r机型内核代码中有)

watermark_boost_factor的推出,是为了降低external fragmentation event次数,这个次数等于内核mm_page_alloc_extfrag这个trace event事件的发生次数,

这个次数降低了,内核内存碎片问题也会减轻不少的,下面是推出该patch的来龙去脉。

kernel在降低外部碎片率的弊端

The kernel reduces the probability of such events by increasing the watermark sizes by calling set_recommended_min_free_kbytes early in the lifetime of the system.

This works reasonably well in general but if there is enough sparsely populated pageblocks then the problem can still occur as enough memory is free overall and kswapd stays asleep.

watermark_boost_factor的提出

This patch introduces a watermark_boost_factor sysctl that allows a zone watermark to be temporarily boosted when an external fragmentation causing events occurs.

The boosting will stall allocations below the boosted low watermark and kswapd is woken unconditionally to reclaim an amount of memory relative to the size of the high watermark and the watermark_boost_factor until the boost is cleared。

思路很简单,就是常用的降低内存碎片的思路:

限制内核page cache使用,类似于提高vm.min_free_kbytes,虽然调大 vm.min_free_kbytes 确实会导致一些内存浪费,另外page cache减少,系统的io读性能也会下降。

但性能优化就是这种针对不同的业务问题场景做折衷的,有些时候,比如旗舰手机总内存本身就大,内存充足,这个时候重点关注的是碎片化问题,那么就上boost watermark方案优化碎片。

watermark_boost_factor的优化

kswapd avoids any writeback or swap from reclaim context during this operation to avoid excessive system disruption in the name of fragmentation avoidance.

这个地方其实是做了快速回收机制,限制boost watermark里面只回收干净文件页,其他的脏页,还有匿名页就不回收了(因为回收这两个很耗时),这样内存回收快了,由boost watermark而触发的kswapd耗时会减少不少。

五 其他的抗碎片优化

1:最直接的内核态分配连续内存时,可以用类似vmalloc代替kmalloc, 避免连续物理内存分配需求。

2:现在设备也进步了,相机里面有了smmu,dma数据传输时,不再有硬性非得分配大块连续物理内存的需求了,也可以一页一页地拼接了,但是这样做每次分配都得map/umap,

而且还得一页一页从buddy里面去分配也很耗时的,尤其对于相机上层下发的>=2M dma数据传输需求,能一次搞到一大块连续物理内存最好了。

3:zone_moveable

这个是个虚拟zone,它的提出开始是为了抗碎页,把安卓那些highmem和moveable分配需求集中放到这个zone里面,从而使得这个zone里面都是可移动页面,方便compact。

但是后来发现它有更好的用途 memhotplug,而原先的用途抗碎页被更好的机制页面迁移类型给取代了(况且用这个zone_moveable来抗碎页也不好)。

4:cma内存分配。

这个cma和上面的zone_movealbe在抗碎页方面其实都有坑,

驱动需要cma时,分配不出连续物理内存页面时,就要做很重的系统回收操作,影响性能,而且最后还有可能分配不出去,这个其实是跟内核内存回收状态机做对抗的。

5:内核zram模块,还有其他地方的unmoveable页面变成moveable页面。

其他内存碎片优化方案

1 禁止分配大块unmoveable的连续物理内存

暂时保密。

2 周期性的内存compact触发

暂时保密

3 结合网上搜的,我自己想的抗内存碎片优化方案

设计背景:

主要解决安卓系统长时间运行碎片化严重时,线程创建耗时问题,具体是因为线程在为内核栈分配连续物理内存时受阻,这个估计app冷启动性能也会受影响,因为启动时要创建大量线程出来。

具体代码调研:

现在安卓系统,至少android新版本内核代码,我看到CONFIG_VMAP_STACK配置项并未被开启,而且内核栈占内存大小肯定是order>0的,这样:

kernel/msm-4.19/kernel/fork.c

SYSCALL_DEFINE0(vfork)和kernel_thread(Create a kernel thread.) 都会调用_do_fork --> copy_process --> dup_task_struct --> alloc_thread_stack_node --> alloc_pages_node里面分配连续物理内存就会受阻。

设计思路:

参看下面的朱辉优化patch, 把一些前台关键app进程/线程的内核栈内存页连续分配放到这个highatomic里面了,这样至少在系统内存碎片化严重场合,在相机app冷启动方面,肯定会有性能收益的。

Use HighAtomic against long-term fragmentation

总结

内核在内存抗碎片方面的优化是历史悠久的,印象中是从2005年就开始了,然后持续到现在,都在源源不断在mm/{page_alloc.c vmscan.c}里面进抗碎片优化patch.

抗碎片是个内核内存分配方面的系统架构性的优化工程,里面优化方案牵涉面广,而且优化背景也比较复杂。

所以涉及到篇幅问题,要想让读者对系统内存碎片化这个问题理解透彻,只能从全局入手,把握整体和要害,大概简单通俗易懂的讲些抗碎片方面的实现原理和轮廓,再进一步详细的还得看代码理解。

当理解了内核在内存抗碎片方面做的努力,就理解了内核内存管理至少40%的代码。

转载地址:http://burvb.baihongyu.com/

你可能感兴趣的文章
4--网关
查看>>
4.内存非连续分配管理方式
查看>>
5.虚拟内存的概念、特征以及虚拟内存的实现
查看>>
mmap()函数:建立内存映射
查看>>
munmap()函数:解除内存映射
查看>>
Java异常处理
查看>>
JQueryUI实现对话框
查看>>
Java流(Stream)/文件(File)/IO
查看>>
文件处理(压缩与解压)
查看>>
Java中的目录
查看>>
JQuery实现对select选择框的赋值
查看>>
SweetAlert插件
查看>>
JSON学习
查看>>
有关项目的基础知识
查看>>
创建Java工程实现发送邮件(163邮箱)
查看>>
java.lang.NoClassDefFoundError: com/sun/mail/util/MailLogger javax/mail/MessagingException
查看>>
JavaScript学习
查看>>
JavaScript学习总结
查看>>
JQuery学习总结笔记1
查看>>
JQuery学习笔记2
查看>>