虚拟内存
操作系统引入了虚拟内存,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存
有两种映射方式:
- 内存分段
- 内存分页
内存分段
于是程序可由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段(Segmentation)的形式把这些段分离出来。
因为分段后各段是离散的,为了保证程序能正常运行,就必须能从物理内存中找到各个逻辑段的存放位置,实现这个功能的是段表
段表
- 段表里面保存的是这个段的基地址
- 如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址。
分段的办法很好,优点是解决了程序本身不需要关心具体的物理内存地址的问题,但它缺点是:
- 第一个就是外部内存碎片的问题。
- 第二个就是内存交换的效率低的问题。(硬盘的读写速度比内存慢太多了,每一次内存交换,我们都需要把一大段连续的内存数据写到硬盘上。 )
内存分页
分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,我们叫页(Page)。在 Linux 下,每一页的大小为 4KB。为什么要分页?为了更合理的分配进程在内存中存放
虚拟地址与物理地址之间通过页表来映射
寻址过程:
- 简单来说就是:重定位寄存器(保存着该模块的物理地址)+ 目标的逻辑地址
分页的优势:
采用了分页,那么释放的内存都是以页为单位释放的,也就不会产生无法给进程使用的小内存。
如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为换出(Swap Out)。
一旦需要的时候,再加载进来,称为换入(Swap In)。所以,一次性写入磁盘的也只有少数的一个页或者几个页(不会出现一整个段的磁盘读写),不会花太多时间,内存交换的效率就相对比较高。
这种简单的页表有空间上的缺陷,页表必须连续存放,当页表过大时,需要占用很多连续的页框
多级页表
多级页表是解决页表空间上、以及缓存友好型的缺陷的
两级页表工作原理:将连续存放的页表再进行分级
工作原理:
①按照地址结构将逻辑地址拆分成三部分
②从PCB中读出页目录表始址,再根据一级页号查页目录表,找到下一级页表在内存中的存放位置
③根据二级页号查表,找到最终想访问的内存块号
④结合页内偏移量得到物理地址
- 如果使用了二级分页,一级页表就可以覆盖整个 4GB 虚拟地址空间,但如果某个一级页表的页表项没有被用到(操作系统会将页面换出到硬盘),也就不需要创建这个页表项对应的二级页表了,即可以在用到时才创建二级页表。
TLB(快表)
快表又称联想存储器。
多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道转换的工序,这显然就降低了这俩地址转换的速度,也就是带来了时间上的开销。
根据程序的局部性原理,我们把最常访问的几个页表项存储到访问速度更快的TLB中,与之相对应,内存中的页表称为慢表
- 有了 TLB 后,那么 CPU 在寻址时,会先查 TLB,如果没找到,才会继续查多级页表。
段页式内存管理
内存分段和内存分页组合起来后,就称为段页式内存管理
段页式内存管理实现的方式:
- 先将程序划分为多个有逻辑意义的段(也叫做虚拟内存),也就是分段机制;
- 接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页;
这样,地址结构就由段号、段内页号和页内位移三部分组成。
根据这个地址结构,经过三次内存访问(查段表、查页表、查偏移量),最终找到物理地址。