• 程序员详解iOS的原生和第三方虚拟内存机制

    2012-02-04

    分类:iOS

     

     

    虚拟内存这项技术本质上就是对内存地址进行映射,使得进程认为自己拥有连续的,大量的内存,提高内存利用率,降低程序编写难度。比如一个程序被系统告知其可用的内存片段是0到100页。而实际上其占用的内存片段可能是分散的,有可能其占用的真正物理范围是70-120页,201页到240页,还有10页在磁盘上面。

     

    因此,虚拟内存范畴可以划分为两类

    第一类:将进程占用的内存地址映射到RAM内其他位置。

    第二类:将进程占用的内存地址映射到磁盘上面。

    而我们通俗讲的虚拟内存就是第二类。

     

    第一类由于都是在RAM内进行的,速度很快,并且有专门硬件负责转换,因而就像是把宾馆房间的门牌号换一下而已,对程序的执行没有任何影响。

     

    第二类由于磁盘的速度读写速度太慢,且很多都会有一定读写次数的限制,因此,当在磁盘上的页面要被使用时候,并非直接在磁盘上修改,而是重新搬运回RAM并暂时冻结进程,搬运完成后在RAM内被修改。而RAM内不活动的页面也会在内存不足时候搬运到磁盘上,为活动的进程提供可用的物理内存。也就是说,磁盘相当于一个仓库而已,真正干活的地方还是在RAM里面。

    这种方式使得在一些小内存的机器上也可以运行一些占用内存大程序,但是不足之处就是慢、卡。

     

    iOS5必定是有第一类虚拟内存的,但是没有第二类。

     

    首先,如果使用虚拟内存,必定会造成一定的慢,卡,大家在PC上内存满时候应该体会过。而这一点正是苹果所不愿意的。苹果一定要让一项技术可以流畅的在设备上运行时候才让它出现。这个很好理解,多任务就是这样的。

     

    其次,设备会在内存不足时候自动关掉一些后台程序,如果使用了这项技术,就不会出现内存不足的情况,一旦内存不足,系统会自动将一些不活动进程在内存里数据搬到磁盘里,为活动的程序提供空间,因而也就是说所有的程序都会在后台保留,最终虚拟内存占用的磁盘空间也会越来越大。而事实上并没有这种情况。而苹果本身的设计也就是允许用户不去关闭这些后台程序。

    当然,你也可以认为iOS的虚拟内存不会提供给应用程序使用。但是如果真的这样,这虚拟内存又有什么用呢?

     

    除此之外,苹果也在发布会后的一次WWDC大会上说了:limited memory/virtual memory/no swapfile。也就是说并没有通俗意义上的虚拟内存。

     

    至于一些开发者发现在terminal里面输入top时候有一个VM的数值,并怀疑它是虚拟内存大小。那个具体是什么我也不知道,但是我认为并不是的。那个数值确实会随着程序开的越多而越大,甚至可以到达4G。

     

    下面是我分析的办法。我将用户盘和系统盘全部塞满,发现系统仍然可以正常运行。当我打开那些程序的时候,VM的数值同样增大,最终同样可以到4G以上。那这部分空间是在什么地方呢?假如你说是在除系统盘和用户盘以外的地方,好的,这不是不可能,但我们可以算一下。我是32G的,用户盘大小29754M,系统盘大小1024M,加起来30778M也就是30G,那剩下的4G往什么地方塞?况且一般来讲由于换算原因和其它因素实际可用空间都会小于称标空间的。

     

    至于iOS系统的内存管理究竟是怎么样的呢?据我推测是这样的。

     

    ①当内存不足时候,首先会先叫后台程序或者系统进程释放。此时后台程序会主动释放一些不太重要的数据资料,比如说图片信息之类的,保留最重要的状态信息,与此同时也可能对内存数据进行压缩。此时,由于占用处理器资源,可能会出现卡顿。

     

    ②当内存依然不足时,系统便开始考虑关闭一些后台程序了。此时,后台程序会得到信号,然后开始运行,进行数据的保存,完成后退出,释放内存。此时,由于会占用处理器以及储存器,可能会再次导致卡顿。

     

    ③如果问题还不能得到解决,系统就会强制结束前台程序,同时在/var/logs/AppleSupport/下面留下一堆lowmemory的错误报告。这就是常说的闪退的一种原因。

     

    由此也可以说明,iOS系统的内存管理确实很先进,确实是没有必要去关闭后台程序。当然,如果你认为①②步骤导致的小卡让你很不爽,那你还是主动去关吧。

     

    说完了iOS系统的内存管理,下面来说一下用deb安装的虚拟内存,也就是真正意义上的虚拟内存。

     

    有人说开启这种虚拟内存完全没有用,只能是使得内存看上去增大了很多而实际上没有任何用,还会导致系统不稳。

     

    而我想在此澄清的是:

     

    ①虚拟内存并不能增大你设备的内存,只是为正在运行的程序腾出空间。

     

    打个比方,就是虚拟内存并不能增大你工作间的面积,但是它给你提供了一个仓库,可以将一些当前没有用的东西搬进去放着,这样你就可以拥有更多空间干你正在干的事,而仓库到底不是工作的地方。

     

    ②虚拟内存原本是不会导致设备系统不稳定的,在iOS3时代用过的人都应该知道,这个只是在iOS4时代之后才出现的问题。

     

    ③至于虚拟内存是否会影响设备的寿命,这个我想应该是可以忽略的。我通过查看一天的内存页面输出量,也就相当于写入闪存的数据量。如果不开虚拟内存大概是几MB,如果开启大概是200MB左右。如果你开启的大概是256MB,也就是平均这个区域一天才能全部写满一次。

     

    当然也有的锋友担心的是对同一个区块反复擦写。其实这个是不必担心的,因为闪存有损耗平衡,它会尽量少写入擦写次数多的地方,并且每次重启虚拟内存文件都是重新创建的。除此以外只有在内存不足的时候才会写入闪存,而最主要的读取是不会影响寿命的。而nand闪存写入次数大概是10万次,结合总容量,看看有多大影响?

     

    我们使用虚拟内存的主要目的是给当前运行的程序提供更多的物理内存,防止出现系统由于内存不足采取的措施导致的卡顿和闪退,当然也可以在后台运行更多的程序。

     

    使用虚拟内存一定程度上可能会导致切换程序的卡顿。此时系统正在将磁盘内的数据转移到内存。

     

    UNIX的虚拟内存是这样的,在内存并没有短缺的时候,就开始将内存内一些不活动的页面写入磁盘,这样当进程需要内存时候,可以直接将这部分分配给进程,如果这些不活动页面没有被分配,而占用他们的进程又需要修改储存在其中的数据,则也可以直接修改,因此唯一可能造成卡顿的操作就是激活有页面被交换到磁盘上去的进程,而即便这样,也只需要将磁盘上一部分数据读取到内存就可以。经过测试,touch4闪存读取速度是接近40MB/s,也就是说,假设一个进程占用了40M内存并被全部交换到闪存里,最多这个进程也就被暂停1s。而事实上,很少有程序会占用到40M内存,基本上就是游戏,并且一般很少会全部交换到闪存,就算这样,激活这个进程也没必要把全部页面都交换到内存里,除此以外,还记得切换程序的过渡动画吗,貌似也有1s吧。因此,几乎感觉不到卡顿的,就算有一点,也没关系啊,总比后台被关掉和前台闪退好吧。

     

    因此,虚拟内存还是有很大好处的,并不是只是让内存看上去大一点的东西。

     

    下面,我就来为大家剖析一下deb虚拟内存原理是什么。

    其实所有的虚拟内存的deb的原理完全一样,因此横向比较其稳定性没有任何意义。简而言之,其功能只是开启了系统原生就有的功能而已。

     

    所有的虚拟内存deb解包后都有一个放在/system/library/launchdeamons/里面的一个plist文件。这个路径存放的是所有开机启动的进程配置文件。一般这个plist指向启动的程序就是在/sbin/里面的dynamic_pager。也有的是指向vm,而这个vm就是deb安装后放在sbin里面的一个程序,本质上和dynamic_pager是一样的。而其他文件不过就是一些辅助用途,比如fm用来释放内存,还有一个关闭虚拟内存加密用的。

     

    下面我们来讲一讲这个dynamic_pager到底是个什么东西。

    其实它并不是虚拟内存的进程,虚拟内存不需要进程,是操作系统的功能。这个进程的功能是和系统通信,负责创建,删除虚拟内存文件。如果你强行干掉这个进程,虚拟内存仍然可用,但不能增加减少交换文件数量。一旦交换文件写满,当前的程序会卡死。

     

    在terminal里面登陆root后直接输入dynamic_pager回车就可以开启虚拟内存。这个进程有这么几个选项:

    -F 单个虚拟内存交换文件大小,默认为64m,使用时候在后面输入文件字节数。

    -S 虚拟内存交换文件路径和名称,默认在/var/vm,默认文件名swapfile编号。

    -H 设置当swapfile的总剩余空间低于多少字节时候创建新的交换文件。

    -L 设置swapfile总剩余空间多于多少字节时删除空闲的交换文件

    -P 优先级,不过貌似没什么用。

     

    讲清楚了这个程序的作用,下面说说它的来历。

     

    很多人以为这个是系统自带的,其实不是的。这是越狱后cydia自动安装上去的。大家打开cydia,在刚越狱完后就安装的软件包里面可以找到,有一个点开后在文件系统一项可一看到这个程序。

     

    总之,这个dynamic_pager是十分重要的,尽管不是原生的,但是由其作用是开启系统的虚拟内存,我们可以知道,iOS原生就支持虚拟内存,只不过是被苹果拿掉了而已。

     

    说完了这个,我再给大家讲一下虚拟内存交换文件的管理方法。

    经过我的实验和查询一些资料,我总结出来了其管理的特点。

    其映射方式很有可能直接由内存地址映射到闪存的物理地址,也就是说其读写不用经过文件管理系统,直接按照闪存的物理地址写入。因此你将虚拟内存文件删除不会影响虚拟内存的工作。而其生成这个文件的唯一目的是占个位置,让操作系统和别的程序知道这个区域是有用途的,防止其他程序在这块地址创建文件导致内存数据被篡改。

    除此以外,无论你怎么设权限,就算全部权限取消,就是每个用户组读取,写入,执行都取消,也不影响虚拟内存,交换文件一样被修改。很有可能其完全不受文件管理系统控制,完全独立开来。因此修改其权限没有什么意义。很多人说修改为777,事实上000还更稳定。

     

    最后,也就是大家最关心的,为什么虚拟内存不稳定,原理见下。

     

    虚拟内存造成系统不稳定的直接原因就是重要进程崩溃和出错。

    崩溃还算一种比较好的结果。

    如果是普通程序,就是闪退,safari最典型。

    如果是springboard,就是安全模式。

    如果是launch,就是重启。

     

    进程出错可能会导致当机,而最为严重的是对某些文件的错误修改,也就是虚拟内存导致白苹果的重要原因,当然我还遇到过所有程序消失之类的现象。

     

    因为每次进程崩溃都会在/var/logs/AppleSupport下面留下错误报告。经过长时间的搜集和整理,发现其主要都是一类错误,就是SIGABRT或者SIGBUS。这都是常见的内存错误,一般都是由于进程请求了一个错误的内存地址导致的,错误报告附带了这个地址。我还发现,其请求的地址都是超出了RAM范围的。也就是说其请求的是被交换到闪存上的部分。

     

    一个开启了虚拟内存的机器,当出现这种情况时,系统会检测出进程请求的地址溢出,此时会出现中断,也就是处理器停止处理当前正在处理的任务,转而处理一个临时新增的任务,也就是将这个地址映射到的磁盘区域的数据转移到内存里面,然后再恢复之前的任务。也就是说,出现这种情况时候很可能此时系统并没有中断,当前正在执行的任务没有停止,没有进行数据的转移,最终导致内存地址出错。这个同样可以解释进程出错,可能数据转移还没有完成,原先的任务却开始运行,此时溢出的内存地址已经映射到内存区,不会出现内存错误,但是数据转移没有完成,也就是说这块区域的数据并不是全是闪存里的数据,结果就是进程出错。

     

    造成这个的因素是这样的。

     

    苹果为了流畅,反应灵敏可谓无所不用极其。大家也知道ios的用户界面渲染优先级非常高,完全有可能苹果直接把用户界面渲染也作为了一个中断。中断也是有优先级的。如果这个中断优先级高于虚拟内存的,就可能出现上面讲的情况,数据还没有转移完,虚拟内存的任务却被停止了,而处理器开始处理用户界面渲染的任务。如果刚好用户界面渲染的数据被交换到了闪存上,而没来得及转移到RAM内,就会出现内存地址溢出或者进程出错,最典型就是安全模式和花屏。而Springboard和用户界面渲染关系最为密切,这样也可以解释为什么这个进程崩溃的次数最多。

     

    要想解决这个问题,就要降低用户界面渲染优先级或者取消其中断的权利,当然也可以提高虚拟内存中断的优先级。这一步仍然有待研究来实现。

     

    分享到: