5)模式转换
模式转换
接下来需要从现在的 16 位的实模式转变为之后 32 位的保护模式
保护模式的地址计算
1 |
|
16位实模式下地址的计算方式是段基址左移四位,再加上偏移地址
当 CPU 切换到保护模式后,同样的代码,内存地址的计算方式就不一样了
ds 寄存器里存储的值,在实模式下叫做段基址,在保护模式下叫段选择子。段选择子里存储着段描述符的索引
通过段描述符索引,可以从全局描述符表 gdt 中找到一个段描述符,段描述符里存储着段基址。
段基址取出来,再和偏移地址相加,就得到了物理地址(准确说是线性地址,再经过分页转换后才是物理地址),整个过程如下:
总结一下就是,段寄存器(比如 ds、ss、cs)里存储的是段选择子,段选择子去全局描述符表中寻找段描述符,从中取出段基址。然后再加上偏移地址,就得到了最终的物理地址。
全局描述符表 gdt
操作系统把全局描述符表 gdt的位置信息存储在一个叫 gdtr 的寄存器中。
指令就是lgdt gdt_48
, lgdt 就表示把后面的值(gdt_48)放在 gdtr 寄存器中,gdt_48 标签如下:
1 |
|
- **
0x800
**:GDT的界限是2048字节,表示最多可以有256个GDT条目。 - **
512+gdt
和0x9
**:GDT的基地址是0x9xxxx
,具体地址通过将512
加上某个基地址gdt
来计算,即0x9020 + gdt
setup.s 编译后是放在 0x90200 这个内存地址的,而 gdt 表示 setup.s 内的偏移量,所以要加上 0x90200 这个值,才能表示 gdt 这个标签在整个内存中的准确地址。
gdt 这个标签处,就是全局描述符表在内存中的真正数据了。
1 |
|
将这些数值按描述符结构映射:
- Limit 15:0:
0x07FF
- Base 15:0:
0x0000
- Base 23:16:
0x00
- Type:
0x9A
- 二进制:
10011010
- P: 1 (段存在)
- DPL: 00 (特权级别 0)
- S: 1 (代码段或数据段)
- Type: 1010 (代码段,可读、可执行)
- 二进制:
- Limit 19:16:
0x0
- AVL: 0
- D/B: 1 (32 位操作数)
- G: 1 (4KB 粒度)
- Base 31:24:
0x00
将这些部分组合起来,我们得到了一个描述 8MB(2048 个 4KB 页)的代码段描述符,基地址为 0。
所以这个 GDT 定义了两个段,一个是代码段,一个是数据段。每个段的大小都是 8MB,基地址都是 0,粒度为 4KB,运行在 32 位保护模式下。代码段是只读可执行的,而数据段是可读可写的。
第二个和第三个段描述符的段基址都是 0,也就是之后在逻辑地址转换物理地址的时候,通过段选择子查找到无论是代码段还是数据段,取出的段基址都是 0,那么物理地址将直接等于程序员给出的逻辑地址(准确说是逻辑地址中的偏移地址)
idtr 寄存器是中断描述符表,其原理和全局描述符表一样。发生中断时,CPU 会拿着中断号从中断描述符表里寻找中断处理程序的地址,找到以后,就会跳转到相应的中断程序去执行。