重生之我学操作系统——第十三、十四、十五章 地址转换
一、进程的地址空间
- OS提供了一个物理内存的抽象:地址空间
- 包含运行程序的所有内存状态
- 代码:存储指令
- 栈(向上增长):局部变量、函数参数、返回值等
- 堆(向下增长):动态分配的内存:malloc、new
- 包含运行程序的所有内存状态
程序并不是真正存储在物理地址的0-16KB的地方
进程看到的都是虚拟地址
二、内存相关API
显式的申请内存:malloc()
void* malloc(size_t size)
- 在堆上申请内存空间
- size参数为申请空间的大小(byte)
- 成功则返回指向分配内存的指针,失败则返回空指针
释放内存:free()
void free(void* ptr)
- 释放调用malloc分配的内存
- 参数为调用malloc返回的指针(释放内存大小不是用户决定)
int *pi; //local variable
pi = (int *)malloc(sizeof(int) * 4)
free(pi)
可能错误:
- 没有申请足够的内存:导致缓冲区溢出(buffer overflow)
- 忘记初始化分配的内存:导致读取未知数据
- 忘记释放内存:导致内存泄露(memory leak)
- 在用完之前释放内存:导致悬挂指针(dangling pointer)
- 错误的调用free:导致无效释放(invalid free)
其他申请内存API:calloc()
void *calloc(size_t num, size_t size)
- 在堆上申请内存空间,并且将其置0
- 可以防止忘记初始化分配的内存
其他申请内存API:realloc()
void *realloc(void *ptr, size_t size)
- 可以增大分配内存空间(当需要添加一些信息)
- 参数ptr传入由malloc, calloc或者realloc分配的内存块指针
- 参数size表示新的内存大小
- 返回值可能与ptr相同,也可能不同
相关系统调用:brk,sbrk,mmap
三、“基址+界限”地址转换
- 如何实现高效、灵活的内存虚拟化?
- 如何提供应用程序所需的灵活性?
- 如何保持控制应用程序可访问的内存位置?
- 如何高效的实现这些?
高效和控制是现代操作系统的两个主要目标, 分别对应性能和可靠性
基于硬件的地址转换(受限直接访问)
- 硬件对内存访问进行处理 (高效)
- 将指令中的虚拟地址(virtual address)转换为实际内存的物理地址 (physical address)
- OS需要在关键位置介入,设置好硬件以完成正确的转换 (控制)
- OS需要记录和管理哪些信息?
假设1:地址空间小于实际物理内存
假设2:假设每个地址空间大小一
- pc=128, 获取指令,地址是
32896 = 128 + 32𝐾𝐵(𝑏𝑎𝑠𝑒)
- 执行指令
- 从虚拟地址15K开始加载,地址是
47𝐾𝐵 =15𝐾𝐵 + 32𝐾𝐵(𝑏𝑎𝑠𝑒)
- 从虚拟地址15K开始加载,地址是
- 硬件需要提供什么支持?
- 特权模式
- 基地址/界限寄存器
- 虚拟地址转换/越界检查
- 注册异常程序
- 触发异常
- OS需要做什么?(三个关键时刻)
- 当进程创建的时候,OS必须寻找内存空间
- 当进程终止的时候,OS必须回收内存空间
- 当进程切换的时候,OS需要保存和恢复基地址和界限
小结
- 进程的地址空间
- 内存虚拟化、(虚拟)地址空间
- 内存相关API
- 用法和可能的问题
- “基地址+界限”地址转换
- 地址转换机制、基址/界限寄存器、OS需要做什么