3.1认识保护模式
- 先来看一段代码
定义了三个描述符的GDT,GDT的基址和长度,还有两个选择子
- GdtPtr就是放到gdtr里面的东西,你看他是不是6字节啊!
dd就是4字节,不信你看下面gdtr结构示意图。
dw就是俩字节
看到没有,GDT里面有三个段啊,
,两个选择子,定义成了地址!为啥是那样定义的呢?这两个选择子实际上就是偏移量啊,以后可以从选择子上面找到描述符啊哈哈哈哈。这里用的和真是的选择子不一样,这里估计就是为了仿真把!
- LABEL_DESC_CODE32是代码段的描述符地址啊
- xor异或
- xor eax,eax,那不就是把eax清零吗!!
- SHL逻辑左移指令
- shift logical left
- 第三段跳到实模式
- 实现实模式到保护模式的转换
- 这个程序被编译成了COM文件,最简单的二进制文件。
- 意味程序执行时的内存映像和二进制文件映像是一样的
- 上面的程序中secton没什么实质作用
- 不定义它们,从执行结果来看也是一样的(编译出来的二进制有微小差别)
- 定义它们只是使代码结构上清晰,且后面我们对这个程序渐渐扩展的时候,它还有点妙用
- [SECTION.gdt]这个段
- Descriptor是在 pm.inc中定义的宏:
DB定义字节类型变量,一个字节数据占1个字节单元,读完一个,偏移量加1
DW定义字类型变量,一个字数据占2个字节单元,读完一个,偏移量加2
DD定义双字类型变量,一个双字数据占4个字节单元,读完一个,偏移量加4
上面的图片是一个宏,这特妈不就是段描述符吗。8个字节啊!!
-
这个宏表示的不是一段代码,而是一个数据结构,大小8字节。
-
并列有3个 Descriptor,看上去是个结构数组,这个数组的名字叫做GDT
数组的名字叫GDT,从何体现?
- GdtLen是GDT长度,
- GdtPtr
- 前2字节是GDT长度,后4字节GDT的基地址
- 为啥定义这个呀!
- 因为把他们放在gdtr寄存器哦!
-
到一个代码段
-
[BITS 16]指明: 是16位代码段
-
这段修改一些GDT中的值
- 然后执行一些不常见指令
- 最后通过jmp跳转
-
这一句将“真正进入保护模式”
- 实际它将跳转到第三个section,即[ SECTIONS32]中,
- 这个段是32位的,执行最后一小段代码。
- 这段代码看上去是往某个地址处写入了2字节,然后无限循环
- 首先按照注释中所说的办法编译:
- 然后在 Virtual PC中按第2章2.5节的方法将包含 pmtestl. com的文件夹共享,且运行它,结果如图3-1
- 打印了一个红色的字符P,然后再也不动了。
- 程序的最后一部分代码中写入的2字节是写进显存
- 什么是GDT?
- 那些怪怪的指令到底在做什么?
- 了解到
- 程序定义了一个叫做GDT的数据结构。
- 16位代码进行了一些与GDT有关的操作。
- 程序最后跳到32位代码中做了一点操作显存的工作
- 不明就里的内容
- GDT是什么?它是干什么用的?
- 程序对GDT做了什么?
- jmp Selectorcode32:0跟我们从前用过的jmp有什么不同?
- 不打算全面介绍保护模式的课程,本着够用原则,不涉及保护模式的所有内容,只要能*地编写操作系统代码就足够
- V86是保护模式的一部分,如果你不想在自己的操作系统中支持16位程序,你可能永远都不需要知道它的实现方法,学习它简直是浪费
3.1.1 Global Descriptor Table
- IA-32下,CPU有两种工作模式:实模式和保护模式。
- 打开自己的PC,开始时CPU工作在实模式下,经过某种机制之后,オ进入保护模式。
- 保护模式下,CPU有着巨大的寻址能力,并为强大的32位操作系统提供了更好的硬件保障。
- Intel8086是16位的CPU,有16位的寄存器、16位的数据总线
- 及20位的地址总线和1 MB寻址能力。
- 地址是由段和偏移两部分组成的,物理地址遵循这样的计算公式
物理地址=段值( Segment)×16+偏移( offset)
段值和偏移都是16位的
- 80386开始, Intel家族的CPU进入32位时代。
- 80386有32位地址线,寻址空间达4GB。
- 单从寻址这方面说,使用16位寄存器的方法已经不够用了。
- 这时候,我们需要新的方法来提供更大的寻址能力。
- 当然,保护模式的优点不仅仅在这一个方面。
- 实模式下,16位寄存器需用“段:偏移”才能达到IMB的寻址能力
- 有了32位寄存器,一个寄存器就可以寻址4GB的空间,是不是从此段值就被抛弃?
- 没有,地址仍用“SEG:OFFSET”表示,不过保护模式下“段”的概念发生变化
- 实模式下,段值还可看做地址一部分,段值为XXXXH表示以 XXXXOH开始的一段内存
- 保护模式下,段值仍由原来16位的cs、ds等寄存器表示,但此时它仅仅变成一个索引,指向GDT的一个表项,表项定义了段的起始地址、界限、属性等
- 就是GDT(还可能是LDT,这个以后再介绍)。
- GDT表项叫做描述符( Descriptor)。
-
GDT提供段式存储机制,这种机制是通过段寄存器和GDT中的描述符共同提供。
-
看一下如图3-2所示的描述符的结构
-
示意图表示的是代码段和数据段描述符,还有系统段描述符和门描述符。
-
BYTE5和BTYE6中的一堆属性看上复杂
-
段的基址和界限。
- 由于历史问题,它们都被拆开
-
那些属性,暂时先不管
- Descriptor这个宏用比较自动化的方法把段基址、段界限和段属性安排在一个描述符中合适的位置,有兴趣的读者可以研究这个宏的具体内容
- 本例GDT中有3个描述符,DESC_DUMMY、 DESC_CODE32和 DESC_ VIDEO
- DESC_VIDEO,段基址是OB8000h。
- 指向的正是显存
- GDT中的每一个描述符定义一个段,
- cS、ds等段寄存器是如何和这些段对应起来的呢?
- 段[SECTON.s32]中有两句代码是这样
- 段寄存器gs值变成了 Selector Video,
- Selector Video是这样定义
的意思是count=$ -offset A。
- 它好像是DESC_ VIDEO这个描述符相对于GDT基址的偏移。
- 有一个专门的名称,叫做选择子( Selector),它也不是一个偏移,而是稍稍复杂一些,它的结构如图3-3
- T和RPL都为零时,选择子就变成了对应描述符相对于GDT基址的
偏移,就好像我们程序中那样。 - mov [gs:edi], ax
- gs的值是 Selector Video,它指示对应显存的描述符DESC VIDEO
- 这条指令将把ax的值写入显存中偏移位edi的位置
- 整个的寻址方式可以用如图3-4所示
- 示意图,真实的描述符中段基址以及段偏移等内容在描述符中的位置不是像图中这样安排
- “段:偏移”形式的逻辑地址经段机制转化成“线性地址”,而不是“物理地址”,原因以后会提到。
- 上面程序中,线性地址就是物理地址
- 包含描述符的,不仅可是GDT,也可LDT。
-
只剩下[ SECTION.s16]这一段没有分析。
-
既然[ SECTION.s32]是32位的程序,并且在保护模式下执行,
-
[SECTION.s16]的任务一定是从实模式向保护模式跳转
3.1.2实模式到保护模式,不一般的jmp
- 到 SECTION.s16]看一下初始化32位代码段描述符的这一段,
- 先将LABEL_SEG_CODE32的物理地址(即[SECTION.s32]这个段的物理地址)赋给eax
- 然后把它分成三部分赋给描述符DESC_CODE32中的相应位置
- DESC_CODE32的段界限和属性已经指定,所以至此,DESC_CODE32的初始化全部完成
- 接下来把GDT的物理地址填充到了 GdtPtr这个6字节的数据结构,
- 然后执行
lgdt [GdtPtr]
- 这一句将GdtPtr指示的6字节加载到gdtr,gdtr的结构如图3-5
- GdtPtr和gdr的结构完全一样
- 下面是关中断,之所以关中断,
- 因为保护模式下中断处理的机制是不同的,不关掉中断将会出现错误
- 再下面几句打开A20地址线。
- 8086是用SEG:OFFSET这样的模式分段的,
- 它能表示的最大内存是FFFF:FFFF即10FFEFh
- FFFF0+0FFFF=10FFEFh
- 1MB=FFFFF是最大地址啦!!
- 8086只有20位地址总线,只能寻址1MB,如果试图访问超过1MB的地址时会怎样?
- 系统并不异常,回卷(wrap)回去,重新从地址零寻址。
- 到了80286时,真的可以访问到1MB以上的内存,遇到同样的情况,系统不会再回卷寻址,造成了向上不兼容,为保证百分百兼容
- IBM用8042键盘控制器来控制第20个(从零开始数)地址位,这就是A20地址线,如果不被打开,第20个地址位将会总是零。
- 为访问所有内存,需把A20打开,开机默认关闭
- 如何打开呢?有点复杂,只用通过操作端口92h来实现这一种方式,
- 如代码3-1中那样
- 这不是惟一的方法,且在某些个别情况下,这种方法可能会出现问题。但在绝大多数情况下,它是适用的。
- 把crO的第0位置1
- 这一位决定实模式和保护模式
- cr0结构如图3-6
- PE位为0时,CPU运行于实模式,为1时,CPU运行于保护模式。
- mov cr0,eax”这一句之后,系统就运行于保护模式之下了。
- 但此时cs的值仍然是实模式下的值,需把代码段的选择子装入cs。所以,我们需要jmp
- 这个跳转的目标将是描述符DESC_CODE32对应的段的首地址,即标号 LABEL_SEG_CODE32处。