Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

地址重定位与内存保护

复习

  • 死锁是怎样形成的:四个必要条件与环形等待
  • 怎样处理死锁:预防、避免、检测和恢复的取舍
  • 地址空间:每个进程都以为自己独占一片连续内存

TL;DR

  • 程序在写的时候并不知道自己将来会被装到内存的哪里
  • 把它里面的地址调整到实际位置,叫重定位(relocation)
  • 用一对基址 + 界限寄存器,可以同时完成重定位和保护
  • 越界访问会被硬件拦下,触发异常交给内核

正文

  上一章说,每个进程都以为自己独占一片连续内存。可现实是:程序被装进物理内存的哪个位置,事先根本不知道,旁边还挤着别人。

  那么问题来了:程序里写的地址,凭什么能对得上真实的物理位置?

两个麻烦

  先看看会撞上哪些麻烦:

  • 重定位:程序在编译时把地址定在了“从 0 开始”的位置,可它实际可能被装到物理地址 5000 处。所有地址都得跟着挪,否则一执行就访问错地方。
  • 保护:就算地址挪对了,也得防止程序越界访问——它凭什么只能碰自己那一段,而不能改隔壁进程甚至内核的数据?

  麻烦看似两个,其实可以用一套机制一起解决。

基址加界限

  最简单也最经典的做法,是给进程配一对寄存器:

  • 基址寄存器:记录这个进程在物理内存里的起始地址
  • 界限寄存器:记录这段内存有多长

  程序给出的每个虚拟地址,硬件都先做一次检查,再做一次加法:

  1. 检查:地址是否小于界限?若大于等于界限,说明越界,立刻触发异常,交给内核处理
  2. 换算:物理地址 = 基址 + 虚拟地址
虚拟地址 v ──[检查 v < 界限]──> 物理地址 = 基址 + v
                     │
                  越界则异常

  这样一来:

  • 重定位:基址会随进程实际被装的位置而变,虚拟地址不用改
  • 保护:界限把进程死死框在自己那一段里,越界就被硬件拦下

  两个问题,一对寄存器就都办了。

一个能立刻感受到的好处

  有了这套机制,进程可以被装到任意位置,也可以被整个挪走再挪回来。程序从头到尾都以为自己在“从 0 开始”的那片空间里,完全不必关心物理内存的排布。

  不过,这套“一整段连续内存”的模型还是太粗糙:程序里的代码、数据和栈,需求各不相同,硬塞进一整段并不合适。能不能把它们分开管理?下一章,我们把内存切成“段”。

思考题

  如果只有基址寄存器、没有界限寄存器,程序越界访问时会发生什么?界限寄存器究竟保护的是“内存”,还是“进程”?

小结

知识点

  • 重定位:把程序里的地址调整到实际装载位置
  • 保护:防止程序越界访问
  • 基址寄存器给出起始位置,界限寄存器限制长度
  • 硬件检查越界并完成地址换算

参考资料

  1. Wikipedia(zh):重定位:relocation
  2. Wikipedia(zh):内存保护:memory protection

思考题答案(仅供参考)

  如果只有基址寄存器、没有界限寄存器,程序给出的地址照样会被换算成“基址 + 地址”,但它想加多少就加多少——越界的地址会落到别的进程甚至内核的地盘上,等于没有保护。所以界限寄存器保护的不是某一块内存本身,而是进程之间、以及进程与内核之间的隔离:它保证一个进程只能碰自己该碰的那一段。少了它,隔离和稳定就都无从谈起。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