1. 虚拟内存

对于操作系统的使用者而言,内存就像是一个一排排按照从0到n被编好数字的收纳柜,每个柜子可以存放8个bit,也就是一个字节,我们将需要存放的信息切成若干个字节,放到连续的柜子中。以后我们只需要知道所使用的第一个柜子的编号,以及所用的柜子的数目,就可以完整的取出我们所保存的数据。

知道地址就可以找到我们要的数据,然后从内存读到处理器就可以进行操作了,整个流程如下所示

Linux虚拟内存与线性地址翻译_物理地址

但问题真的这么简单吗?当然不可能,假设以下情况

  • 情景1:假如我调用一个软件为进程A,在内存0x11111111处开始存了若干个字节的字符串,表示的是我学习资料的种子链接,还没来得及写回到硬盘中,这时我打开了另一个软件,设为进程B,不小心对内存0x11111111出的信息进行了修改,最后当内存中信息写回硬盘时,我发现我的的学习资料已经无了。
  • 情景2:我的电脑只有4G内存,其中2G运行着系统内核,如果一个应用程序占用1G内存,那么我一次只能使用2个程序,如果我想一边打开浏览器一边写代码一边听音乐就无法做到了。

为了更好的使用内存,人们引用了虚拟内存,虚拟内存的作用可以总结为3点:

  • 虚拟内存可以利用物理内存起到缓存的作用,提高进程访问磁盘的速度,虚拟内存=物理内存+磁盘;
  • 虚拟内存可以为进程提供独立的内存空间,让每个进程都感觉自己在独享物理内存,简化程序的链接、加载过程并通过动态库共享内存;
  • 虚拟内存保证了每个进程的地址空间与其他进程相隔离,保证了安全性;

实际上的过程是这样的:


Linux虚拟内存与线性地址翻译_操作系统_02

处理器CPU发出的虚拟地址通过MMU的翻译生成PTEA(页表项地址),通过页表查找获取物理地址PA,通过物理地址再到高速缓存或者内存中获取数据;如果如果PTE的有效位是0,则表示该页已失效,系统会触发缺页中断,主存将从磁盘中读取新页,最终将数据返回。

2. MMU虚拟内存翻译

以Intel i7core内存系统在为例


Linux虚拟内存与线性地址翻译_虚拟内存_03

现在来介绍一些名词

  • 物理内存(physical memory),主存RAM,实际能使用的物理空间。
  • 物理页(physical page),把物理内存按照页表的大小进行划分,一般大小为4k,aka.页框。
  • 物理地址(physicaladdress,PA), 物理内存划分了根据物理页划分为很多块,通过物理地址进行定位。
  • 物理页号(physicalpage number,PPN) ,定位缓存中的数据字。
  • 物理页号偏移(physicalpage offset, PPO),定位缓存中的数据块。
  • 物理地址 PA = PPN + PPO
  • 虚拟内存(virtual memory),每个程序独有,由多个虚拟页(VP, virtual page)组成。
  • 虚拟内存的地址编码称虚拟地址空间(virtual address space VAS),跟物理内存一样,但虚拟内存是每个进程独有的,其大小是根据操作系统的指令集位有关,如32位,64位,32位,每个进程就有4G,64位有个百亿的GB。
  • 虚拟页(virtual page,VP ),把虚拟内存按照页表的大小进行划分,虚拟页的数目=物理页的数目+磁盘中页的数目。
  • 虚拟地址(virtual address),通俗说是计算机进程加载地址的指令,进程给的虚拟地址通过MMU进行获取地址计算物理地址空间,然后获取物理地址对应的数据传送到CPU上。
  • 虚拟页号(virtual page number ,VPN),用于定位页表的PTE(页表项)。
  • 虚拟页号偏移(virtual page offset VPO),跟PPO值一样。
  • 虚拟地址 VA = VPN + VPO
  • 虚拟页号VPN = TLBT + TLBI
  • TLB索引(TLB index,TLBI),在TLB中作为组索引。
  • TLBT标记(TLB tag,TLBT),在TLB中作为行匹配。
  • 缓存标记(cache tag,CT),在高速缓存中作为行匹配。
  • 缓存索引(cache index,CI),在高速缓存中作为组索引。
  • 缓存偏移(cache offset,CO),在高速缓存中用作行内偏移来选择目的数据块。
  • 页表(page tables,PT),虚拟地址与物理地址的对应表集合,存放在主存中。
  • 页表条目(page table entry,PTE),虚拟地址与物理地址具体对应记录。页表是由多个页表条目PTE组成的数组,PTE 由一个有效位 和 n位地址字段组成,如果设置了有效位,那么地址字段就标识DRAM中相应的物理页的起始位置。

