为了解决内存安全和效率问题,现在的计算机和操作系统引入了虚拟内存和物理内存,这里不做详述。我们主要探讨,通过原理,找到优化App的方案。
虚拟内存的工作原理:引用了虚拟内存后 , 在我们认为进程中有一大片连续的内存空间,也就是说从 0x000000 ~ 0xffffff
我们是都可以访问的。但是实际上这个内存地址只是一个虚拟地址,而这个虚拟地址通过一张映射表映射后才可以获取到真实的物理地址。
也就是说,系统对真实物理内存访问做了一层限制,只有被写到映射表中的地址才是被认可可以访问的。虚拟地址
0x000000 ~ 0xffffff
这个范围内的任意地址我们都可以访问,但是这个虚拟地址对应的实际物理地址是计算机来随机分配到内存页上的。
工作原理如下:
显然 , 引用虚拟内存后就不存在通过偏移可以访问到其他进程的地址空间的问题了 。
因为每个进程的映射表是单独的,在你的进程中随便你怎么访问,这些地址都是受映射表限制的,其真实物理地址永远在规定范围内,也就不存在通过偏移获取到其他进程的内存空间的问题了。
而且 , 应用每次被加载到内存中 , 实际分配的物理内存并不一定是固定或者连续的,这是因为内存分页以及
懒加载
以及 ASLR 。
Android 4.0
、Apple iOS4.3
、OS X Mountain Lion10.8
开始全民引入ASLR
技术,而实际上自从引入ASLR
后,黑客的门槛也自此被拉高,不再是人人都可做黑客的年代了。
cpu 寻址过程:通过虚拟内存地址,找到对应进程的映射表;通过映射表找到其对应的真实物理地址,进而找到数据。这个过程被称为 地址翻译,这个过程是由操作系统以及 cpu
上集成的一个 硬件单元 MMU
协同来完成的 。
虚拟内存分页
刚刚提到虚拟内存和物理内存通过映射表进行映射,但是这个映射并不可能是一一对应的,那样就太过浪费内存了。为了解决效率问题,实际上真实物理内存是分页的。而映射表同样是以页为单位的。换句话说,映射表只会映射到某一页,并不会映射到具体每一个地址。
Mac OS
、linux
内存4kb
一页,iOS
是16kb
一页。可以使用PAGESIZE
命令,在终端直接查看。
0 和 1 代表当前地址有没有在物理内存中。
从上图我们也可以看出,进程的实际物理内存地址并不是连续的,而是由若干完整的内存分页组成。
- 当应用被加载到内存中时 ,并不会将整个应用加载到内存中。只会放用到的那一部分。也就是
懒加载
, 换句话说就是应用使用多少 , 实际物理内存就分配多少。 - 当应用访问到某个地址,映射表中为
0
,也就是说它并没有被加载到物理内存中时 , 系统就会立刻阻塞整个进程 , 触发一个缺页中断 ,即Page Fault
。 - 当一个
缺页中断
被触发,操作系统会从磁盘中重新读取这页数据到物理内存上,然后将映射表中虚拟内存指向对应物理内存。 如果当前内存已满,操作系统会通过置换页算法找一页数据进行覆盖。这也是为什么开再多的应用也不会崩掉,但是之前开的应用再打开,就会重新启动的根本原因。
思考:
Page Fault
会阻塞进程,那么肯定会对性能产生影响,那么我们是不是可以在这里做一些优化呢?