
背景
在计算机领域里,任何一个技术概念,都是为了解决实际问题而诞生的,为什么有二进制重排,本篇文章一起研究下
目标
- 明白物理内存的两大弊端
- 明白虚拟内存如何解决物理内存两大问题
- 明白发生页终断的原因
- 理解为什么ASLD技术
研究
虚拟内存的诞生
在早期的计算机中 , 并没有虚拟内存的概念 , 任何应用被从磁盘中加载到运行内存中时 , 都是完整加载和按序排列的 .

直接使用物理内存 , 会出现以下问题:
- 安全问题 : 由于在内存条中使用的都是真实物理地址 , 而且内存条中各个应用进程都是按顺序依次排列的 . 那么在 进程1 中通过地址偏移就可以访问到其他进程的内存 。
- 效率问题 : 随着软件的发展 , 一个软件运行时需要占用的内存越来越多 , 但往往用户并不会用到这个应用的所有功能 , 造成很大的内存浪费 , 而后面打开的进程往往需要排队等待。
为了解决上面两个问题,虚拟内存随机诞生
虚拟内存解决安全问题
理解虚拟内存和物理内存的管理

- 引用了虚拟内存后 , 在我们进程中认为自己有一大片连续的内存空间实际上是虚拟的 , 也就是说从 0x000000 ~ 0xffffff 我们是都可以访问的 . 但是实际上这个内存地址只是一个虚拟地址 , 而这个虚拟内存地址通过一张映射。
- 表映射后才可以获取到真实的物理地址,系统对真实物理内存访问做了一层限制 , 只有被写到映射表中的地址才是被认可可以访问的 .
- 例如 , 虚拟地址 0x000000 ~ 0xffffff 这个范围内的任意地址我们都可以访问 , 但是这个虚拟地址对应的实际物理地址是计算机来随机分配到内存页上的 .
虚拟内存解决进程间安全问题原理
显然 , 引用虚拟内存后就不存在通过偏移可以访问到其他进程的地址空间的问题了 .
因为每个进程的映射表是单独的 , 在你的进程中随便你怎么访问 , 这些地址都是受映射表限制的 , 其真实物理地址永远在规定范围内 , 也就不存在通过偏移获取到其他进程的内存空间的问题了 .
而且实际上 , 每次应用被加载到内存中 , 实际分配的物理内存并不一定是固定或者连续的 , 这是因为内存分页以及懒加载以及ASLR(地址随机加载)所解决的安全问题 .
理解通过虚拟内存地址如何找到数据
引入虚拟内存后 , cpu 在通过虚拟内存地址访问数据的过程如下 :
- 通过虚拟内存地址 , 找到对应进程的映射表 .
- 通过映射表找到其对应的真实物理地址 , 进而找到数据 .
这个过程被称为地址翻译
虚拟内存解决效率问题
前面提到虚拟内存和物理内存通过映射表进行映射 , 但是这个映射并不可能是一一对应的 , 那样就太过浪费内存了 ,为了解决效率问题 , 实际上真实物理内存是分页的,而映射表同样是以页为单位的 .
在 linux 系统中 , 一页内存大小为 4KB , 在不同平台可能各有不同:
- Mac OS 系统中 , 一页为 4KB ,
- iOS 系统中 , 一页为 16KB .
可以在终端通过pagesize指令获取映射表的大小,例如mac如下:
那么为什么说内存分页就可以解决内存浪费的效率问题呢 ?
内存分页原理
(上图中我们也看出 , 实际物理内存并不是连续以及某个进程完整的)
映射表左侧的 0 和 1 代表当前地址有没有在物理内存中 . 为什么这么说呢 ?
- 当应用被加载到内存中时 , 并不会将整个应用加载到内存中 . 只会放用到的那一部分 . 也就是懒加载的概念 , 换句话说就是应用使用多少 , 实际物理内存就实际存储多少 .
- 当应用访问到某个地址 , 映射表中为 0 , 也就是说并没有被加载到物理内存中时 , 系统就会立刻阻塞整个进程 , 触发一个我们所熟知的 缺页中断 - Page Fault .
- 当一个缺页中断被触发 , 操作系统会从磁盘中重新读取这页数据到物理内存上 , 然后将映射表中虚拟内存指向对应 ( 如果当前内存已满 , 操作系统会通过置换页算法 找一页数据进行覆盖 , 这也是为什么开再多的应用也不会崩掉 , 但是之前开的应用再打开时 , 就重新启动了的根本原因 ).
通过这种分页 和覆盖机制 , 就完美的解决了内存浪费和效率问题 .
理解ASLR(地址空间随机布局)技术
问题:当应用开发完成以后由于采用了虚拟内存 , 那么其中一个函数无论如何运行 , 运行多少次 , 都会是虚拟内存中的固定地址 .
假设应用有一个函数 , 基于首地址偏移量为 0x00a000,都是固定的 , 那么虚拟地址从 0x000000 ~ 0xffffff , 基于这个 , 那么这个函数我无论如何只需要通过 0x00a000 这个虚拟地址就可以拿到其真实的实现地址 .
而这种机制就给了很多黑客可操作性的空间 , 他们很轻易的提前写好程序获取固定函数的实现进行修改hook操作。
为了解决这个问题 , ASLR(地址空间随机布局) 应运而生 . 其原理就是 每次虚拟地址在映射真实地址之前 , 增加一个随机偏移值 , 以此来解决我们刚刚所提到的这个问题 .
几乎所有的操作系统开始全民引入 ASLR 技术 , 而实际上自从引入 ASLR 后 , 拉高了黑客的hook门槛.
总结
问题1: 物理内存的两大弊端
- 内存安全问题:各个进程都可以通过地址偏移获取不同进程的数据
- 效率问题:启动进程,会一次加载全部代码和数据到物理内存,且当app一部分功能不使用也会常驻内存,后面进程申请内存需要排队安排
问题2: 虚拟内存如何解决内存安全性问题
- 进程 内存都需要通过单独的虚拟映射表和物理进行内存映射 ,获取数据,由于每一个进程访问的空间范围都是单独的映射表提供,所以解决了数据安全问题
问题3: 虚拟内存如何解决内存效率问题
- 映射表 内部采用懒加载的方式申请物理内存,应用启动不是所有数据都被写入物理内存,采用按需加载
- 当进程内存去映射表申请数据不存在,会触发分页终断page fault 操作,从磁盘读取数据,然后写入物理内存,返回数据
- 当映射表去物理内存申请空间,出现内存不足时,物理内存会对那些不使用的空间进行覆盖写入,用到覆盖的数据的进程启动需要再次触发分页终断,重新申请数据
问题4: 什么时候出现分页终断
- 当进程内存去映射表申请数据不存在,会触发分页终断page fault 操作 ,从磁盘读取数据,然后写入物理内存,返回数据,这个过程由于系统会阻塞进程,同时iOS会涉及签名问题,会引起一部分时间的消耗,后续会讲解如何通过较少分页终断优化启动时间
问题5: 为什么需要ASLR操作
- 未使用ASLR(地址空间随机布局)之前,进程启动,函数的地址都是固定不变,黑客很容易hook函数地址进行操作
- 使用ASLR(地址空间随机布局)之后,进程启动,函数的地址是随机的,提高了黑客hook函数的成本,提高了安全性
感谢您的阅读,本篇文章已阅读完毕,欢迎浏览我的其他文章!