您的位置:首页 > 其它

进入保护模式

2015-02-03 11:02 211 查看
本文为<x86汇编语言:从实模式到保护模式> 第11章笔记

以下图2, 图4和图5截自Intel手册

全局描述符表

全局描述符表中存放着段描述符, 每个段描述符8个字节.

为了跟踪全局描述符表, 处理器内部有一个48位寄存器, 叫做全局描述符表寄存器(GDTR), GDTR分为两部分,分别为32位的线性地址和16的边界, 32位线性基地址部分保存的是全局描述符表在内存中的起始线性地址, 16位边界部分是全局描述符表的边界(界限), 其值等于表的大小(总字节数) - 1, 因为从偏移0开始. 如果界限值是0, 表示表的大小是1字节.

因为GDT的界限是16位的, 故该表最大是2 ^ 16字节, 也就是65536字节(64KB), 又因为一个描述符8字节, 所以该表最多定义8192个描述符.在进入保护模式之后, 处理器立即要按新的内存访问模式工作, 所以, 必须在进入保护模式之前定义GDT, 但是, 由于实模式下只能访问1MB的内存, 故GDT通常多定义在1MB以下的内存范围中, 当然, 允许在进入保护模式后换个位置重新定义GDT



存储器段描述符

每个描述符占8字节, 下图中, 上面位高32位, 下面为低32位



段描述符中, 指定了32位的段起始地址和20位的段边界, 在实模式下, 段地址并非真实的物理地址, 计算物理地址时需要左移4位. 和实模式不同, 32位保护模式下, 段地址是32位的,如果未开启分页功能, 该线性地址就是物理地址. 段基地址可以是0~4GB范围内的任意地址, 段界限用来限制段的扩展范围, 因为访问内存的方法是用段基地址加上偏移量, 所以, 对于向上扩展的段, 如代码段和数据段来说, 偏移量从0开始递增, 段界限决定了偏移量的最大值; 对于向下扩展的段,如栈段来说, 段界限决定了偏移量的最小值.

G位是粒度位, 用于解释段界限的含义, G = 0时, 段界限以字节为单位, 此时段的扩展范围是从1字节到1MB, 因为段界限是20位的, 当G = 1时, 段界限以4KB位单位, 这样, 段的扩展范围是从4KB到4GB.

S位用于指定描述符的类型, S = 0时, 表示是一个系统段, S = 1时, 表示是一个代码段或数据段(栈也属于特殊数据段)

DPL表示描述符的特权级, 特权级分别是0, 1, 2, 3, 其中0是最高特权级, 3是最低特权级, 刚进保护模式时执行的代码具有0特权级(可以看成是从处理器那里继承来的), 在这里, 描述符的特权级用于指定要访问该段做必须具有的特权级, 如果这里的数值是2, 那么, 只有特权级0, 1和2的程序能够访问该段, 特权级3访问时, 处理器会予以阻止.

P是段存在位, P位用于指示描述符所对应的段是否存在, 一般来说, 描述符所指示的段都位于内存中. 但是, 当内存紧张时, 有可能只是建立了描述符, 对应的内存空间并不存在, 这时, 就应当把描述符P位清0, 表示段并不存在. 另外, 同样是在内存紧张的情况下, 会把很少用到的段换出到硬盘, 腾出空间给急需内存的程序使用(当前正在执行的), 这时, 同样要把P位清0, 当再次轮到它执行时, 再装入内存, 然后将P位置1. P位是由处理器负责检查的, 每当通过描述符访问内存中的段时, 如果P位是0, 处理器就产生一个中断, 通常该中断处理程序是由操作系统提供, 该处理过程的任务是负责将该段从硬盘换回内存, 并将P位置1, 在多用户, 多任务的系统中, 这时一种常用的虚拟内存调度策略.

