地址重定位与内存保护
复习
- 死锁是怎样形成的:四个必要条件与环形等待
- 怎样处理死锁:预防、避免、检测和恢复的取舍
- 地址空间:每个进程都以为自己独占一片连续内存
TL;DR
- 程序在写的时候并不知道自己将来会被装到内存的哪里
- 把它里面的地址调整到实际位置,叫重定位(relocation)
- 用一对基址 + 界限寄存器,可以同时完成重定位和保护
- 越界访问会被硬件拦下,触发异常交给内核
正文
上一章说,每个进程都以为自己独占一片连续内存。可现实是:程序被装进物理内存的哪个位置,事先根本不知道,旁边还挤着别人。
那么问题来了:程序里写的地址,凭什么能对得上真实的物理位置?
两个麻烦
先看看会撞上哪些麻烦:
- 重定位:程序在编译时把地址定在了“从 0 开始”的位置,可它实际可能被装到物理地址 5000 处。所有地址都得跟着挪,否则一执行就访问错地方。
- 保护:就算地址挪对了,也得防止程序越界访问——它凭什么只能碰自己那一段,而不能改隔壁进程甚至内核的数据?
麻烦看似两个,其实可以用一套机制一起解决。
基址加界限
最简单也最经典的做法,是给进程配一对寄存器:
- 基址寄存器:记录这个进程在物理内存里的起始地址
- 界限寄存器:记录这段内存有多长
程序给出的每个虚拟地址,硬件都先做一次检查,再做一次加法:
- 检查:地址是否小于界限?若大于等于界限,说明越界,立刻触发异常,交给内核处理
- 换算:物理地址 = 基址 + 虚拟地址
虚拟地址 v ──[检查 v < 界限]──> 物理地址 = 基址 + v
│
越界则异常
这样一来:
- 重定位:基址会随进程实际被装的位置而变,虚拟地址不用改
- 保护:界限把进程死死框在自己那一段里,越界就被硬件拦下
两个问题,一对寄存器就都办了。
一个能立刻感受到的好处
有了这套机制,进程可以被装到任意位置,也可以被整个挪走再挪回来。程序从头到尾都以为自己在“从 0 开始”的那片空间里,完全不必关心物理内存的排布。
不过,这套“一整段连续内存”的模型还是太粗糙:程序里的代码、数据和栈,需求各不相同,硬塞进一整段并不合适。能不能把它们分开管理?下一章,我们把内存切成“段”。
思考题
如果只有基址寄存器、没有界限寄存器,程序越界访问时会发生什么?界限寄存器究竟保护的是“内存”,还是“进程”?
小结
知识点
- 重定位:把程序里的地址调整到实际装载位置
- 保护:防止程序越界访问
- 基址寄存器给出起始位置,界限寄存器限制长度
- 硬件检查越界并完成地址换算
参考资料
- Wikipedia(zh):重定位:relocation
- Wikipedia(zh):内存保护:memory protection
思考题答案(仅供参考)
如果只有基址寄存器、没有界限寄存器,程序给出的地址照样会被换算成“基址 + 地址”,但它想加多少就加多少——越界的地址会落到别的进程甚至内核的地盘上,等于没有保护。所以界限寄存器保护的不是某一块内存本身,而是进程之间、以及进程与内核之间的隔离:它保证一个进程只能碰自己该碰的那一段。少了它,隔离和稳定就都无从谈起。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