内存管理概述
内存管理概述
复习定位
内存是程序运行时指令和数据所在的地方。没有内存管理就没有多道程序——因为物理内存不够同时放多个程序。OS的内存管理模块负责:决定每个程序放在物理内存的哪个位置、在多个程序间分配内存、在物理内存不足时将部分数据换出到磁盘、把虚拟地址转换为物理地址。知道这些之后——考试中的页面置换和地址转换的起点来自于此。
存储器的层次结构
计算机系统中的存储器不是单一的——为了平衡速度、容量和价格三个矛盾的要求——构建了一个多级层次:寄存器< L1 Cache< L2< L3<主存(DRAM)< SSD< HDD。每层比上一层慢约2-5倍(寄存器到L1慢约2倍)、容量约大4倍、单位价格便宜约一个数量级。系统自动将当前最频繁访问的数据放在寄存器或L1中——将不常访问的数据放在低速的SSD中。
操作系统在存储管理中主要管理主存(DRAM)和SSD之间——利用按需分页和页面置换算法在DRAM和交换分区之间移动数据。上层的Cache完全由硬件管理——对操作系统透明。
地址空间的概念
物理地址——RAM芯片的硬件地址——CPU通过地址总线直接发送到这个地址读写数据。逻辑地址(在x86保护模式下也叫虚拟地址)——程序自己的代码和数据所在的地址——由CPU的MMU根据页表自动转换为物理地址。
| 地址类型 | 产生时机 | 说明 |
|---|---|---|
| 逻辑地址(虚拟地址) | 编译/链接时 | 程序中的地址: 指令跳转目标、变量地址 |
| 线性地址(分段机制后) | 分段单元转换后 | x86将逻辑地址+段基址→线性地址 |
| 物理地址 | 分页单元转换后 | 最终在内存芯片上寻址 |
从程序角度看——在x86-64 Linux上每个进程有一个巨大的虚拟地址空间(48位地址线——256TB)。进程A的地址0x7fff0000和进程B的地址0x7fff0000是两个完全不同的物理位置——由各自的页表决定映射关系。OS的主要内存管理任务就是维护这个从虚拟地址到物理地址的映射。
地址绑定的阶段
源程序中的符号地址(变量名、函数名)需要被绑定到最终的物理内存地址。绑定可以在以下不同阶段进行:
编译时:如果知道程序将驻留在内存中的固定地址——编译器产生绝对代码。例如在嵌入式bare-metal程序中将起始地址设为0x00010000——所有引用在编译时确定。如果起始地址被占——程序必须重新编译。
加载时:编译时生产可重定位代码(地址相对于0)——加载器将加载地址加上程序中的地址——静态修正。程序加载到不同地址——加载器执行不同的修正。
运行时:进程在执行过程中可能被移动——(如对换重新载入到不同物理地址)——所以运行时必须推迟到CPU在指令执行时才转换地址。这需要MMU硬件支持——CPU在每条指令访问内存时通过动态地址转换完成逻辑→物理地址的映射。
覆盖技术与对换
在早期的OS和大存储占用有限的时代——覆盖技术和交换是一个有效的手段。
覆盖(Overlay):把一个程序按照模块间的调用关系(树型分层)划分——同一层不会互相调用的模块共享同一块内存区域——当模块A运行完后将模块B用从磁盘加载到同一块区域覆盖A。这需要程序员精心设计覆盖结构——开销大——但解决了一个原本装不进512KB内存——现在可以装进64KB内运行的问题。
对换(Swapping):操作系统把整道程序(进程的整个内存映像)换出到磁盘——腾出内存空间给其他需要运行的进程。被换出的进程进入挂起状态——当再次被调度时从磁盘的交换分区加载到合适的内存位置上。这一完整进程交换在早期的分时系统中是主要的内存管理手段——虽然昂贵但使多道程序成为可能。现代OS使用页面交换(以页为单位颗粒度更细)替代了完整进程交换。
复习检查
逻辑地址和物理地址的区别是什么?为什么程序不能直接使用物理地址——物理地址的绝对固定导致多道程序的隔离性怎么保证?
地址绑定的各个时刻(编译时/加载时/运行时)——每种什么时候会被采用——各牵涉哪些开销(重新编译/重新装载/MMU低效率浪费的问题)?
覆盖技术中为什么同层模块可以相互覆盖——如果被覆盖的模块A正在运行但未完成——覆盖B进入一段覆盖了A的内存时——后面为什么不再跳回A继续执行?
对换(Swapping)技术在现代操作系统中为什么被页面交换(paging page)取代——将一个完整的大进程(4GB)swap到磁盘比仅将访问过的1M未用页框换出的时间相差多少数量级?
地址空间(Address Space)对多道程序的意义——两个进程在各自的虚拟地址空间中有相同的指令代码(如ls指令代码复用)但不通过共享内存,物理上就是加载了同一份可执行文件的多个副本页框——OS会在加载时节省这个复制的物理内存吗?