文章正文
访问未分配的内存:引用先前未曾访问的内存指针分配 – XNU将其转换为EXC_BAD_ACCESS异常,并且该过程接收到段故障(SIGSEGV,信号#11)。
访问已分配但未提交的内存:解除引用先前已分配但尚未使用的内存的指针[没太理解]. - XNU拦截并且意识到它不再可以拖延,并且必须分配物理页面。引起Page Fault的线程在分配这些页面时将挂起不再运行。
访问内存,但未能符合其权限:内存页面以与标准UNIX文件权限类似的方式受r / w / x保护。尝试写入只读页(r--或r-x)将导致页错误,XNU内核将这种异常转换为总线故障(SIGBUS,信号#7),或者如果这个页是系统隐含的共享内存时变为写入时复制(COW)操作。
vmmap - 检查单个进程的虚拟内存,类似于Linux的/ proc / <pid> / maps的方式,查看进程的“maps”。
vm_stat - 从系统范围的角度提供虚拟内存的统计信息。这本质上只是一个调用Mach host_statistics64 API的封装,并打印出vm_statistics64_t的相关信息(从<mach / vm_statistics.h>
top - 提供与性能相关的系统上的以及每个进程的统计信息,通过这个命令可以看到MemRegions,PhysMem和VM统计信息。
译:在iOS和Mavericks中处理低内存条件的方式(一)
http://newosxbook.com/articles/MemoryPressure.html,译:冯绍波
译者语:作者在14年写了mac os&ios的圣经,16年出了第二版,可惜国内现在买的都是14年版本的译本,所以只能从作者的网站上了解一些最新的内容了。
1、关于
OS X和iOS中的内存压力是虚拟内存管理的一个非常重要的方面,在我的书中已经探讨了一点。虽然在书中我提到了Jetsam 和 memorystatus,但是该机制随着时间的推移发生了重大变化,导致最近在Mavericks电脑上引入了一些非常重要的sysctl接口和系统调用。这些新的特性改变是在我为OS X和iOS开发Process Explorer 工具时遇到的,因此我在这里记录他们。并作为本书第12章的附录,当然你也可以自己阅读。
你为什么要关心这个?
对于物理设备来说,CPU的另一个方面就是物理内存(RAM)。RAM是系统中最稀有的资源,如果应用程序有充足的内存能够使用直接关系到更好的性能,由于内存总量是一定的,所以这通常是牺牲其他应用程序的内存做代价的。在iOS中,没有可以能够交换内存的交换空间,使得内存资源更为关键。本文旨在让您在下次调用malloc()或mmap()多考虑一下,以及澄清在iOS上崩溃的最常见原因就是由于较低系统内存引起的。
2、首先要说的就是:虚拟内存
无论应用程序是干什么的,但是它必须使用内存。内存中保存这程序的代码,数据和状态。当然内存上与其他应用的隔离,会提供更多的安全性和稳定性。因此我们将此空间称为应用程序的虚拟内存,它是应用程序作为成为进程的特性之一:同一进程中,应用程序的线程将共享相同的这段虚拟内存空间。
虚拟存储器中的术语“虚拟”意味着存储器空间,但并不完全对应于系统上的真实存储器。这表现在几个方面:
1、虚拟内存空间可能超过可用的实际内存量 – 依赖于处理器处理宽度和操作系统,虚拟内存空间可高达4GB(32位)或256TB(64位)。特别是在后一种情况下,远远超过实际可用的内存量。
2、事实上,虚拟内存可能根本不存在:由于这样巨大的内存空间超过了物理内存支持能力,只有当应用程序明确请求虚拟内存(即分配),系统将物理内存映射到虚拟内存中 。因此,真正的内存在虚拟内存映像中相当稀疏。
3、即使分配了,虚拟内存可能仍然是虚拟的:大多数情况下,程序员分配远远超过他们需要的。因此,malloc操作只分配页表项,但很少分配内存本身。实际上是访问内存时,比如,通过memset,才会导致物理上的分配。
4、系统可以将内存备份到磁盘或网络上 - 或者称为“交换”内存到后备存储。 OS X传统上使用交换文件(在/ var / vm中), iOS没有使用交换特性。
5、使用的虚拟内存可能会共享或可能不共享。操作系统能够使得当前的进程与其他进程共享虚拟内存,这对这些进程都是不可感知的。这适用于您使用的文件缓存(即调用mmap时声明的内存)。如果你的进程和另一进程mmap同一个文件,操作系统可以给你每个你的私有虚拟副本,这实际上是由一个单一的物理副本支持。所述物理副本将被标记为不可写。只要每个进程从内存中读取,一个副本就足够了。然而,如果进程向这种隐式共享存储器写入,则写入过程将触发page fault,这将导致内核执行写时复制(COW),其产生新的物理副本,用于这个进程的写入。
把上面总结一下,我们可以得出以下“公式”:
VSS = RSS + LSS + SwSS
VSS 虚拟集大小,由top,ps和其他报告可以看到。
RSS Resident集大小- 进程的实际RAM占用空间。也显示在top,ps等命令中
LSS “Lazy” 集大小- 系统已同意分配但尚未分配的内存
Swss “Swap”集大小 - 以前在RAM中但已被推出以进行交换的内存。在iOS中,此值始终为0
所有以上的东西都可以通过一个简单的例子证明:使用vmmap可以查看任何一个进程,这里以shell本身为例:
专业术语:
在本文中,使用了以下术语:
Page: 内存管理的基本单位。在英特尔和ARM架构下,通常为4k(4096),在ARM64下为16k。您可以在OS X(或其他操作系统上使用sysctl hw.pagesize)上使用pagesize命令找出默认页面大小。英特尔架构下支持超级页面(8k)和巨页(2MB),但在实际中,这些相对很少使用.
Phyyical Memory / RAM :安装在主机(Mac或ios)上的有限内存量。您可以使用hostinfo命令获取此值。
Virtual Memory:程序或系统本身分配的内存,通常通过调用malloc,mmap或更高级别的调用(例如Objective-C 支持的alloc等等)。虚拟内存可以是私有的(由单个进程拥有)也可以是共享的(由2+个进程拥有)。共享内存对应用程序可以是可见的或是不可见的(操作系统上的)共享。
Page Fault: 当内存管理单元(MMU)检测到对违反虚拟内存的访问时,即出现缺页异常(Page Fault)。
也即下列情况之一发生:
工具
Apple提供了几个重要的工具来检查看内存的情况:
当然,我这里提一下我自己写的工具,进程探索器(procexp),它提供相对于TOP命令更丰富的内存统计信息。
3、内存压力
Mach层内部有两个变量可以表示内存压力:
vm_page_free_count:目前free内存的页数。
vm_page_free_target:至少有多少内存也应该是free。
你可以使用sysctl很容易的看到这两个值:
当free页面数量低于目标量时,我们就有了一个压力情况(这里还有其他潜在的情况,但为了简单,这里先做省略)。您还可以使用sysctl命令查询vm.memory_pressure的值,得到压力状态。在OS X 10.9和更高版本中,您还可以查询kern.memorystatus_vm_pressure_level,它有三个值:1(NORMAL),2(WARN)或4(CRITICAL)
在内核初始化之后,主线程变为vm_pageout,并生成一个专用线程vm_pressure_thread,用于监视内存压力事件。这个线程是空闲的(一般情况都在阻塞自己,等待唤醒的信号)。当检测到压力时,线程将由vm_pageout被唤醒。这种行为在XNU 2422/3(OSX 10.9 / iOS 7)中被修改(源码中明显看到将其移到在vm_pressure_response中)。
另外要说明的是,只有VM_PRESSURE_EVENTS被#define的时候,VM压力处理才会被编译到XNU内核中。vm_pressure_thread在2050的内核中什么都不做, 如果没有定义,2422/3的内核也不会被启动。此外,在iOS内核中,配置#define CONFIG_JETSAM后,将修改内存压力事件的一些行为,这个表现为更频繁地将事件分配到memorystatus线程,用以更新其计数器以及其他的用途。
[mach] _vm_pressure_monitor
XNU导出有#296系统调用,这个调用没有被文档记录,vm_pressure_monitor (bsd / vm / vm_unix.c),它是mach_vm_pressure_monitor(osfmk / vm / vm_pageout.c)的一个封装。这个系统调用(通常有Mach内部调用)定义如下:
int vm_pressure_monitor(int wait_for_pressure,int nsecs_monitored,uint32_t * pages_reclaimed);
这个调用一般将立即返回,如果wait_for_pressure非零就会阻塞。它将返回pages_reclaimed在nsecs_monitored的计数中释放了多少物理页面(而不是真正的nsecs,如循环迭代那么多)。其返回值表示所需的提供的页数(对应sysctl命令中的vm.page_free_wanted属性)。调用系统的调用很简单,而且不需要root权限。 (同样,你可以使用sysctl查询vm.memory_pressure)。
你可以使用procexp结合“vmmon”参数,尝试这个系统调用(这里说明一下,procexp通过一个线程实现了交互模式,显示压力警告)。指定附加参数“oneshot”将不等待压力,直接调用。否则,将一直等待直到检测到压力为止。
但是如何实际回收内存?为此,我们需要涉及memorystatus.
未完待续。
Dec. 31, 2016, 11:24 p.m. 作者:zachary 分类:iOS 阅读(2305) 评论(0)