先不需要看 Intel 手册,楼主对操作系统的内存管理没有正确的概念,建议先阅读操作系统教材 —— 例如楼主可能没有分清逻辑地址、虚拟地址/线性地址、物理地址之间的关系。我简单抛块砖。1. 在实模式时代,CPU 限定了操作系统的寻址能力为 1MB。段基址左移 4位 累加 16位 偏移地址得出20位的物理地址(实模式下段寻址的上限)。在这种模式下缺乏必要的保护设计也没有高效的内存管理机制。2. 在保护模式时代,段寄存器的大小并没有变化仍然是16位,而偏移地址的“窗口”扩展到了32位。为了达到 2^32 的寻址能力,此时段寄存器(Selector 选择子)的实际作用是查表(GDT表),通过查表得出那被 Intel 为了兼容 80286 而打散的 32位 基址,累加32位偏移地址之后得出线性地址/虚拟地址(2^32 = 4GB 保护模式下段窗口的上限)如果没有开启分页机制的话,那么这个便是物理地址。3. 如果开启了分页机制,那么虚拟地址只是一个中间步骤,两级页表结构(PDE、PTE)最终将虚拟地址映射到物理页上,最简单的情况下页的大小是4KB,这就是 Swap 的基本单位。稍加思索便可以想到 —— 保护模式下的段机制可以实现代码段的区分/隔离,页属性也可以实现同样的功能,那么两套机制没有必要同时存在 —— 操作系统的设计者将段机制透明化(即所谓的 Flat Mode),更何况,基于分页机制还可以实现虚拟内存管理,何乐而不为呢?4. 进程模型和内存模型密不可分。进程模型的发展不是一蹴而就的,它大致经历了“单任务模型”、“协作式多任务模型(这是微软设计的...)”、“抢占式多任务模型”等阶段。单任务模型太Easy;协作式模型太善良,对“坏人”没有办法;抢占式模型才考虑的比较全面。那么,抢占式模型的考虑全面又体现在哪里?重点之一就是提高安全性——强调隔离,进程间不互相干扰,各进程的线程平等调度等等等等。而这一切必须要有CPU硬件厂商的支持才行,让我们看看80386是怎么设计和处理的吧:我们可以从两个角度来分析“多任务”和“保护”之间的关系: 进程管理的角度 && 内存管理的角度。从进程管理的角度看,问题又分成两个方面——(1)多任务之间需要隔离,这一点段机制能够做到,页机制也能够做到。(2)一个任务地址空间内的Kernel Space和User Space之间也需要隔离,两者缺一不可。从内存管理的角度看,这是多任务隔离的延伸。因为多任务的隔离迫切需要物理地址隔离作为前提,如果不隔离物理地址,多进程的实现也是混乱的,干扰的。那怎么办?Intel对地址做了抽象,物理地址对应用程序再也不可见了。应用程序享用4GB线性地址空间的一半(Linux为3:1),此时进程间谁也看不见谁,谁也不知道对方的存在,任务切换时就像是对一个进程(线程)的“瞬间冰冻”,等它再醒来的时候它还是会认为自己在独占着CPU、独占着物理地址空间。5. 再回来看楼主的问题。PE 加载后就是使用自己的 2GB User Space,它根本无法感知(它也不用关心)底下 MM 对内存的实际实现/调度,应用程序可以访问0x0012ff?? 它也可以接着访问 0x7ff????? 至于这两段代码所在的页面在不在物理内存里,根本是它不关心的 —— 唯一的差别就是,在物理内存里的代码执行的快那么一点点(对CPU来说),不在物理内存里的代码执行时引发了 #14,倒页面的过程多花费了一些CPU时间。6. 想分析 Win32 下 Page Fault 的代码实现资料很多,Hook 一下 0x0E,或是调试内核。7. 如果想自己编码实践分页机制,资料也非常的多,下面是一个实验(切换页表对应不同的物理页得出不同的结果——BAR / FOO):
vincent wrote:在一开始的时候就把程序所有的硬盘空间当虚拟地址的页面文件了,不知道这时候算不算在平时说的page files,可是张老师上面却说了,不是!
这种情况使用的是文件映射,不是专用的页交换文件(.e.g. c:\pagefile.sys)。也正是因为这个原因,像Windows这样的操作系统是不可以完全关闭页机制(Paging)的,即使是禁止了专用的页交换文件(在计算机属性>性能>高级>虚拟内存对话框中可以设置)以后。
前面Thomson说的很对,每个进程有一个VAD树,它是把虚拟内存和磁盘上的映射文件关联起来的关键纽带。