D/B位是默认的操作数大小或默认的栈指针大小, 该标志对不同的段有不同的效果, 对于代码段, 此位称作D位, 用于指示指令中默认的偏移地址和操作数大小, D = 0表示是16位的, D = 1, 指示是32位的. 如果D = 0, 处理器在这个段执行时, 将使用16位指令指针寄存器IP, 否者使用32位的EIP. 对于栈段来说, 该位就做B位, 用于在进行隐式的栈操作时, 是使用SP还是使用ESP, 隐式的栈指令包括 push, pop和call等, 如果该位位0, 则使用SP, 栈段的上部边界位0xffff, 否者使用ESP, 上部边界为0xffffffff.

L位是64位代码位, 保留此位给64位处理器使用

TYPE共4位, 指示描述符的子类型, 或者说是类别, 对数据段来说, 分别是X, E, W, A位, 对代码段来说是X, C, R, A位


上表中, A位是已访问位, 每当该段被访问时, 处理器自动将该位置1, 对该位清0是由软件(操作系统)负责的, 通过定期监视该位的状态, 可以统计出该段的使用频率. X代表执行, E位指示扩展方向, W位指示可写, C位指示段是否为特权级依从的, C = 0表示非依从的代码段, 这样的代码段只能供同特权级的程序使用, 或者通过门调用, C = 1则表示这样的代码段为依从代码段, 可以从特权级比它低的程序调用并进入. 代码段总是可以执行的, 但是为了防止程序被破坏, 它是不能写入的. 它的R位不是用来限制处理器是否可读的, 而是用来限制程序的, 一个典型的例子是使用段超越前缀CS:来访问代码段中的内存.

AVL位是软件可以使用的位, 通常由操作系统来使用他, 处理器不使用它.

段选择子



3~15是段描述符在描述符中的索引, TI = 0时表示描述符在GDT中, TI = 1时, 描述符在LDT中, RPL是请求特权级, 表示给出当前选择子的那个程序的特权级, 正是该程序要求访问这个内存段.

段描述符高速缓存器



如上图所示, 在32位处理器上运行时, 每个段寄存器还包括一个不可见的部分, 称为描述符高速缓存器, 用来存放段的线性基地址, 段界限和段属性, 既然不可见, 那就是处理器不希望我们访问它. 事实上, 我们也没有任何办法来访问这个不可见部分, 它是由处理器内部使用的.

当处理器在执行任何改变段选择器(段寄存去CS, DS等)的指令时(比如pop, jmp, call, ret等), 就将指令中提供的索引号(就是选择子中的索引)乘以8(一个描述符8字节)作为偏移地址, 同GDTR中提供的线性基地址相加, 以访问GDT, 如果没有发现什么问题(比如超出了GDT的界限), 就自动将找到的描述符加载到不可见的描述符高速缓存部分. 加载的部分包括段的线性基地址, 段界限和段的访问属性. 此后, 每当有访问内存时, 就不再访问GDT中的描述符, 直接用当前段寄存器描述符高速缓存器提供的线性基地址.

进入保护模式

从0x92端口读取的数据, 第2位为开A20, 第一位 = 1为重启

在保护模式下, 不允许使用mov指令改变段寄存器CS的内容, 比如: mov cs, ax, 企图这样做将导致处理器产生一个无效操作码的异常中断.

下面是一段主引导区代码, 实验环境书中也有说, 我的资源中也有配书资源下载, 里面有说如何配置VirtualBox和Bochs, 这里说一下配置Bochs, 配书资源中关于配置Bochs中的

Disk&Boot --> ATA channel 0 --> First HD/CD on channel 0 --> Type of disk image这个选项是vpc, 我用2.6.2配置时选vpc无法启动, 得选flat才可以, 2.6.0貌似选vpc可以

