文章正文
Darwin的LibC(<malloc / malloc.h>)定义了一个malloc_zone_pressure_relief(OSX 10.7 / iOS 4.3)
LibCache(<cache.h>)定义了缓存成本(cache_set_and_retain),这允许在遇到压力事件时自动清除缓存
GCD(<dispatch / source.h>)定义DISPATCH_SOURCE_TYPE_MEMORYPRESSURE(自OSX 10.9开始)
使用sysctl kern.memorystatus_jetsam_change:Jetsam的优先级列表可以从用户空间改变。这有点像Linux的oom_adj机制,linux中可以指定一个oom的负整数使的应用程序避免OOM(通过降低他们的oom_score也会有效果)。同样在iOS,launchd(启动了所有的应用程序)也可以设置优先级列表。(例如,q.v com.apple.voiced.plist,指定了jetsammemorylimit(8000)和JetsamPriority(-49)。这个sysctl内部实际上调用memorystatus_list_change函数(BSD /kern/ kern_memorystatus.c),这个函数中设置了进程的优先级和状态标志(包括是否是活跃的或者前台等等)。在这种情况下,Android的“LMK”机制与linux的oom类似(android中是在运行时根据应用的前候台状况调整oom_adj的值,lmk的机制选取的时候一般会杀后台应用)。直到iOS 6.X,这种方法仍然是可用的。
使用memorystatus_control(#440)系统调用:xnu 2107内核中引入,(也就是说,iphone上iOS 6就引入了,Mac上是OS X 10.9引入),这(未文档化的)系统调用允许您使用几个“命令”去控制memorystatus和jetsam(ios),如下表所示:
使用posix_spawnattr_setjetsam:可以使用posix_spawnattr系列函数,但这些函数都是未文档化的,仅存在于iOS系统中(这是iOS 7,launchd如何处理Jetsam的方式)。
使用sysctl kern.memorypressure_manual_trigger。这个系统调用用于模拟内存压力级别,这个在OS X 10.9的memory_pressure功能中(-S)使用。memory_pressure其实只是一个变量值<sys / event.h >,可以有这三种状态NOTE_MEMORYSTATUS_PRESSURE_ [NORMAL | WARN | CRITICAL]
使用sysctl kern.memorystatus_purge_on_ *值(OS X)。这些值不会像pageout守护进程那样严重影响memorystatus操作,pageout进程能够迫使memorystatus强制清除警告,紧急或临界状态的值。将这些值设置为0将禁用清除。
使用memorystatus_get_level(#453):此系统调用返回一个介于0和100之间的数字,指定可用内存的百分比。这个仅限于诊断系统的状态。Mavericks或者后续的电脑中,也可以通过活动监视器(和我的进程管理器程序)显示内存压力。
3 / 1/2014 - 添加了jetsam属性plist从iPhone5s,并注意到ledgers特性。
2 / 10/2016 - 增加了jetsam / memorystatus命令为xnu 32xx(iOS 9,OS X 10.11 )。还更新了在iOS上使用的procexp程序
为了简单起见,我们忽略一个事实,某个给定进程所有的某些虚拟内存实际上是为了内核使用去保留和映射的。顺便说一句,64位架构中的256TB,是由于硬件施加的限制(事实上,没有人会实际全部使用它,一般都会少于16EB)。Mac OS X用户空间虚拟内存为47位地址(0x7fffffffffff)也就是128-TB,最上面的(0xffffffff8 ...)128TB为内核保留。
为了简化,我不打算说明真实的条件。
我不会深入讲解进程空闲降级的过程。从10.9开始,进程可以被移动到空闲band,使得它们可以是退出的候选列表中。进程可以通过参数PROC_INFO_CALL_DIRTYCONTROL 调用proc_info,以使内核跟踪进程的状态,这样当进程在“dirty”时或是“clean”都可以寻求保护以免被杀死。这通常与vproc机制(<vproc.h>)一起使用
译:在iOS和Mavericks中处理低内存条件的方式(二)
http://newosxbook.com/articles/MemoryPressure.html,译:冯绍波
MemoryStatus和Jetsam
由于移动设备没有交换空间,XNU移植到iOS时,苹果遇到了一个严重的问题。与PC不同,虚拟内存可以“溢出”到外部存储中,由于闪存的限制,移动设备不太容易实现这个机制。因此,内存已经成为一个更加重要也更稀缺的资源.
MemoryStatus,这个机制,最初在iOS中引入,是一个负责处理低RAM事件的内核线程。iOS认为唯一的方法就是:为使应用程序能够正常运行,系统应该Jettison(弹出)尽可能多的RAM,即使是意味着杀死其他的应用程序也要释放出内存,这就是iOS所指的jetsam,可以在XNU源代码中看到#if CONFIG_JETSAM编译选项。在OS X中,memorystatus不代表kill,表示的是那些标记为空闲退出的进程,这是一种稍微温和的方法,更适合桌面环境。使用dmesg和grep,就可以看到memorystatus的操作。
memorystatus线程是一个单独的线程(不与vm_pressure_thread直接相关),它在XNU的BSD部分启动(通过调用bsd / kern / bsd_init.c中的memorystatus_init方法)。如果定义了CONFIG_JETSAM(iOS),memorystatus会启动另一个线程memorystatus_jetsam_thread,该线程是一个循环,但基本上在阻塞状态,并在必要时唤醒以终止内存列表上的权重最高的进程。只要memorystatus_available_pages <= memorystatus_available_pages_critical,就一直循环,直到条件不满足时,再次阻塞。在iOS中,memorystatus / jetsam不打印消息,但肯定在/Library/Logs/CrashReporter/LowMemory-YYYY-MM-DD-hhmmss.plist中留下了被杀应用的的痕迹,这些日志由CrashReporter生成,并且类似于崩溃日志,它们包含了转储文件。
如果你有一个越狱设备,有一个简单的方法验证jetsam的运行,可以运行一个小的二进制程序,这个程序的功能就是连续分配和memset一个8MB大小的内存块。你会看到很多的应用程序被kill,直到这个恶意的二进制文件也被杀死。
应该注意,通过Jetsam彻底的杀死进程虽然显的无情,但也并唯一一个例子。Linux(以及继承于Linux的Android)在其有一个类似的机制,称为“OOM”( out-of-memory)【译者注:android为LMK(lower memory killer)】,它持有每个进程(可能是可调整的)的得分,并且当遇到存储器不足时,杀死具有高分数的进程。在桌面Linux中,在系统的交换分区耗尽时,OOM会唤醒,在Android中,当RAM运行低也会kill进程。而Android的选取进程也是通过分数驱动的(比分实际上是由进程使用了多少RAM得到),iOS版的做法上面已经说过不再赘述。
基于XNU 2423的源码,jetsam使用“priority bands”(请参阅<SYS / kern_memorystatus .h> JETSAM_PRIORITY常量),这是另一种说法,即jetsam一直在跟踪进程,这些进程由内核空间(memstat_bucket)中的21个链表的数组中维护。Jetsam从最低的bucket 中选择第一个进程(从0开始,或JETSAM_PRIORITY_IDLE),如果当前的bucket的进程列表是空的就,移动到下一个优先级列表(详见bsd/kern/kern_memorystatus.c文件中memorystatus_get_first_proc_locked方法)。进程的默认优先级设置为18,允许jetsam选择空闲和后台进程,这些进程排序在交互式的进程和可能重要的进程之前。具体的排序如下图所示:
Jetsam 的触发还有另一种方式,通过设定进程内存的“高水位线”(HWM),在进程超过水位线时,彻底杀死进程。在jetsam中,HWM模式表示的是,在进程的RSS内存超过系统范围限制时触发(更准确地说,是进程所占的物理上的内存,包括RSS,以及压缩的和I / O Kit相关的内存)。可以使用memorystatus_control操作设置HWM(MEMORYSTATUS_CMD_SET_JETSAM_HIGH_WATER_MARK,稍后讨论)。
在iOS中,Launchd可以设置jetsam bonds的优先级。最初这是在每个守护进程实现的(即在其plist)。不过现在这些设置已移至com.apple.jetsamproperties.model.plist(例如N51(5s),J71(iPad Air)等)。这看起来像下面:
杀死一个进程有点无情,但是对于缺乏交换区的移动设备,RAM的消耗又是苛刻的,所以除了杀进程,真的没有什么可以做。然而,在使用Jetsam杀死进程之前,memorystatus确实允许进程“赎回自身”,通过让memorystatus线程首先发送内核通知(也称为kevent)到 “候选者”中,进而避免被杀。这个knote(NOTE_VM_PRESSURE,<sys / event.h>)将被EVFILT_VM kevent()过滤器接受,例如UIKit将这个通知翻译为iOS App开发者所熟悉的didReceieveMemoryWarning通知。 Darwin的libC和GCD都有内存压力处理器,具体来说:
一般来说,注册了内存压力的应用程序(直接使用Darwin API,或间接地通过UIKit调用)应该减少它们的缓存以及空闲不需要的内存(这里应当注意的是遍历memory的结构体可能导致页错误,从而进一步导致内存的压力)。 UIKit是闭源代码,但是当UIApplication遇到内存警告时,jtool提供了一个很好的反汇编程序来演示它的行为:
有时候,仅仅通过释放内存可能不足以缓解内存压力。最常见的情况是,释放的内存可能马上被另一个应用程序占用了,这不是我们想要看到的。在这些情况下,最后的办法就是使用Jetsam机制杀死潜在在候选人列表中优先级最高的进程.
Controlling memorystatus
在系统中存在这个可以随机决定杀死进程的jetsam线程可能有点危险。因此,苹果使用几个API来控制Jetsam / memorystatus。当然,这些是私有的,也没有文档化(如果你在你的应用程序中使用它们,苹果可能会封杀你的开发人员帐户),但是,他们也是存在的:
memorystatus中的其他可配置值:
Ledgers
iOS重新引入了Ledgers 机制在iOS 5(或者是5.1)上,这个概念已被移植到OS X。我说“重新引入”,因为Mach的原生设计中就有,但从来没有真正被正确实现,直到现在。
Ledgers可以帮助解决资源过度利用的问题。与经典的UN * X模型不同(setrlimit,用户已知为ulimit),Ledgers具有更细粒度,类似QoS的模型,其中Ledgers为每个资源分配一定的配额(RAM,CPU,I / O ),并且以奇怪的方式“重新填充”,这允许操作系统提供漏桶式QoS机制,保证服务级别,如果进程超过其Ledgers,则一个Mach异常就会产生(EXC_RESOURCE,#12,如果memory 服务)。
今后,苹果实现完全基于Ledgers的机制的RAM管理会是很有意义的,特别是在iOS上RAM是一个稀缺的资源(没有交换区)。Jetsam将可能变成作为一种最后手段去保证内存。
参考:
深入理解Mac OS X和iOS内部,J Levin
ChangeLog:
脚注:
Jan. 2, 2017, 12:30 p.m. 作者:zachary 分类:iOS 阅读(3440) 评论(2)
评论: