跳过正文

存储器层次结构(三):OS与软硬件界面

·121 字·1 分钟
Banson
作者
Banson
计算机系统分层

简而言之,我们在(二)中介绍的是红框内部的工作原理,即一颗 CPU 核心是如何通过各种硬件机制与缓存或主存交互的。但是在这一篇中,我们会把它们当作一种整体(它们向软件提供了存取数据的功能),讨论在软件侧还做了什么与存储器相关的工作。

很遗憾,正如 OSI 七层网络模型的命运一样1,在这里也不存在严格的分层、抽象、隔离。总要有一些 dirty work 需要做。操作系统就是这样一个角色:一方面,它在软硬件中属于软件(系统软件怎么不是软件呢),是 CPU 的用户;但另一方面,它又看到了过多硬件底层的细节,例如 TLB、缺页等等。这些内容是本文讨论的重点。

虚拟内存与地址空间
#

在现代计算机系统中,虚拟内存是操作系统提供的最基础且最重要的抽象之一。但在虚拟内存普及之前,操作系统对内存的管理远没有如今这样灵活和安全。

不安全内存访问的问题
#

早期的个人计算机(如 8086、80286 时代)运行在实模式(real mode)下。实模式下,CPU 直接使用物理地址访问内存,所有程序看到的都是同一片物理空间,没有任何隔离。此时,操作系统几乎不参与内存管理,程序之间完全暴露彼此的内存内容。

早期的 MS-DOS、Windows 3.x 等操作系统就是运行在实模式或类似的分段保护模式下。由于缺乏隔离和保护,程序一旦越界读写,极易破坏操作系统或其他程序的数据,导致系统崩溃(蓝屏、死机等现象非常常见)。

虚拟内存的魔法
#

虚拟内存像是“每个学生的私人自习室”。你走进图书馆,发现每个人都在安静地学习,桌面上摊着各自的书本,彼此互不干扰。神奇的是,这些自习室其实都是同一个大厅,只不过每个人眼里看到的布局完全不同——这就是虚拟内存的魔法。

想象一下,如果所有进程都直接看到物理内存,就像所有学生只能在同一张大桌子上学习,会发生什么?一本书被 A 同学拿走,B 同学就找不到了;C 同学随手画了个小人,D 同学以为是自己写的笔记……混乱不堪。

虚拟内存的本质,就是给每个进程分配一套“看起来独立”的内存空间。进程 A 以为自己独占 0x00000000 到 0xFFFFFFFF,进程 B 也这么以为。实际上,他们都只是在各自的“幻觉”里快乐学习,操作系统则在背后悄悄地把这些虚拟地址翻译成真实的物理地址,保证互不干扰。

简单来说,这就是分页(简单起见,我们不管分段了)。物理内存被切成若干大小相同的块,每一块被称为一个物理页。对于应用程序,它认为自己可以访问 0x00000000 到 0xFFFFFFFF 范围内的每一页,并且看起来是连续的。但其实每个虚拟页都可能对应物理内存中的任何一个物理页。操作系统通过这种映射提供了“连续且独占”的假象。

页表
#

既然大家都活在自己的虚拟世界里,CPU 怎么知道到底该去物理内存的哪一块找数据?这就要靠页表(Page Table)了。

页表就像一份“虚拟地址到物理地址”的翻译字典。每当 CPU 访问内存,实际上是先拿着虚拟地址去查字典,找到对应的物理地址,再去实际的内存里取数据。这个过程大致如下:

  1. 虚拟地址被分为两部分:高位是页号(Page Number),低位是页内偏移(Offset)。
  2. 页号用来在页表中查找,找到对应的物理页框号(Frame Number),可以理解为某页的起始地址。
  3. 物理地址 = 物理页框号 + 页内偏移。

举个例子,假如虚拟地址是 0x12345678,查页表发现它应该对应物理地址 0xABCDEF78,CPU 就会老老实实去 0xABCDEF78 取数据。

页表是由操作系统维护的。进程切换时发生了什么,我们以后再聊,现在只关注一些宏观概念吧。

TLB:翻译小助手
#

还记得前面说的“页表”吗?它是虚拟地址和物理地址之间的“翻译字典”。但问题来了:每次查字典都得翻厚厚的一本书,效率就太低了。

于是,工程师们发明了 TLB(Translation Lookaside Buffer,转换后援缓冲区)。你可以把它想象成“随身小抄”——把最近常用的几页翻译结果抄在一张小纸条上,随时放在手边。下次再遇到同样的虚拟地址,直接看小抄,省时省力。TLB 本质上就是页表专用的 Cache,因此:

  • 命中(Hit):如果翻译结果就在小抄上,CPU 直接拿来用。
  • 未命中(Miss):如果没找到,才去查页表,然后再把新结果抄到小抄上,准备下次用。

TLB 容量有限(毕竟小抄不能太大),但因为程序的时间局部性,所以大部分时候都能命中。只有偶尔需要查新内容时,才会慢一点。

缺页:你要的书不在架上
#

再来讲一个经典场景:你在图书馆自习,查阅某本书。你以为这本书就在书架上,结果一摸,空的!原来这本书被管理员暂时收回仓库了。这时候,你只能举手叫管理员帮你去仓库把书找出来,这就需要等一会儿。

在计算机里,这种情况就叫缺页(Page Fault)。当程序访问一个虚拟地址,发现对应的物理页面根本没在内存里(可能还在硬盘上,或者根本没分配),CPU 就会发出一个“缺页中断”,把控制权交给操作系统。

更准确地说,操作系统普遍采用一种懒加载的策略。当进程申请分配内存时,操作系统仅仅是在页表中做了一个标记就返回了。这样,等到实际访问该页面时,CPU 就会发现它并不存在物理页,此时再实际分配。又如进程 fork 时对内存的 CoW 机制也是类似,不过在这里就不展开。

操作系统收到通知后,会:

  1. 判断这个页面应该在哪(比如在磁盘的某个地方)。
  2. 把它从磁盘读到内存里。
  3. 更新页表,让虚拟地址指向新的物理位置。
  4. 让程序继续运行,好像什么都没发生过。

缺页处理是慢动作,但对用户来说,这一切都是自动完成的。你只会感觉有时候程序“卡了一下”,其实是后台在搬东西。

Swap:临时把书借出去
#

物理内存就像图书馆的书架,空间有限。万一你要看的书太多,书架放不下怎么办?管理员会把暂时不用的书打包送到仓库(硬盘)里,等你需要时再搬回来。这就是Swap,即“换出”和“换入”。

  • 换出:当内存不够用时,操作系统会把一些“近期没人看的页面”存到硬盘上,腾出空间给新页面。
  • 换入:当你又需要这些页面时,再从硬盘搬回内存。

Swap 让你可以“同时”运行很多程序,哪怕物理内存不够用。但代价就是——硬盘速度远慢于内存,频繁 swap 也被称为换页风暴,会让用户体验变卡。

操作系统会尽量聪明地选择哪些页面该 swap out,比如很久没人用的、后台程序的数据等,这涉及到一些类似 LRU 的算法。


  1. 良好的分层和抽象之间总有很多后门,例如 ARP 协议。 ↩︎

相关文章