本文介绍了 iOS 上 mmap 的几个主要应用场景,其中最重要的是可以用它来解决应用程序
OOM的问题。更多内容和技术交流,欢迎关注微信公众号:码工笔记
一、mmap 基本概念
mmap:文件映射,用于将文件或设备映射到虚拟地址空间中,以使用户可以像操作内存地址一样操作文件或设备。
二、mmap 的属性分类:
-
根据 mmap 是否基于文件:
- 基于文件的映射:
- 将硬盘上的文件映射到进程的虚拟地址空间中的一段空间,开发者可以像读写内存一样直接读写硬盘上的文件。
- 不基于文件的映射(
MAP_ANONYMOUS)- 在虚拟地址空间中开辟一段空间,类似
malloc
- 在虚拟地址空间中开辟一段空间,类似
- 基于文件的映射:
-
根据对 mmap 数据的修改是否立即对别的进程可见:
MAP_SHARED:- 基于文件:其他 map 了同一文件的进程可以看到本进程对文件的修改
- 不基于文件:写进程需要调用 msync 才能让别的进程看到修改
MAP_PRIVATE:修改只有本进程可见
三、mmap 的主要特点及其使用场景:
-
用 mmap 提高文件读写效率
mmap 可以使开发者像操作连续内存一样读写一个文件。且默认使用操作系统的分页功能,按需懒加载相关的页。硬盘上的文件数据(实际上是
Page cache中的数据)以页为单位映射到页表项,读写效率较高。 -
用 mmap 提高写文件的可靠性
在 iOS 系统中,写文件有很多种不同的方式,如 fopen/fwrite,open/write,mmap 等。不论采用哪种方式,在调用完写操作之后,写入的数据并不会直接固化到硬盘的文件中,而是会由操作系统或标准库进行缓存(以提高I/O效率)。使用 mmap 的方式,如果写完后用户进程崩溃,操作系统缓存的数据会由系统保证将它正确地 flush 到硬盘上,而类似情况下 write/fwrite 写的的数据则可能会丢失(详见客户端开发基础知识——写文件避“坑”指南)。
-
用 mmap 来解决 App 内存限额问题(
OOM)在 iOS 系统中,如果应用程序进程占用的内存过多超过预定限额,就会发生 OOM(Out Of Memory),这种情况下系统会杀死进程,造成用户视角的闪退。应用程序所能使用的内存限额由多种因素决定,主要分以下两种:
- 操作系统人为设置的限额
- iOS 系统会根据不同的机型、系统、应用程序类型做不同的限制
- 像第三方输入法(
Keyboard Extension)进程,最新的 iOS 系统限定其最多只能使用 80M 的内存,超过此限额则就会被操作系统杀死
- 设备内存的物理限额
- 设备的物理内存大小是一定的,比如 iPhone 6 的内存为 1G,也即运行在手机上的所有进程(包括用户进程和操作系统)一共最多只有 1G 的物理内存可用。
- 因为 iOS 没有 swap 交换区,操作系统在物理内存资源无法满足需要时只能通过杀死用户进程来释放资源
- 系统会优先杀死后台进程,如果还是不能满足需求,才会杀死前台进程
设备上运行的各个进程在其所占用的物理内存中所存放的数据按其是否可以安全地丢弃可以分为两种:
- 可安全丢弃的(即丢掉后还可以从文件中完整无误地读回来)
- 不可丢弃的(即本数据在操作系统中并未明确与某文件有直接映射关系)
其中,可安全丢弃的数据,即使它目前占用了物理内存,因为它随时可以被操作系统按需从文件中读回来,所以操作系统可以随时根据需要把这些数据从物理内存中清除出去。所以,可以认为这种数据是不占据物理内存配额的。
而不可丢弃的数据,这种数据只存在于物理内存中,没有文件与它绑定。因此它所在的物理内存会一直被占着,直到应用程序显式通知操作系统释放这些内存(比如调用 free 方法),任何提前释放都可能会导致数据丢失(data loss)。
还有一种较特殊的情况是:compressed memory,它是指操作系统可能会在一定条件下选择将某些 dirty memory 进行压缩,以减少它们占用的物理内存大小,在进程需要访问它们的时候先做解压再进行访问。这种类型的内存也是不可丢弃的,它会一直占用压缩后大小的物理内存。
如果使用了基于文件的 MAP_SHARED 类型的 mmap ,应用程序在相应的虚拟内存地址上的读写操作都直接映射到文件块上,其所用物理内存在操作系统看来都是可以安全丢弃的,完全不占用物理内存配额(页表项所占的除外)。
- 操作系统人为设置的限额