逻辑/线性/物理地址
定义
-
逻辑地址
- 是一个程序所看到和使用的地址空间,是逻辑意义上的存在,一般用段内偏移量表示
- 需要经过
分段转换- 实模式下:段基地址 + 段内偏移
- 保护模式下:段选择符 + 段内偏移
- 本质上还是运用了基地址,这样不同程序的代码运行不会冲突
-
线性地址
- 逻辑地址经过
分段转换,就变成线性地址,是平坦的 - 如果不使用
分页,那么这就是物理地址
- 逻辑地址经过
-
物理地址
- 如果使用了分页,那线性地址要经过
分页转换,才会最终变成物理地址
- 如果使用了分页,那线性地址要经过
分页
-
【问题】分段在保护模式中解释过了,那分页又是什么?
- 分页就是指: 按页为单位进行映射。
- 对于连续的线性地址可以映射到不连续的物理内存上。
-
存在分页时,线性地址不能直接到物理内存中找,而是要先经过如下路径
-
处理线性地址,划分为-
高10位
- 准备用来生成
页目录的单项地址偏移,进入下一步
- 准备用来生成
-
中间10位
- 准备用来生成
页表的单项地址偏移,进入下下步
- 准备用来生成
-
低12位
- 终极偏移量,留到最后使用
- 由于2 ^ 12 = 4KB
- 所以可用来在某个4KB的页里面准确定位
-
【问】这里为什么是10位作为偏移?
-
【答】10位如果左移2位,就能生成12位的真正偏移地址,地址跨度是4个字节,所以后面每一项的大小为4字节
-
-
转战到
页目录-
要先从
CR3寄存器拿到基地址- 取高20位作为
页目录的高位拼接地址
- 取高20位作为
-
页目录总共有 2 ^ 10 = 1024 项
-
每一项大小为4字节32位,取出
- 高20位
- 作为
页表的高位拼接地址,进入下一步
- 作为
- 高20位
-
所以页目录总占用空间为 1024 * 32bit = 4KB
-
【问】这里为什么是高20位作为高位拼接地址?
-
【答】高20位在左边,右边再拼接上一步的12位真正偏移地址,刚好就能组成32位地址(其实这种拼接 等价于 “完整的32位”与“12位” 做一次加法运算)
-
-
转战到
页表- 上一步已经拿到
页表的高位拼接地址 - 页表也是有 2 ^ 10 = 1024 项
- 每一项大小也是4字节32位,取出
- 高20位
- 作为
页的高位拼接地址,后面接上12位的终极偏移量,到物理内存中寻找页
- 作为
- 高20位
- 所以页表总占用空间为 1024 * 32bit = 4KB
- 上一步已经拿到
-
这么说来,页目录和页表也是页(大小为4KB)
-
总结下来,1024项的页目录,每一项分别指向一个1024项的页表,每一项页表可以指定20位的高位拼接地址(也就是物理内存中的单个页的起始地址)
【猜想】这里的门道可能很大,因为可以让不同的线性地址指向同一个物理地址,2(2 ^ 10) * (2 ^ 10) 能得出 2 ^ 20个坑位(即1048576个页面),而这些坑位可以拿物理内存中的20位高位拼接地址去随便填,甚至是重复?
【猜想2】同样,不同程序中同一个虚拟地址也可以指向不同的物理地址?
查了下资料,通过改变段寄存器实现,貌似在分段生成线性地址时有GDTR和LDTR这两个概念
例子:在linux内核中,就有四种段选择器,分别是
_USER_CS 用户态的代码
_USER_DS 用户态的数据
_KERNEL_CS 内核态的代码
_KERNEL_DS 内核态的数据
代表用户态和内核态,可以用同样的地址,二者会被分段机制分配到不同的线性地址上
由于大部分Linux用户态程序不使用LDT局部描述符表,所以内核定义一个默认的LDT供大部分进程共用,然而,在某些情况下,进程可能需要设置它们自己的LDT