手动触发zone gc的一些实验和心得

苹果在IOS11和MACOS10.13后移除了mach_zone_force_gc,因此堆风水后需要手动触发gc。
这里gc构造方法是采用siguza在https://siguza.github.io/v0rtex/提到的方法,在各个ZONE都申请一些内存并释放,构成内存垃圾,触发内核的垃圾回收。
判断gc产生的标准是比较两次生成内存垃圾的时间,如有显著提升就表明gc触发成功。该方法在MACOS上同样可以使用。

1.gc会持续一段时间
内核zone_gc并不会完全阻塞用户态程序执行,但会在这段时间占用CPU,减慢用户态程序执行时间,因此,判断gc触发成功后,一定要延时一会儿再继续堆喷射,否则就会出现边堆喷,边出现新的页,影响堆喷预期。

2.gc时ipc_port的zone靠后释放,且被优先重用
gc会先释放常规zone的页面,再释放ipc_port的页面,因此接下来的堆喷不需要喷很多就能覆盖到ipc_port释放的页面。而如果不顾一切狂喷之前内存垃圾之和的量,虽然也能成功,但途中又会造成多次gc,徒然影响效率,还会有内存耗尽的风险。
zone_gc() starting…
zone_gc() of zone VM map holes freed 768 elements, 6 pages
zone_gc() of zone pagetable anchors freed 2 elements, 2 pages
zone_gc() of zone kalloc.32 freed 1440512 elements, 11254 pages
zone_gc() of zone kalloc.48 freed 1087405 elements, 12793 pages
zone_gc() of zone kalloc.64 freed 818816 elements, 12794 pages
zone_gc() of zone kalloc.80 freed 654330 elements, 12830 pages
zone_gc() of zone kalloc.96 freed 545360 elements, 12832 pages
zone_gc() of zone kalloc.128 freed 409152 elements, 12786 pages
zone_gc() of zone kalloc.160 freed 327471 elements, 12842 pages
zone_gc() of zone kalloc.192 freed 273024 elements, 12798 pages
zone_gc() of zone kalloc.256 freed 204784 elements, 12799 pages
zone_gc() of zone kalloc.288 freed 182044 elements, 12820 pages
zone_gc() of zone kalloc.512 freed 101800 elements, 12725 pages
zone_gc() of zone kalloc.8192 freed 5 elements, 10 pages
zone_gc() of zone ipc ports freed 260172 elements, 10692 pages
zone_gc() of zone namei freed 8 elements, 2 pages
zone_gc() of zone ulocks freed 93 elements, 2 pages
zone_gc() of zone ksyn_waitq_element freed 73 elements, 1 pages
zone_gc() of zone os reasons freed 51 elements, 1 pages

   越后面整理释放的ZONE里的页越先被重用?

3.单个ZONE中先成为内存垃圾的页(未触发gc)先被重新使用
这是因为xnu代码中free_to_zone里调用的re_queue_tail把free页放在all_free链表末尾。
这个问题我最开始得出了错误的结论,实验如下:在自己想要喷射的理想页设上标志魔术字,再多次堆喷,每次都按喷射顺序回收内存,并从回收的内存中判断出理想页在所有堆喷页中的排序,可以发现每喷一次,顺序就会颠倒一次。这使得我得出了相反的结论,即:单个ZONE中越后面成为内存垃圾的页(未触发gc)越先被重新使用。
但后来我才发现由于我ool_msg堆喷方式的问题,单个msg的msgh_descriptor_count很大,一个descriptor单元里有一页数据。这种方式在释放内存时,是反方向释放(见ipc_kmsg_copyout_body的for循环),当然导致页成为内存垃圾的顺序完全反了过来。

4.单个ZONE中先成为内存垃圾的页,在触发gc后仍先被重新使用
先期堆风水时,会堆砌大量ipc_port单元,产生大批ipc_port对应zone中的页,释放后成为内存垃圾,触发GC后,再重新申请。
由于ipc_port对应zone中的页先被重用,因此可以调整堆风水时ipc_port单元的堆砌数量,再对比后面首次堆喷完成后理想页的位置。可以发现堆砌数量越多,理想页偏移位置越远。证明此时没有发生顺序颠倒。
zone_gc中对free_list进行kmem_free的顺序是是从旧到新,里面代码还没研究。
第2,4点似乎有矛盾,以后研究出来再更新吧

3 个赞

第3点标题有笔误,应该时越后面成为内存垃圾的页(未触发gc)越先被重新使用,之前写反了。已编辑修改


第3点之前想错了,已修改更新