让每个进程以为自己拥有一片连续内存,同时不能破坏内核和其他进程。
| 为什么失败 | 被迫设计 | 目标 |
|---|---|---|
| 内存只是地址到字节,不知道归属 | 地址空间 | 每个进程有自己的地址视图 |
| 虚拟地址需要落到物理内存 | 页表 | 记录虚拟页到物理页的映射 |
| 有些地址不能读写执行 | 页权限 | 把读、写、执行、用户态访问分开 |
| 非法访问不能拖垮系统 | 异常处理 | 杀掉坏进程或返回错误,内核继续运行 |
| 区域 | 放什么 | 权限 | 为什么 |
|---|---|---|---|
| text | 程序代码 | R / X | 代码可执行,但不应被普通写入修改 |
| data | 全局变量 | R / W | 程序状态需要读写 |
| heap | 动态分配内存 | R / W | malloc / new 需要增长空间 |
| stack | 调用栈 | R / W | 函数调用和局部变量需要栈 |
| kernel mapping | 内核代码和数据 | 仅内核态 | 系统调用和异常处理需要进入内核,但用户不能访问 |
| 对象 | 字段 | 负责什么 |
|---|---|---|
AddressSpace | page_table、regions | 一个进程看到的虚拟内存世界 |
PageTable | virtual_page、physical_page、flags | 地址翻译和权限检查 |
MemoryRegion | start、end、type、permissions | 记录 text / data / heap / stack 的边界 |
PageFault | address、reason、process、mode | 解释为什么访问失败 |
FrameAllocator | free_pages、used_pages、reserved_pages | 分配和回收物理页 |
| 问题 | 第一版选择 | 代价 |
|---|---|---|
| 是否每个进程独立页表 | 是 | 切换进程要切换页表,但隔离清楚 |
| 是否映射内核空间 | 所有进程页表保留内核映射,但用户不可访问 | 系统调用快,但权限必须严格 |
| 是否支持懒加载 | 第二版再做 | 第一版先完整加载程序,简单可靠 |
| 是否支持 swap | 不做 | 先不把内存管理扩成性能工程 |
| 实验 | 通过标准 | 证明了什么 |
|---|---|---|
| 互相隔离 | 程序 A 不能读取程序 B 的地址 | 进程地址空间独立 |
| 内核保护 | 用户程序写内核地址触发异常,系统继续运行 | 用户态 / 内核态边界成立 |
| 权限保护 | 写 text 段失败,执行 data 段失败 | R / W / X 权限有效 |
| 栈增长 | 合法栈增长可分配新页,越界增长被拒绝 | 异常处理不是只有杀进程,也能按规则补页 |
| 内存耗尽 | 物理页不够时返回错误,不破坏页表 | 资源失败路径可控 |