我迷失在田野里,渐渐忘记了回家的路
几种不同的地址
名称 | 概念 |
---|---|
逻辑地址 | 程序产生的段内偏移,由段选择符和段内偏移组成的地址。由操作系统决定,和物理地址一一对应的地址,C语言中的指针即为逻辑地址 逻辑地址就是段+页偏移 |
线性地址 | 段基址+段内偏移量构成的地址,如果未开启分页,那么线性地址就是物理地址,否则还需二次转换。 线性地址就是段偏移,但是还未进行页转换的地址 虚拟地址就是线性地址(linux内核明确表示) |
物理地址 | 真实物理内存中的地址,即内存地址寄存器中的地址物理地址 |
物理地址
物理地址就是硬件支持的地址空间,起始地址为0,一直到$\textrm{MAX}_{\textrm{sys}}$,如下所示:
每个物理内存单元的物理地址编号是唯一的,给写程序造成了不便,因此我们需要使用逻辑地址或虚拟地址,建立虚拟地址与物理地址之间的映射,从而可以使用相同的虚拟地址指向不同的物理地址
逻辑地址
CPU运行进程时看到的地址,起始地址为0,一直到$\textrm{MAX}_{\textrm{proc}}$
逻辑地址的作用
- 避免直接将物理地址暴露,从而导致访问任意内存程序崩溃
- 可以用相邻的虚拟地址映射到不相邻的物理地址
- 可以使用一系列的虚拟地址访问大于可用物理内存的内存缓冲区(虚拟内存)
- 不同进程间使用虚拟地址相互隔离
例如,如果同一时刻由两个进程P1和P2,那么两者的逻辑地址范围是多少?假设逻辑地址最大为65535。实际上,P1和P2可以有一样范围的地址,都为0-65535,操作系统会确保其逻辑地址指向不同的物理地址。
从这个问题中我们也可以看出,操作系统帮我们屏蔽了底层的物理实现。
逻辑地址的生成过程
生成时机和限制
- 编译时(最不灵活,写死的,但是比较简单)
- 假设起始地址已知
- 如果起始地址改变,必须重新编译
- 加载时(加载程序可以选择加载位置,但是加载好了执行过程中不能变)
- 如编译时起始位置未知,编译器需要生成可重定位的代码
- 加载时生成绝对地址,在可执行文件中有一个重定位表,根据表修改绝对地址
- 执行时(执行过程中可以移动代码,最灵活,需要虚拟地址支持)
- 执行时代码可移动(前两种情况不能随意改动)
- 需要地址转换(映射)硬件支持
地址转换
虚拟地址(线性地址)到物理地址转换步骤
这里讨论一个二级页表情况下VA到PA的转换过程,已知一个虚拟地址为0x01EB6338,CR3寄存器的值为0xAC0F1000,求物理地址。
第一步:写出二进制地址并拆分
虚拟地址的二进制为:$0000,0001 ,1110 ,1011 ,0110 ,0011 ,0011 ,1000$
按照线性地址2级分页进行划分,地址可以被拆分为:
- Dir:0000,0001,11
- Table:10 ,1011 ,0110
- Offset:0011 ,0011 ,1000
换算成十六进制可得到如下结果:
- 页目录索引:7
- 页表索引:0x2B6
- 偏移量:0x338
第二步:根据CR3寄存器的物理地址确定页目录表的基地址
CR3中存放的物理地址就是页目录表的基地址
第三步:计算页表项的地址
页表地址存放在Page Directory Entry的第7项中,即
1 | [0xAC0F1000 + 4 * 7] = [0xAC0F101C] = 0xAC0F101C: pte |
0xAC0F101C这个地址保存了pte的值,假设这个值为0x2F103841,那么其中PTE为0x2F103000,而页表属性为0x381。
第四步:计算页面的物理地址
页表的地址为0x2F103000,存放在第0x2B6项,所以虚拟地址所在页的物理地址为:
1 | [0x2F103000 + 0x2B6 * 4] = [0x2F103AD8] = 0x2F103AD8: pa |
这里得到的pa是未经偏移的物理地址,假设为0x7095e847,那么页面物理地址为0x7095E000,属性为0x847
最后一步:计算偏移量
最终物理地址为0x7095E000+0x338 = 7095E338
地址生成过程的硬件解释
CPU与内存连接的结构图如上图所示,我们从一条指令执行过程出发,分析虚拟地址到物理地址的转换过程。
- CPU当前正在执行一条指令
- ALU:需要逻辑地址指向的内存内容
- MMU:根据页表进行逻辑到物理的转换
- CPU控制器:给总线发送物理地址请求
- 内存
- 根据控制信号是读还是写,向总线发送或接收内容
- 操作系统
- 建立逻辑地址LA到物理地址PA的映射,地址转换是MMU硬件完成的,而操作系统只负责提供映射表
寻址方式
现代CPU采用虚拟寻址的方式,需要将虚拟地址翻译成物理地址,这样才能访问真实的物理内存。完成虚拟地址转换为物理地址的的硬件是CPU中的MMU。转换过程为:
其中,C语言中的指针就是虚拟地址中的Offset部分,如果关闭了段选,那么线性地址就是虚拟地址。更具体的带有多级页表的转换方式如下:
地址检查
在地址转换过程中,操作系统会对地址进行检查。例如在段选过程中,操作系统会判断逻辑地址是否大于段长度寄存器,如果是,那么会触发内存异常。如果不是,那么地址会和段基址寄存器相加,生成线性地址。操作系统负责设置段长度和段基址。
分级
上面的线性地址被分为了两级,分别是:
- Dir
- Table
Page Table Entry
PDE与PTE
PDE与PTE的大小都为32位,具体格式如下:
PDE(页目录表)
MMU通过%cr3
寄存器获得PD位置,即完成不同进程之间PD的切换
PTE(页表)
PTE由Physical Page Number(20-bit)和flags(12-bit)组成,其中:
- Physical Page Number = PPN,根据这个值可以找到对应的物理内存页的基地址
- flag记录了该页的控制信息(是否存在、能否写以及能否被进程使用等)
MMU将会把这20位的虚拟地址替换为PTE中的物理地址。PTE由MMU加载并存储在内存中。
标志位解析
标志位 | 全称 | 功能 |
---|---|---|
P | Present | 对于大多数操作系统,如果present为0,代表PDE或PTE数据无效 |
W | Writable | 内存是否可写 |
U(PTE_U) | User | 用户是否可访问 |