其地址翻译流程图如下所示:

Linux虚拟内存与线性地址翻译_物理地址_04

按照从快到慢、从正常到异常的顺序分别介绍几种可能的通路

首先通过虚拟地址VA获取物理地址PA:

  1. 处理器产生一个48位的虚拟地址VA,由36位VPN和12位VPO组成
  2. VPN低4位作为快表索引(TLBI),高32位作为标记(TLBT),在TLB(快表,高速缓存)中进行查找
    (1)若TLB命中,则可获得PPN,PPN + PPO(PPO == VPO) = PA,即可的到物理地址,跳转至步骤4
    (2)若TLB未命中,跳转至步骤3
  3. 在多级页表中查询最终物理地址的PPN,每一级页表项PTE = PPN + Flages + 保留部分。控制寄存器CR3 用于保存第一级页表的物理地址,因此被称为PTBR,获取到一级页表的物理地址后,据VPN1做偏移量查找到对应的页表项,根据该页表项中的PPN到物理内存中获取下一级页表首地址,再根据VPN2查找到对于的页表项,获取更下一级页表的物理地址,直到第四级页表,通过VPN4获取第四级页表中的PTE,从而得到最终物理地址的PPN,PA = PPN + PPO(PPO == VPO) ,进而得到物理地址,多级页表结构如下:
    Linux虚拟内存与线性地址翻译_mmu_05

然后通过物理地址PA获取数据:

  1. 根据PA = CT + CI + CO,在L1高速缓存中查找对应数据
    (1)若缓存命中,直接返回数据
    (2)若缓存不命中,则跳转至步骤5
  2. 根据PA从主存中获取物理页
    (1)该物理页有效,直接根据PPO偏移获取数据返回
    (2)该物理页无效,跳转至步骤6
  3. 当操作系统发现是一个缺页中断时,查找出来发生页面中断的虚拟页(进程地址空间中的页面)。这个虚拟页的信息通常会保存在一个硬件寄存器中,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析该指令,通过分析找出发生页面中断的虚拟页面。
    (1)检查虚拟地址的有效性及安全保护位。如果发生保护错误,则杀死该进程。
    (2)操作系统查找一个空闲的物理页,如果没有空闲的物理页则需要通过页面置换算法找到一个需要换出的物理页。
    (3)如果找的物理页中的内容被修改了,则需要将修改的内容保存到磁盘上,此时会引起一个写磁盘调用,发生上下文切换(在等待磁盘写的过程中让其它进程运行)。(注:此时需要将物理页置为忙状态,以防物理页被其它进程抢占掉)
    (4)物理页干净后,操作系统根据虚拟地址对应磁盘上的位置,将保持在磁盘上的页面内容复制到“干净”的物理页中,此时会引起一个读磁盘调用,发生上下文切换。
    (5)当磁盘中的页面内容全部装入物理页后,向操作系统发送一个中断。操作系统更新内存中的页表项,将虚拟页面映射的物理页号PPN更新为写入的物理页,并将物理页标记为正常状态。
    (6)恢复缺页中断发生前的状态,将程序指令器重新指向引起缺页中断的指令。
    (7)调度引起页面中断的进程,操作系统返回汇编代码例程。
    (8)汇编代码例程恢复现场,将之前保存在通用寄存器中的信息恢复。
  4. 将虚拟地址重新映射到新的物理地址,获取物理页,根据PPO偏移获取并返回数据。

3. 虚拟内存实际分配

在32位的机器上,地址空间从​​0x00000000~ 0xFFFFFFFF​​​,总大小为4GB。一般而言,低地址空间,从​​0x00000000~ 0x7FFFFFFF​​使用户空间,高地址空间被分配给系统。

内存管理
每个进程都拥有自己的4G(32位机)虚拟内存地址,各个进程之间是相互独立的,每个进程的数据可由其中线程共享。虚拟内存地址本身不对应任何物理地址,直接引用会引发错误,虚拟内存地址必须映射物理地址后才能储存数据。内存分配其实指的是虚拟内存地址映射物理内存,内存回收就是指解除映射关系。

一个进程的内存分配

一个进程的虚拟内存如下所示:


Linux虚拟内存与线性地址翻译_虚拟内存_06

本文参考 :

《深入理解计算机系统》

缺页中断处理过程