0. 概述
linux0.11对内存的管理是段页式的,所以其地址映射过程是将逻辑地址先通过分段机制变换得到线性地址,然后再通过分页机制变换为实际的物理地址。
1. 分段
linux除了启动时运行在实模式下,其他时间运行在保护模式下。
保护模式下寻址和实模式有所不同:
- 实模式即DS:SI的形式去寻址。
- 保护模式通过段选择子的方式来寻址。(计算机科学中经典的加一层设计)
- 在保护模式下,段寄存器中存放的是一个段描述符表(Segment Descriptor Table)中某描述符项在表中的索引值。索引值指定的段描述符项中含有需要寻址的内存段的基地址、段的长度值和段的访问特权级别等信息。
在Linux0.11中,程序逻辑地址到线性地址的变换过程使用了CPU的全局段描述符表GDT和局部段描述符表LDT。
由GDT映射的地址空间称为全局地址空间,由LDT映射的地址空间则称为局部地址空间,而这两者构成了虚拟地址的空间。
2. 分页
采用多级页表的形式进行分页。
控制寄存器CR3保存着是当前页目录表在物理内存中的基地址(因此CR3也被称为页目录基地址寄存器PDBR)。
32位的线性地址被分成三个部分:
- 页目录表中偏移。
- 页表中偏移。
- 页内偏移。
4. 例子
以下边这段程序为例子,跟踪 i
的地址翻译过程。(即从逻辑地址怎么映射到物理地址的)
#include <stdio.h>
int i = 0x12345678;
int main(void)
{
printf("The logical/virtual address of i is 0x%08x", &i);
fflush(stdout);
while (i)
;
return 0;
}
运行以上程序,结果为:
The logical/virtual address of i is 0x00003004
可知 i
的逻辑地址为0x00003004。
4.1 分段(逻辑地址 => 线性地址)
ctrl+c
使Bochs暂停运行,进入调试状态:
(0) [0x000000fa7063] 000f:00000063 (unk. ctxt): cmp dword ptr ds:0x00003004, 0x00000000 ; 833d0430000000
ds:0x3004是虚拟地址,ds表明这个地址属于ds段。
- 在GDT(全局描述符表)中找到LDT的基地址
<bochs:49> sreg
es:0x0017, dh=0x10c0f300, dl=0x00003fff, valid=1
Data segment, base=0x10000000, limit=0x03ffffff, Read/Write, Accessed
cs:0x000f, dh=0x10c0fb00, dl=0x00000002, valid=1
Code segment, base=0x10000000, limit=0x00002fff, Execute/Read, Non-Conforming, Accessed, 32-bit
ss:0x0017, dh=0x10c0f300, dl=0x00003fff, valid=1
Data segment, base=0x10000000, limit=0x03ffffff, Read/Write, Accessed
ds:0x0017, dh=0x10c0f300, dl=0x00003fff, valid=3
Data segment, base=0x10000000, limit=0x03ffffff, Read/Write, Accessed
fs:0x0017, dh=0x10c0f300, dl=0x00003fff, valid=1
Data segment, base=0x10000000, limit=0x03ffffff, Read/Write, Accessed
gs:0x0017, dh=0x10c0f300, dl=0x00003fff, valid=1
Data segment, base=0x10000000, limit=0x03ffffff, Read/Write, Accessed
ldtr:0x0068, dh=0x000082fa, dl=0xa2d00068, valid=1
tr:0x0060, dh=0x00008bfa, dl=0xa2e80068, valid=1
gdtr:base=0x00005cb8, limit=0x7ff
idtr:base=0x000054b8, limit=0x7ff
GDT的位置由gdtr明确给出,在物理地址的0x00005cb8。
ldtr是GDT的段选择子。0x0068=0000000001101000(二进制),表示LDT表存放在GDT表的1101(二进制)=13(十进制)号位置。
<bochs:50> xp /2w 0x5cb8 + 13 * 8
[bochs]:
0x00005d20 <bogus+ 0>: 0xa2d00068 0x000082fa
段描述符定义如下:
组合出LDT的基地址:0x00faa2d0
- 在LDT中找到段描述符,将逻辑地址映射为线性地址
查看LDT:
<bochs:51> xp /8w 0xfaa2d0
[bochs]:
0x00faa2d0 <bogus+ 0>: 0x00000000 0x00000000 0x00000002 0x10c0fb00
0x00faa2e0 <bogus+ 16>: 0x00003fff 0x10c0f300 0x00000000 0x00fab000
根据ds为0x0017(0x0017=0000000000010111(二进制)),由段选择子的概念,得出是LDT中的第10(二进制)项。
LDT[2]的内容为0x00003fff 0x10c0f300,根据段描述符的概念可知:ds所代表的段的基址为0x10000000;
由:线性地址 = 段基址 + 段内偏移
所以ds:0x3004的线性地址就是:0x10000000 + 0x3004 = 0x10003004。
4.2 分页(线性地址 => 物理地址)
从线性地址计算物理地址,需要查找页表。线性地址变成物理地址的过程如下:
0x10003004的页目录号是64,页号3,页内偏移是4。
IA-32下,页目录表的位置由CR3寄存器指引。查看CR3寄存器内容。
<bochs:52> creg
CR0=0x8000001b: PG cd nw ac wp ne ET TS em MP PE
CR2=page fault laddr=0x10002fac
CR3=0x000000000000
PCD=page-level cache disable=0
PWT=page-level write-through=0
CR4=0x00000000: pke smap smep osxsave pcid fsgsbase smx vmx osxmmexcpt umip osfxsr pce pge mce pae pse de tsd pvi vme
EFER=0x00000000: ffxsr nxe lma lme sce
由上可知:页目录表的起始地址(CR3)为0x000000000000。
页目录表中的第64项为:
<bochs:54> xp /w 0 + 64 * 4
[bochs]:
0x00000100 <bogus+ 0>: 0x00fa3027
‘
所以页表所在物理页框号为0x00fa3,即页表在物理内存的0x00fa3000位置。
查看页表0x00fa3000的第三页,有
<bochs:57> xp /w 0x00fa3000 + 3 * 4
[bochs]:
0x00fa300c <bogus+ 0>: 0x00f9c067
组合上页内偏移,物理地址为0x00f9c004。
验证下:
<bochs:58> xp /w 0x00f9c004
[bochs]:
0x00f9c004 <bogus+ 0>: 0x12345678
5. 总结
参考资料
[1] linux内核完全注释
[2] 哈工大操作系统实验指导书