mov ax, cs
02.        mov ss, ax
03.        mov sp, 0x7c00
04.
05.        ; 计算GDT所在地址的逻辑段地址
06.        mov ax, [cs:gdt_base + 0x7c00]
07.        mov dx, [cs:gdt_base + 2 +0x7c00]
08.        mov bx, 0x10
09.        div bx
10.        mov ds, ax              ; 商为段地址
11.        mov bx, dx              ; 余数为偏移地址
12.
13.        ; 全局描述符#0, 第一项必须为0
14.        mov dword [bx + 0x00], 0x00000000
15.        mov dword [bx + 0x04], 0x00000000
16.
17.        ; 全局描述符#1, 代码段描述符
18.        ; 线性基地址: 0x00007c00, 段界限: 0x01ff, 段界限在数值上等于段的长度 - 1, 因此该段的长度是0x200, 即512字节
19.        ; 粒度位字节(G = 0), 属于存储器段(S = 1)
20.        ; 32位的段(D/B = 1), 位于内存当中(P = 1)
21.        ; 段的特权级位0(DPL = 00)
22.        ; 只执行(TYPE = 1000)
23.        mov dword [bx + 0x08], 0x7c0001ff
24.        mov dword [bx + 0x0c], 0x00409800
25.
26.        ; 全局描述符#2, 数据段描述符
27.        ; 线性基地址: 0x000b8000, 段界限: 0xffff
28.        ; 粒度位字节(G = 0), 属于存储器段(S = 1)
29.        ; 32位的段(D/B = 1), 位于内存当中(P = 1)
30.        ; 段的特权级位0(DPL = 00)
31.        ; 可读可写, 向上扩展的数据段(TYPE = 0010)
32.        mov dword [bx + 0x10], 0x8000ffff
33.        mov dword [bx + 0x14], 0x0040920b
34.
35.        ; 全局描述符#3, 栈段描述符
36.        ; 线性基地址: 0x00000000, 段界限: 0x07a00
37.        ; 粒度位字节(G = 0), 属于存储器段(S = 1)
38.        ; 32位的段(D/B = 1), 位于内存当中(P = 1)
39.        ; 段的特权级位0(DPL = 00)
40.        ; 可读可写, 向下扩展的数据段, 在这里是栈段(TYPE = 0110)
41.        mov dword [bx + 0x18], 0x00007a00
42.        mov dword [bx + 0x1c], 0x00409600
43.
44.        ; 初始化全局描述符表
45.        mov word [cs:gdt_size + 0x7c00], 31     ; 全局描述符表界限(总字节数 - 1)
46.        lgdt [cs:gdt_size + 0x7c00]             ; 加载全局描述符表
47.
48.        ; 开A20
49.        in al, 0x92
50.        or al, 0x02
51.        out 0x92, al
52.
53.        cli                                     ; 保护模式下中断机制尚未建立, 关中断
54.
55.        ; 打开保护模式, cr0寄存器0位置1
56.        mov eax, cr0
57.        or eax, 1
58.        mov cr0, eax
59.
60.        ; 进入保护模式, 详情参见本书P199 11.7 清空流水线并串行化处理器
61.        ; 注意, 不管你用的是16位远转移, 还是32位远转移
62.        ; 因为现在已经处于保护模式下, 处理器都将把第一个参数0x0008视为选择子
63.        ; 而不是实模式下的逻辑段地址
64.        jmp dword 0x0008:_ProtectMode
65.
66.        [bits 32]
67.
68._ProtectMode:
69.        ; 选择子00000000000_10_000B, 索引为2的描述符
70.        mov ax, 0x10
71.        mov ds, ax
72.
73.        mov byte [0x00], 'H'
74.        mov byte [0x02], 'e'
75.        mov byte [0x04], 'l'
76.        mov byte [0x06], 'l'
77.        mov byte [0x08], '0'
78.        mov byte [0x0a], ' '
79.        mov byte [0x0c], 'O'
80.        mov byte [0x0e], 'n'
81.        mov byte [0x10], 'z'
82.        mov byte [0x12], '.'
83.        mov byte [0x14], '.'
84.        mov byte [0x16], '.'
85.
86.        hlt                   ; 因为关中断, 所以处理器不会被唤醒
87.
88.gdt_size    dw 0
89.gdt_base    dd 0x7e00
90.
91.times       510 - ($ - $$) db 0
92.            dw 0xaa55
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: